paint-by-numbers

Relaxing paint-by-numbers game  https://p.ce9e.org/paint-by-numbers/
git clone https://git.ce9e.org/paint-by-numbers.git

commit
b5fe756e51e6458241fd0bac1351efc472041b15
parent
40b91c9ff42f398f53e5d1148fea0a9d6449eb73
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2023-02-13 09:04
more sophisticated color clustering

Diffstat

A js/color.js 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M js/loader.js 80 +++++++++++++++++++++++++++++++++++++------------------------

2 files changed, 111 insertions, 31 deletions


diff --git a/js/color.js b/js/color.js

@@ -0,0 +1,62 @@
   -1     1 var srgbToRgb = function(c) {
   -1     2     var x = c / 255;
   -1     3     if (x < 0.04045) {
   -1     4         return x / 12.92;
   -1     5     } else {
   -1     6         return Math.pow((x + 0.055) / 1.055, 2.4);
   -1     7     }
   -1     8 };
   -1     9 
   -1    10 var rgbToSrgb = function(c) {
   -1    11     var x;
   -1    12     if (c < 0.04045 / 12.92) {
   -1    13         x = c * 12.92;
   -1    14     } else {
   -1    15         x = Math.pow(c, 1 / 2.4) * 1.055 - 0.055;
   -1    16     }
   -1    17     return x * 255;
   -1    18 };
   -1    19 
   -1    20 export var rgbToLab = function(srgb) {
   -1    21     var [r, g, b] = srgb.map(srgbToRgb);
   -1    22 
   -1    23     var l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
   -1    24     var m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
   -1    25     var s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
   -1    26 
   -1    27     l = Math.cbrt(l);
   -1    28     m = Math.cbrt(m);
   -1    29     s = Math.cbrt(s);
   -1    30 
   -1    31     return [
   -1    32         0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,
   -1    33         1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,
   -1    34         0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s,
   -1    35     ];
   -1    36 };
   -1    37 
   -1    38 export var labToRgb = function(lab) {
   -1    39     var l = lab[0] + 0.3963377774 * lab[1] + 0.2158037573 * lab[2];
   -1    40     var m = lab[0] - 0.1055613458 * lab[1] - 0.0638541728 * lab[2];
   -1    41     var s = lab[0] - 0.0894841775 * lab[1] - 1.2914855480 * lab[2];
   -1    42 
   -1    43     l = l * l * l;
   -1    44     m = m * m * m;
   -1    45     s = s * s * s;
   -1    46 
   -1    47     var rgb = [
   -1    48         +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
   -1    49         -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
   -1    50         -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s,
   -1    51     ];
   -1    52 
   -1    53     return rgb.map(rgbToSrgb);
   -1    54 };
   -1    55 
   -1    56 export var hex = function(rgb) {
   -1    57     return '#'
   -1    58         + Math.floor(rgb[0] / 16).toString(16)
   -1    59         + Math.floor(rgb[1] / 16).toString(16)
   -1    60         + Math.floor(rgb[2] / 16).toString(16);
   -1    61 };
   -1    62 

diff --git a/js/loader.js b/js/loader.js

@@ -1,3 +1,5 @@
   -1     1 import { rgbToLab, labToRgb, hex } from './color.js';
   -1     2 
    1     3 var file2img = function(file) {
    2     4     // FIXME: uses unsafe inline image
    3     5     return new Promise((resolve, reject) => {
@@ -26,46 +28,62 @@ var img2data = function(img, width) {
   26    28     return _ctx.getImageData(0, 0, _canvas.width, _canvas.height);
   27    29 };
   28    30 
   29    -1 var round = function(c) {
   30    -1     return Math.floor(c / 51) * 3;
   31    -1 };
   32    31 
   33    -1 var makecolor = function(a) {
   34    -1     return '#'
   35    -1         + round(a[0]).toString(16)
   36    -1         + round(a[1]).toString(16)
   37    -1         + round(a[2]).toString(16);
   38    -1 };
   -1    32 class Cluster {
   -1    33     constructor(center) {
   -1    34         this.center = center;
   -1    35         this.count = 1;
   -1    36     }
   39    37 
   40    -1 var sRGB = function(c) {
   41    -1     var x = round(c) / 15;
   42    -1     if (x < 0.04045) {
   43    -1         return x / 12.92;
   44    -1     } else {
   45    -1         return Math.pow((x + 0.055) / 1.055, 2.4);
   -1    38     distance(color) {
   -1    39         return Math.sqrt(
   -1    40             Math.pow(this.center[0] - color[0], 2)
   -1    41             + Math.pow(this.center[1] - color[1], 2)
   -1    42             + Math.pow(this.center[2] - color[2], 2)
   -1    43         );
   46    44     }
   47    -1 };
   48    45 
   49    -1 var makeContrast = function(a) {
   50    -1     var l = 0.2126 * sRGB(a[0]) + 0.7152 * sRGB(a[1]) + 0.0722 * sRGB(a[2]);
   51    -1     return l > 0.18 ? '#000' : '#fff';
   52    -1 };
   -1    46     add(color) {
   -1    47         this.center = [
   -1    48             (this.center[0] * this.count + color[0]) / (this.count + 1),
   -1    49             (this.center[1] * this.count + color[1]) / (this.count + 1),
   -1    50             (this.center[2] * this.count + color[2]) / (this.count + 1),
   -1    51         ];
   -1    52         this.count += 1;
   -1    53     }
   -1    54 }
   53    55 
   54    56 var analyze = function(img) {
   55    -1     var c, i, j;
   56    -1     var colors = ['white'];
   57    -1     var contrasts = ['black'];
   -1    57     var j;
   -1    58     var clusters = [];
   58    59     var out = [];
   59    -1     for (i = 0; i < img.data.length; i += 4) {
   60    -1         c = makecolor(img.data.slice(i, i + 3));
   61    -1         j = colors.indexOf(c);
   62    -1         if (j === -1) {
   63    -1             j = colors.length;
   64    -1             colors.push(c);
   65    -1             contrasts.push(makeContrast(img.data.slice(i, i + 3)));
   -1    60     for (var i = 0; i < img.data.length; i += 4) {
   -1    61         var lab = rgbToLab([
   -1    62             img.data[i],
   -1    63             img.data[i + 1],
   -1    64             img.data[i + 2],
   -1    65         ]);
   -1    66 
   -1    67         for (j = 0; j < clusters.length; j++) {
   -1    68             if (clusters[j].distance(lab) < 0.05) {
   -1    69                 clusters[j].add(lab);
   -1    70                 out.push(j + 1);
   -1    71                 break;
   -1    72             }
   66    73         }
   67    -1         out.push(j);
   -1    74         if (j === clusters.length) {
   -1    75             clusters.push(new Cluster(lab));
   -1    76             out.push(j + 1);
   -1    77         }
   -1    78     }
   -1    79 
   -1    80     var colors = ['white'];
   -1    81     var contrasts = ['black'];
   -1    82     for (j = 0; j < clusters.length; j++) {
   -1    83         colors.push(hex(labToRgb(clusters[j].center)));
   -1    84         contrasts.push(clusters[j].center[0] < 0.5 ? 'white' : 'black');
   68    85     }
   -1    86 
   69    87     return {
   70    88         width: img.width,
   71    89         height: img.height,