- commit
- d66c204a8a78b8791a93d156e7fd9ed46550dbd9
- parent
- cbb4c06b777ed3cdef6c20019a8a32649ac67e85
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2023-02-09 07:58
init
Diffstat
| A | index.html | 15 | +++++++++++++++ |
| A | paint.js | 216 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | style.css | 16 | ++++++++++++++++ |
3 files changed, 247 insertions, 0 deletions
diff --git a/index.html b/index.html
@@ -0,0 +1,15 @@ -1 1 <!DOCTYPE html> -1 2 <html> -1 3 <head> -1 4 <meta charset="utf-8" /> -1 5 <title>Paint by the numbers</title> -1 6 <link rel="stylesheet" href="style.css"> -1 7 </head> -1 8 <body> -1 9 <div> -1 10 <input type="file"> -1 11 </div> -1 12 <canvas></canvas> -1 13 <script src="paint.js" type="module"></script> -1 14 </body> -1 15 </html>
diff --git a/paint.js b/paint.js
@@ -0,0 +1,216 @@
-1 1 var input = document.querySelector('input');
-1 2
-1 3 var canvas = document.querySelector('canvas');
-1 4 var ctx = canvas.getContext('2d');
-1 5
-1 6 var offcanvas = document.createElement('canvas');
-1 7 var octx = offcanvas.getContext('2d');
-1 8
-1 9 var data;
-1 10 var pencil = 0;
-1 11
-1 12 var zoom = 1;
-1 13 var dx = 0;
-1 14 var dy = 0;
-1 15
-1 16 var onAnimation = function(fn) {
-1 17 var called = false;
-1 18 return () => {
-1 19 if (!called) {
-1 20 called = true;
-1 21 window.requestAnimationFrame(() => {
-1 22 fn();
-1 23 called = false;
-1 24 });
-1 25 }
-1 26 };
-1 27 };
-1 28
-1 29 var file2img = function(file) {
-1 30 return new Promise((resolve, reject) => {
-1 31 var img = new Image();
-1 32 var url = URL.createObjectURL(file);
-1 33
-1 34 img.onerror = err => {
-1 35 URL.revokeObjectURL(url);
-1 36 reject(err);
-1 37 };
-1 38 img.onload = () => {
-1 39 URL.revokeObjectURL(url);
-1 40 resolve(img);
-1 41 };
-1 42
-1 43 img.src = url;
-1 44 });
-1 45 };
-1 46
-1 47 var img2data = function(img, scale) {
-1 48 var _canvas = document.createElement('canvas');
-1 49 _canvas.height = Math.round(img.height * scale);
-1 50 _canvas.width = Math.round(img.width * scale);
-1 51 var _ctx = _canvas.getContext('2d');
-1 52 _ctx.drawImage(img, 0, 0, _canvas.width, _canvas.height);
-1 53 return _ctx.getImageData(0, 0, _canvas.width, _canvas.height);
-1 54 };
-1 55
-1 56 var round = function(c) {
-1 57 return Math.floor(c / 51) * 3;
-1 58 };
-1 59
-1 60 var makecolor = function(a) {
-1 61 return '#'
-1 62 + round(a[0]).toString(16)
-1 63 + round(a[1]).toString(16)
-1 64 + round(a[2]).toString(16);
-1 65 };
-1 66
-1 67 var sRGB = function(c) {
-1 68 var x = round(c) / 15;
-1 69 if (x < 0.04045) {
-1 70 return x / 12.92;
-1 71 } else {
-1 72 return Math.pow((x + 0.055) / 1.055, 2.4);
-1 73 }
-1 74 };
-1 75
-1 76 var makeContrast = function(a) {
-1 77 var l = 0.2126 * sRGB(a[0]) + 0.7152 * sRGB(a[1]) + 0.0722 * sRGB(a[2]);
-1 78 return l > 0.18 ? '#000' : '#fff';
-1 79 }
-1 80
-1 81 var analyze = function(img) {
-1 82 var c, i, j;
-1 83 var colors = ['white'];
-1 84 var contrasts = ['black'];
-1 85 var out = [];
-1 86 for (i = 0; i < img.data.length; i += 4) {
-1 87 c = makecolor(img.data.slice(i, i + 3));
-1 88 j = colors.indexOf(c);
-1 89 if (j === -1) {
-1 90 j = colors.length;
-1 91 colors.push(c);
-1 92 contrasts.push(makeContrast(img.data.slice(i, i + 3)));
-1 93 }
-1 94 out.push(j);
-1 95 }
-1 96 return {
-1 97 width: img.width,
-1 98 height: img.height,
-1 99 colors: colors,
-1 100 contrasts: contrasts,
-1 101 data: out,
-1 102 };
-1 103 };
-1 104
-1 105 var setPixel = function(x, y, color) {
-1 106 var i = y * data.width + x;
-1 107 octx.fillStyle = data.colors[color];
-1 108 octx.fillRect(x * 10, y * 10, 10, 10);
-1 109 if (color !== data.data[i]) {
-1 110 octx.fillStyle = data.contrasts[color];
-1 111 octx.fillText(data.data[i], x * 10 + 5, y * 10 + 5);
-1 112 }
-1 113 };
-1 114
-1 115 input.addEventListener('change', () => {
-1 116 file2img(input.files[0]).then(img => {
-1 117 // FIXME: configurable size
-1 118 data = analyze(img2data(img, 100 / img.width));
-1 119 offcanvas.width = data.width * 10;
-1 120 offcanvas.height = data.height * 10;
-1 121 octx.textAlign = 'center';
-1 122 octx.textBaseline = 'middle';
-1 123
-1 124 zoom = canvas.height / offcanvas.height * 0.8;
-1 125 dx = (canvas.width - offcanvas.width * zoom) / 2;
-1 126 dy = (canvas.height - offcanvas.height * zoom) / 2;
-1 127
-1 128 // FIXME: zoom to fit and center
-1 129
-1 130 var x, y;
-1 131 for (y = 0; y < data.height; y++) {
-1 132 for (x = 0; x < data.width; x++) {
-1 133 setPixel(x, y, 0);
-1 134 }
-1 135 }
-1 136
-1 137 render();
-1 138 });
-1 139 });
-1 140
-1 141 var resizeCanvas = function() {
-1 142 var rect = canvas.getBoundingClientRect();
-1 143
-1 144 dx += (rect.width - canvas.width) / 2;
-1 145 dy += (rect.height - canvas.height) / 2;
-1 146
-1 147 canvas.width = rect.width;
-1 148 canvas.height = rect.height;
-1 149
-1 150 render();
-1 151 };
-1 152
-1 153 var render = onAnimation(function() {
-1 154 ctx.clearRect(0, 0, canvas.width, canvas.height);
-1 155 ctx.drawImage(offcanvas, dx, dy, offcanvas.width * zoom, offcanvas.height * zoom);
-1 156 });
-1 157
-1 158 window.addEventListener('resize', resizeCanvas);
-1 159 resizeCanvas();
-1 160
-1 161 window.addEventListener('wheel', event => {
-1 162 var rect = canvas.getBoundingClientRect();
-1 163 var cx = event.clientX - rect.x;
-1 164 var cy = event.clientY - rect.y;
-1 165
-1 166 var ocx = (cx - dx) / zoom;
-1 167 var ocy = (cy - dy) / zoom;
-1 168
-1 169 zoom *= Math.pow(2, -event.deltaY / 100 / 100);
-1 170
-1 171 dx = cx - ocx * zoom;
-1 172 dy = cy - ocy * zoom;
-1 173
-1 174 render();
-1 175 });
-1 176
-1 177 window.addEventListener('keydown', event => {
-1 178 // FIXME: kinetic movement;
-1 179 var step = 10;
-1 180 if (event.key === 'w') {
-1 181 dy += step;
-1 182 } else if (event.key === 'a') {
-1 183 dx += step;
-1 184 } else if (event.key === 's') {
-1 185 dy -= step;
-1 186 } else if (event.key === 'd') {
-1 187 dx -= step;
-1 188 } else if (event.key === 'q') {
-1 189 pencil -= 1;
-1 190 } else if (event.key === 'e') {
-1 191 pencil += 1;
-1 192 }
-1 193 render();
-1 194 });
-1 195
-1 196 var onClick = function(event) {
-1 197 if (event.buttons & 1) {
-1 198 var rect = canvas.getBoundingClientRect();
-1 199 var cx = event.clientX - rect.x;
-1 200 var cy = event.clientY - rect.y;
-1 201
-1 202 var ocx = (cx - dx) / zoom;
-1 203 var ocy = (cy - dy) / zoom;
-1 204
-1 205 var x = Math.floor(ocx / 10);
-1 206 var y = Math.floor(ocy / 10);
-1 207
-1 208 if (x >= 0 && x < data.width && y >= 0 && y < data.height) {
-1 209 setPixel(x, y, pencil);
-1 210 render();
-1 211 }
-1 212 }
-1 213 };
-1 214
-1 215 window.addEventListener('mousemove', onClick);
-1 216 window.addEventListener('mousedown', onClick);
diff --git a/style.css b/style.css
@@ -0,0 +1,16 @@
-1 1 html,
-1 2 body {
-1 3 margin: 0;
-1 4 padding: 0;
-1 5 }
-1 6
-1 7 body {
-1 8 display: grid;
-1 9 grid-template-rows: min-content 1fr;
-1 10 height: 100vh;
-1 11 }
-1 12
-1 13 canvas {
-1 14 width: 100%;
-1 15 height: 100%;
-1 16 }