|
24 | 24 | */
|
25 | 25 | let line_count = 500;
|
26 | 26 | let line_opacity = 0.2;
|
27 |
| - let num_anchors = 188; |
28 |
| - let num_anchor_gap = 0; |
29 |
| - let penalty = 100; |
| 27 | + let num_anchors = 200; |
| 28 | + let num_anchor_gap = 10; |
| 29 | + let penalty = 500; |
30 | 30 | let start_anchor = 0;
|
31 | 31 |
|
32 | 32 | $: num_anchor_gap = Math.min(num_anchor_gap, num_anchors / 2 - 1);
|
|
70 | 70 | }
|
71 | 71 |
|
72 | 72 | function draw() {
|
| 73 | + const isPortrait = windowRatio > 1; |
73 | 74 | const maxSize = Math.max(
|
74 | 75 | 250,
|
75 |
| - (windowRatio > 1 ? window.innerHeight / 3 : window.innerWidth / 3) - 100 |
| 76 | + (isPortrait ? window.innerHeight / 3 : window.innerWidth / 3) - 100 |
76 | 77 | );
|
77 | 78 |
|
78 | 79 | const imageRatio = imageSource.height / imageSource.width;
|
|
85 | 86 |
|
86 | 87 | /** @type CanvasRenderingContext2D */
|
87 | 88 | // @ts-ignore
|
88 |
| - const imagePreviewCtx = imagePreview.getContext('2d'); |
| 89 | + const imagePreviewCtx = imagePreview.getContext('2d', { willReadFrequently: true }); |
89 | 90 | imagePreviewCtx.clearRect(0, 0, width, height);
|
90 | 91 | imagePreviewCtx.drawImage(imageSource, 0, 0, width, height);
|
91 | 92 |
|
|
144 | 145 | await init();
|
145 | 146 | windowRatio = window.innerHeight / window.innerWidth;
|
146 | 147 | });
|
| 148 | +
|
| 149 | + /** |
| 150 | + * @param {Event & { currentTarget: EventTarget & HTMLInputElement }} e |
| 151 | + */ |
| 152 | + async function upload(e) { |
| 153 | + if (e.currentTarget.files) { |
| 154 | + await imageUpload(e.currentTarget.files[0]); |
| 155 | + draw(); |
| 156 | + state = STATES.CONFIGURE; |
| 157 | + } |
| 158 | + } |
147 | 159 | </script>
|
148 | 160 |
|
149 | 161 | <svelte:head>
|
|
159 | 171 | <canvas bind:this={imagePreview} />
|
160 | 172 | </div>
|
161 | 173 | <div class="join-item indicator">
|
162 |
| - <span class="indicator-item badge badge-primary">monochrome</span> |
| 174 | + <span class="indicator-item badge badge-secondary">monochrome</span> |
163 | 175 | <canvas bind:this={imageMonochrome} />
|
164 | 176 | </div>
|
165 | 177 | <div class="join-item indicator">
|
166 |
| - <span class="indicator-item badge badge-secondary">output</span> |
| 178 | + <span class="indicator-item badge badge-warning">output</span> |
167 | 179 | <canvas bind:this={imageDraw} />
|
168 | 180 | </div>
|
169 | 181 | </div>
|
|
175 | 187 |
|
176 | 188 | {#if state == STATES.UPLOAD}
|
177 | 189 | <div class="w-screen h-screen flex p-4">
|
178 |
| - <label |
179 |
| - class="m-auto px-10 py-4 flex transition border-2 |
180 |
| - border-gray-300 border-dashed rounded-lg |
181 |
| - appearance-none cursor-pointer |
182 |
| - hover:border-gray-400 focus:outline-none" |
183 |
| - > |
184 |
| - <span class="flex items-center space-x-2"> |
185 |
| - <svg |
186 |
| - xmlns="http://www.w3.org/2000/svg" |
187 |
| - class="w-6 h-6 text-gray-200" |
188 |
| - fill="none" |
189 |
| - viewBox="0 0 24 24" |
190 |
| - stroke="currentColor" |
191 |
| - stroke-width="2" |
192 |
| - > |
193 |
| - <path |
194 |
| - stroke-linecap="round" |
195 |
| - stroke-linejoin="round" |
196 |
| - d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" |
197 |
| - /> |
198 |
| - </svg> |
199 |
| - <span class="font-medium text-gray-400"> Upload an Image </span> |
200 |
| - </span> |
201 |
| - <input |
202 |
| - type="file" |
203 |
| - on:change={async (e) => { |
204 |
| - if (e.currentTarget.files) { |
205 |
| - await imageUpload(e.currentTarget.files[0]); |
206 |
| - draw(); |
207 |
| - state = STATES.CONFIGURE; |
208 |
| - } |
209 |
| - }} |
210 |
| - class="hidden" |
211 |
| - /> |
212 |
| - </label> |
| 190 | + <div class="m-auto flex flex-col"> |
| 191 | + <h1 class="title">string-bean</h1> |
| 192 | + <div class="flex"> |
| 193 | + <label for="upload-button" class="btn btn-outline btn-success m-auto"> |
| 194 | + <span> Upload Image </span> |
| 195 | + </label> |
| 196 | + <input id="upload-button" type="file" on:change={upload} class="hidden" /> |
| 197 | + </div> |
| 198 | + </div> |
213 | 199 | </div>
|
214 | 200 | {:else if state == STATES.CONFIGURE}
|
215 |
| - <div class="flex flex-col px-8 gap-10 items-center"> |
| 201 | + <div class="flex flex-col p-8 gap-10 items-center"> |
216 | 202 | <div class="join">
|
217 |
| - <button class="btn btn-info join-item" on:click={() => (state = STATES.UPLOAD)}> |
218 |
| - Upload Another |
| 203 | + <label class="join-item btn btn-outline btn-success m-auto" for="upload-button"> |
| 204 | + <span> Upload Another </span> |
| 205 | + </label> |
| 206 | + <input id="upload-button" type="file" on:change={upload} class="hidden" /> |
| 207 | + <button class="join-item btn btn-outline btn-primary" on:click={() => draw()}> |
| 208 | + Redraw |
219 | 209 | </button>
|
220 |
| - <button class="btn btn-primary join-item" on:click={() => draw()}> Redraw </button> |
221 | 210 | </div>
|
222 | 211 |
|
223 | 212 | <div>
|
224 |
| - <div class="grid max-w-3xl gap-5"> |
| 213 | + <div class="grid max-w-3xl gap-2"> |
225 | 214 | <div class="flex gap-5 items-center">
|
226 |
| - <kbd class="kbd">Anchor Count</kbd> |
| 215 | + <div class="badge badge-info"> |
| 216 | + <kbd class="whitespace-nowrap w-24 text-center">Anchor Count</kbd> |
| 217 | + </div> |
227 | 218 | <input
|
228 | 219 | type="range"
|
229 | 220 | min="0"
|
|
236 | 227 | min="0"
|
237 | 228 | max="500"
|
238 | 229 | bind:value={num_anchors}
|
239 |
| - class="input input-primary w-48" |
| 230 | + class="input input-sm input-bordered w-48" |
240 | 231 | />
|
241 | 232 | </div>
|
242 | 233 |
|
243 | 234 | <div class="flex gap-5 items-center">
|
244 |
| - <kbd class="kbd">Anchor Gaps</kbd> |
| 235 | + <div class="badge badge-info"> |
| 236 | + <kbd class="whitespace-nowrap w-24 text-center">Anchor Gaps</kbd> |
| 237 | + </div> |
245 | 238 | <input
|
246 | 239 | type="range"
|
247 | 240 | min="0"
|
|
254 | 247 | min="0"
|
255 | 248 | max={Math.round(num_anchors / 2) - 1}
|
256 | 249 | bind:value={num_anchor_gap}
|
257 |
| - class="input input-primary w-48" |
| 250 | + class="input input-sm input-bordered w-48" |
258 | 251 | />
|
259 | 252 | </div>
|
260 | 253 |
|
261 | 254 | <div class="flex gap-5 items-center">
|
262 |
| - <kbd class="kbd">Chord Count</kbd> |
| 255 | + <div class="badge badge-info"> |
| 256 | + <kbd class="whitespace-nowrap w-24 text-center">Line Count</kbd> |
| 257 | + </div> |
263 | 258 | <input
|
264 | 259 | type="range"
|
265 | 260 | min="0"
|
|
272 | 267 | min="0"
|
273 | 268 | max="2000"
|
274 | 269 | bind:value={line_count}
|
275 |
| - class="input input-primary w-48" |
| 270 | + class="input input-sm input-bordered w-48" |
276 | 271 | />
|
277 | 272 | </div>
|
278 | 273 |
|
279 | 274 | <div class="flex gap-5 items-center">
|
280 |
| - <kbd class="kbd">Line Opacity</kbd> |
| 275 | + <div class="badge badge-info"> |
| 276 | + <kbd class="whitespace-nowrap w-24 text-center">Line Opacity</kbd> |
| 277 | + </div> |
281 | 278 | <input
|
282 | 279 | type="range"
|
283 | 280 | min="0"
|
|
291 | 288 | min="0"
|
292 | 289 | max="1"
|
293 | 290 | bind:value={line_opacity}
|
294 |
| - class="input input-primary w-48" |
| 291 | + class="input input-sm input-bordered w-48" |
295 | 292 | />
|
296 | 293 | </div>
|
297 | 294 |
|
298 | 295 | <div class="flex gap-5 items-center">
|
299 |
| - <kbd class="kbd">Shape</kbd> |
| 296 | + <div class="badge badge-info"> |
| 297 | + <kbd class="whitespace-nowrap w-24 text-center"> Shape</kbd> |
| 298 | + </div> |
300 | 299 | <select bind:value={shape} class="select select-bordered w-full max-w-xs">
|
301 | 300 | {#each Object.values(SHAPES) as s}
|
302 | 301 | <option>{s}</option>
|
|
308 | 307 | </div>
|
309 | 308 | {/if}
|
310 | 309 |
|
311 |
| -<footer class="p-5" /> |
| 310 | +<style> |
| 311 | + .title { |
| 312 | + font-size: 3rem; |
| 313 | + font-weight: bolder; |
| 314 | + background: -webkit-linear-gradient(#25af75, #51ffbc); |
| 315 | + background-clip: text; |
| 316 | + -webkit-text-fill-color: transparent; |
| 317 | + padding-bottom: 1rem; |
| 318 | + } |
| 319 | +</style> |
0 commit comments