From 0dcfa88c2991c25cb6d3eec315f071a6739a95bc Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 22 Mar 2023 23:24:11 -0700 Subject: [PATCH] wip --- dither-image.js | 85 +++++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/dither-image.js b/dither-image.js index 7ecd29d..f804df8 100644 --- a/dither-image.js +++ b/dither-image.js @@ -28,6 +28,17 @@ function fromLinear(val) { return unit * 255; } +function toSRGB(val) { + val /= 255; + if (val <= 0.0031308) { + val *= 12.92; + } else { + val = (val * 1.055) ** (1.0 / 2.4) - 0.055; + } + val *= 255; + return val; +} + class RGB { constructor(r, g, b) { this.r = r; @@ -46,41 +57,32 @@ class RGB { return new RGB(r,g,b); } - toLinear() { + map(callback) { return new RGB( - toLinear(this.r), - toLinear(this.g), - toLinear(this.b) + callback(this.r), + callback(this.g), + callback(this.b) ); } + toLinear() { + return this.map(toLinear); + } + fromLinear() { - return new RGB( - fromLinear(this.r), - fromLinear(this.g), - fromLinear(this.b) - ); + return this.map(fromLinear); } - cap() { - if (this.r < 0) { - this.r = 0; - } - if (this.g < 0) { - this.g = 0; - } - if (this.b < 0) { - this.b = 0; - } - if (this.r > 255) { - this.r = 255; - } - if (this.g > 255) { - this.g = 255; - } - if (this.b > 255) { - this.b = 255; - } + toSRGB() { + return this.map(toSRGB); + } + + clamp() { + return this.map((val) => { + if (val < 0) return 0; + if (val > 255) return 255; + return val; + }); } inc(other) { @@ -90,11 +92,11 @@ class RGB { return this; } - static add(a, b) { + add(other) { return new RGB( - a.r + b.r, - a.g + b.g, - a.b + b.b + this.r + other.r, + this.g + other.g, + this.b + other.b ); } @@ -399,6 +401,7 @@ let atariRGB = [ 0xfffa84, 0xffff99, ].map((hex) => RGB.fromHex(hex).toLinear()); +//].map((hex) => RGB.fromHex(hex)); /** * Dither RGB input data with a target palette size. @@ -430,10 +433,9 @@ function decimate(input, palette, n) { let inputPixel = (x, error) => { let rgb = input[x].clone(); if (error) { - rgb.inc(error.cur[x]); + rgb = rgb.add(error.cur[x]); } - rgb.cap(); - return rgb; + return rgb.clamp(); }; // Apply dithering with given palette and collect color usage stats @@ -585,7 +587,7 @@ function decimate(input, palette, n) { let lumas = bucket.map((rgb) => rgb.luma()); let brightest = Math.max(...lumas); if (avg_luma > 0) { - rgb = rgb.multiply(brightest / avg_luma); + rgb = rgb.multiply(brightest / avg_luma).clamp(); } @@ -740,7 +742,7 @@ async function convert(source) { .slice(y * width, (y + 1) * width); if (y > 0) { let error = lines[y - 1].error; - inputLine = inputLine.map((rgb, x) => RGB.add(rgb, error[x])); + inputLine = inputLine.map((rgb, x) => rgb.add(error[x])); } let line = decimate(inputLine, allColors, 4, y); lines.push(line); @@ -897,11 +899,12 @@ async function saveImage(width, height, lines, dest) { if (i >= width) { throw new Error('i >= width'); } - let rgb = atariRGB[palette[output[i]]]; + //let rgb = atariRGB[palette[output[i]]].fromLinear(); + let rgb = atariRGB[palette[output[i]]].toSRGB(); - rgba[y * stride + x * 4 + 0] = fromLinear(rgb.r); - rgba[y * stride + x * 4 + 1] = fromLinear(rgb.g); - rgba[y * stride + x * 4 + 2] = fromLinear(rgb.b); + rgba[y * stride + x * 4 + 0] = rgb.r; + rgba[y * stride + x * 4 + 1] = rgb.g; + rgba[y * stride + x * 4 + 2] = rgb.b; rgba[y * stride + x * 4 + 3] = 255; } }