-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdartart.html
398 lines (376 loc) · 16.6 KB
/
dartart.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>DartArt</title>
<meta name="keywords" content="images, art, dart, grayscale">
<meta name="description" content="Convert image pixels to small darts, pixel luminance is mapped to convexness of the dart">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="author" content="Maarten Pennings">
<style>
body { caret-color: transparent; }
.normalbutton {padding:3px; border: 1px solid black; background-color:lightgray; border-radius:6px; color:black; text-decoration: none; }
.disabledbutton {padding:3px; border: 1px solid gray ; background-color:lightgray; border-radius:6px; color:gray ; text-decoration: none; }
</style>
<script type="text/javascript">
var step0canvas // loaded
var step0canvas // re-sized
var step2canvas // re-grayed
var step3canvas // darts
var step4canvas // save
function step4_activate() {
// Prepare to read step2canvas
const ctx3 = step3canvas.getContext('2d')
const data3 = ctx3.getImageData(0,0, step3canvas.width, step3canvas.height)
// Prepare to write step4canvas
step4canvas = document.getElementById('step4canvas')
step4canvas.width = step3canvas.width
step4canvas.height = step3canvas.height
const ctx4 = step4canvas.getContext('2d')
ctx4.putImageData(data3,0,0)
// Create save button's href
const image = document.getElementById("step4canvas").toDataURL("image/png").replace("image/png", "image/octet-stream")
document.getElementById("save").setAttribute("href", image)
}
function step3_drawdarts(tilesize, tilemargin, data2, ctx3) {
const dartoffset = (tilemargin)/2
const dartrange= (tilesize-tilemargin)
ctx3.fillStyle = '#F8F8F8' // Maybe FFFFFF is better
ctx3.fillRect(0, 0, step3canvas.width, step3canvas.height)
for( y=0; y<data2.height; y++ ) {
for( x=0; x<data2.width; x++ ) {
// Compute index `ix` of pixel (x,y)
const ix = x*4 + y*4*data2.width
// Get luminance value, and map to dart position
const lum= data2.data[ix]
const dartpos= Math.floor( dartoffset+dartrange*lum/255 )
// Draw dart
ctx3.fillStyle = '#080808' // Maybe 000000 is better
ctx3.beginPath()
ctx3.moveTo(x*tilesize+tilesize, y*tilesize)
ctx3.lineTo(x*tilesize+tilesize, y*tilesize+tilesize)
ctx3.lineTo(x*tilesize, y*tilesize+tilesize)
ctx3.lineTo(x*tilesize+dartpos, y*tilesize+dartpos)
ctx3.closePath()
ctx3.fill()
}
}
}
function step3_activate() {
// Prepare to read step2canvas
const ctx2 = step2canvas.getContext('2d')
const data2 = ctx2.getImageData(0,0, step2canvas.width, step2canvas.height)
// Prepare to write step3canvas
step3canvas = document.getElementById('step3canvas')
const ctx3 = step3canvas.getContext('2d')
// Setup sliders (dart drawing is slow, so here we use onchange, not oninput)
const slidersize = document.getElementById('step3sizeslider')
const valuesize = document.getElementById('step3sizevalue')
const update4 = function() {
const tilesize = parseInt(slidersize.value)
const tilemargin = parseInt(slidermargin.value)
valuesize.innerHTML= tilesize+" pixels"
valuemargin.innerHTML= tilemargin+" pixels ("+(tilesize-tilemargin)+" effective)"
step3canvas.width = step2canvas.width * tilesize
step3canvas.height = step2canvas.height * tilesize
step3_drawdarts(tilesize, tilemargin, data2, ctx3)
}
slidersize.oninput = update4
const slidermargin = document.getElementById('step3marginslider')
const valuemargin = document.getElementById('step3marginvalue')
slidermargin.oninput = update4
// Initial convert - dragging sliders will do more converts
slidersize.value = 13
valuesize.innerHTML= slidersize.value
slidermargin.value = 2
valuemargin.innerHTML= slidermargin.value
update4()
}
function step2_grayrange(data) {
var nummin=0
var nummax=0
var lummin = 255
var lummax = 0
for( y=0; y<data.height; y++ ) {
for( x=0; x<data.width; x++ ) {
// Compute index `ix` of pixel (x,y)
const ix = x*4 + y*4*data.width
// The R, G, and B are the same (thanks to step 1), and equal the gray level
const lum= data.data[ix+0]
// Maintain min, max
if( lum<lummin ) { lummin=lum; nummin=0 }
if( lum>lummax ) { lummax=lum; nummax=0 }
// Count min's, max's
if( lum==lummin ) nummin++
if( lum==lummax ) nummax++
}
}
return [lummin,lummax, nummin, nummax]
}
function step2_rgb2gray(lummin,lummax, data1,data2) {
for( y=0; y<data1.height; y++ ) {
for( x=0; x<data1.width; x++ ) {
// Compute index `ix` of pixel (x,y)
const ix = x*4 + y*4*data1.width
// Get RGB values
const red= data1.data[ix+0]
const green= data1.data[ix+1]
const blue= data1.data[ix+2]
const alpha= data1.data[ix+3]
// Convert to luminance https://en.wikipedia.org/wiki/YUV#Full_swing_for_BT.601
var lum= (77*red + 150*green + 29*blue)/256
// Map lummin..lummax to 0..255
lum =255*(lum-lummin)/(lummax-lummin)
// Clip and integer
if( lum<0 ) lum= 0
if( lum>255 ) lum =255
lum = Math.floor(lum)
// Write to data2
data2.data[ix+0]= lum
data2.data[ix+1]= lum
data2.data[ix+2]= lum
data2.data[ix+3]= 255
}
}
}
function step2_activate() {
// Prepare to read step1canvas
const ctx1 = step1canvas.getContext('2d')
const data1 = ctx1.getImageData(0,0, step1canvas.width, step1canvas.height)
// Show actual grayscale range
const [lummin,lummax,nummin,nummax] = step2_grayrange(data1)
document.getElementById('step2feedback1').innerHTML = "luminance range orignal image: "+lummin+" ("+nummin+"\u00D7) .. "+lummax+" ("+nummax+"\u00D7)"
// Prepare to write step2canvas
step2canvas = document.getElementById('step2canvas')
step2canvas.width = step1canvas.width
step2canvas.height = step1canvas.height
const ctx2 = step2canvas.getContext('2d')
const data2 = ctx2.getImageData(0,0, step2canvas.width, step2canvas.height)
const update2 = function(lummin,lummax) {
step2_rgb2gray(lummin, lummax, data1, data2)
ctx2.putImageData(data2,0,0)
const [_lummin, _lummax, _nummin, _nummax] = step2_grayrange(data2)
document.getElementById('step2feedback2').innerHTML = "luminance range after mapping: "+_lummin+" ("+_nummin+"\u00D7) .. "+_lummax+" ("+_nummax+"\u00D7)"
}
// Setup sliders
const minslider = document.getElementById('step2yminslider')
const minvalue = document.getElementById('step2yminvalue')
minslider.oninput = function(e) {
minvalue.innerHTML= this.value
update2(minslider.value,maxslider.value)
}
const maxslider = document.getElementById('step2ymaxslider')
const maxvalue = document.getElementById('step2ymaxvalue')
maxslider.oninput = function(e) {
maxvalue.innerHTML= this.value
update2(minslider.value,maxslider.value)
}
// Initial convert - dragging sliders will do more converts
minslider.value = lummin
minvalue.innerHTML= minslider.value
maxslider.value = lummax
maxvalue.innerHTML= maxslider.value
update2(minslider.value,maxslider.value)
}
function step1_scale(data0,data1) {
function gray0(x,y) {
// Compute index `ix` of pixel (x,y)
const ix = x*4 + y*4*data0.width
// Get RGB values
const red= data0.data[ix+0]
const green= data0.data[ix+1]
const blue= data0.data[ix+2]
const alpha= data0.data[ix+3]
// Convert to luminance https://en.wikipedia.org/wiki/YUV#Full_swing_for_BT.601
const lum= (77*red + 150*green + 29*blue)/256
// return
return lum
}
for( y1=0; y1<data1.height; y1++ ) {
const y0lo= Math.round( data0.height * y1 / data1.height )
const y0hi= Math.round( data0.height * (y1+1) / data1.height )
for( x1=0; x1<data1.width; x1++ ) {
const x0lo= Math.round( data0.width * x1 / data1.width )
const x0hi= Math.round( data0.width * (x1+1) / data1.width )
var sum=0;
for( y0=y0lo; y0<y0hi; y0++)
for( x0=x0lo; x0<x0hi; x0++)
sum += gray0(x0,y0)
const lum = Math.round( sum / ((x0hi-x0lo)*(y0hi-y0lo)) )
const ix = x1*4 + y1*4*data1.width
data1.data[ix+0]= lum
data1.data[ix+1]= lum
data1.data[ix+2]= lum
data1.data[ix+3]= 255
}
}
}
function step1_activate() {
// Prepare to read step0canvas
const ctx0 = step0canvas.getContext('2d')
const data0 = ctx0.getImageData(0,0, step0canvas.width, step0canvas.height)
// Show input image size
const inwidth = step0canvas.width
const inheight = step0canvas.height
document.getElementById('step1feedback1').innerHTML = "size original image: "+inwidth+" \u00D7 "+inheight
// Prepare to write step1canvas
step1canvas = document.getElementById('step1canvas')
const update1 = function(outwidth,outheight) {
step1canvas.width = outwidth
step1canvas.height = outheight
const ctx1 = step1canvas.getContext('2d')
const data1 = ctx1.getImageData(0,0, outwidth, outheight)
step1_scale(data0, data1)
ctx1.putImageData(data1,0,0)
document.getElementById('step1feedback2').innerHTML = "size after scaling "+outwidth+" \u00D7 "+outheight
}
// Setup sliders
const widthslider = document.getElementById('step1widthslider')
const widthvalue = document.getElementById('step1widthvalue')
widthslider.min = 1
widthslider.max = inwidth>256 ? 256 : inwidth // arbitrary limit
const heightslider = document.getElementById('step1heightslider')
const heightvalue = document.getElementById('step1heightvalue')
heightslider.min = 1
heightslider.max = inheight>256 ? 256 : inwidth // arbitrary limit
widthslider.oninput = function(e) {
widthvalue.innerHTML= this.value
const outheight = Math.round(inheight * this.value/inwidth)
if( outheight!=heightslider.value ) {
heightslider.value = outheight
heightslider.oninput()
}
update1(widthslider.value,heightslider.value)
}
heightslider.oninput = function(e) {
heightvalue.innerHTML= this.value
const outwidth = Math.round(inwidth * this.value/inheight)
if( outwidth!=widthslider.value ) {
widthslider.value = outwidth
widthslider.oninput()
}
update1(widthslider.value,heightslider.value)
}
// Initial output width - dragging sliders will change
heightslider.value = inheight>55 ? 55 : inwidth // 55 fits on FHD screen
heightvalue.innerHTML= heightslider.value
heightslider.oninput()
}
function step0_onload() {
step0canvas = document.getElementById('step0canvas')
step0canvas.width = this.width
step0canvas.height = this.height
var ctx0 = step0canvas.getContext('2d')
ctx0.drawImage(this,0,0)
}
function step0_onerror() {
alert("Error loading image")
}
function step0_activate() {
document.getElementById('step0file').onchange = function(e) {
img = new Image()
img.onload= step0_onload
img.onerror = step0_onerror
img.src = URL.createObjectURL(this.files[0])
}
}
function step_activate(step) {
const numsteps=5
// Special check for 0->1
if( step==1 && (step0canvas==undefined || step0canvas.width<1 || step0canvas.height<1) ) {
alert("First load an image")
return
}
// Generate steps overview (button bar at the top)
for(i=0; i<numsteps; i++) {
const elm=document.getElementById('button'+i)
const ii=i // kill closure
if( i<step ) {
elm.className="normalbutton"
elm.onclick=function(){step_activate(ii)} // can jump back
} else if( i==step ) {
elm.className="normalbutton"
elm.onclick=null // can NOT jump to self
} else {
elm.className="disabledbutton"
elm.onclick=null // can NOt skip steps
}
}
// Show the div with step `step`, hide all other steps
for(i=0; i<numsteps; i++) {
const elm=document.getElementById('step'+i)
if( i==step )
elm.style.display = "block"
else
elm.style.display = "none"
}
// Activate step `step`
window["step"+step+'_activate']()
}
function main() {
step_activate(0)
}
</script>
</head>
<body onload="main()">
<p id="overview">
<a href="https://github.com/maarten-pennings/DartArt" style="color:black;text-decoration: none;">DartArt v4</a>
<span id='button0'>LOAD</span>
➤
<span id='button1'>SIZE</span>
➤
<span id='button2'>GRAY</span>
➤
<span id='button3'>DART</span>
➤
<span id='button4'>SAVE</span>
</p>
<div id="step0">
<hr>
<p><b>Select the input image (that will be "dartified")</b></p>
<p><input class="normalbutton" type="file" id="step0file"></p>
<p><canvas id="step0canvas" width="0" height="0"></canvas><p>
<hr>
<p><span class="normalbutton" onclick='step_activate(1)'>next</span></p>
</div>
<div id="step1">
<hr>
<p><b>Select output size</b></p>
<p id="step1feedback1">...</p>
<p>width <input type="range" id="step1widthslider"><span id="step1widthvalue">...</span></p>
<p>height <input type="range" id="step1heightslider"><span id="step1heightvalue">...</span></p>
<p id="step1feedback2">...</p>
<p><canvas id="step1canvas" width="0" height="0"></canvas><p>
<hr>
<p><span class="normalbutton" onclick='step_activate(2)'>next</span></p>
</div>
<div id="step2">
<hr>
<p><b>Map luminance range min..max to 0..255</b></p>
<p id="step2feedback1">...</p>
<p>min <input type="range" min="0" max="255" id="step2yminslider"><span id="step2yminvalue">...</span></p>
<p>max <input type="range" min="0" max="255" id="step2ymaxslider"><span id="step2ymaxvalue">...</span></p>
<p id="step2feedback2">...</p>
<canvas id="step2canvas" width="0" height="0"></canvas>
<hr>
<p><span class="normalbutton" onclick='step_activate(3)'>next</span></p>
</div>
<div id="step3">
<hr>
<p><b>Select tile size, and the margin (not used for the dart)</b></p>
<p>tile size <input type="range" min="4" max="75" id="step3sizeslider"><span id="step3sizevalue">...</span></p>
<p>tile margin <input type="range" min="0" max="15" id="step3marginslider"><span id="step3marginvalue">...</span></p>
<canvas id="step3canvas" width="0" height="0"></canvas>
<hr>
<p><span class="normalbutton" onclick='step_activate(4)'>next</span></p>
</div>
<div id="step4">
<hr>
<p><b>Download to save</b></p>
<canvas id="step4canvas"></canvas>
<hr>
<p><a id="save" class="normalbutton" download="dartart.png">download</a></p>
</div>
</body>
</html>