|
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