From 9826a90e02ebc5e53aea00af1e91e2b91004d1f5 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Sun, 19 Mar 2023 18:22:33 -0700 Subject: [PATCH] some oldbits --- dither-image.js | 71 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/dither-image.js b/dither-image.js index 3b0aec4..5b2756b 100644 --- a/dither-image.js +++ b/dither-image.js @@ -426,7 +426,7 @@ function decimate(input, palette, n, inputError, y) { // Apply dithering with given palette and collect color usage stats let dither = (palette) => { - let fitness = new Float64Array(palette.length); + let fitness = new Float64Array(width); let error = { cur: [], next: [], @@ -476,10 +476,12 @@ function decimate(input, palette, n, inputError, y) { let mag = nextError.magnitude(); fitness[x] = maxDist / mag; + // 442 is the 3d distance across the rgb cube + //fitness[x] = 442 - (nextError.magnitude()); + //fitness[x] = 442 / (442 - nextError.magnitude()); + fitness[x] = 255 / (256 - Math.max(0, nextError.r, nextError.g, nextError.b)); - // just store the distance2 let mag2 = nextError.magnitude2(); - //fitness[x] = mag2; distance2 += mag2; } return { @@ -509,11 +511,12 @@ function decimate(input, palette, n, inputError, y) { //reserved = [0, 5, 10, 15]; // grayscale //reserved = [0, 0x48, 0x78, 15]; // vaporwave //reserved = [0, 0x3c, 0x78, 15]; // red/blue/white + /* if (( y & 1 ) === 0) { - reserved = [0, 0x3c, 0x1a, 15]; // red/yellow/white + reserved = [0, 0x3c, 0x1e, 15]; // red/yellow/white } else { - reserved = [0, 0x76, 0x9a, 15]; // blue/cyan/white + reserved = [0, 0x76, 0x9e, 0xb8]; // blue/cyan/green } */ @@ -553,7 +556,8 @@ function decimate(input, palette, n, inputError, y) { a = a.sort((a, b) => b.count - a.count); a = a.map(({hue}) => hue); a = a.filter((color) => !keepers[color]); - a = a.slice(0, n - reserved.length); + //a = a.slice(0, n - reserved.length); + a = a.slice(0, decimated.length - 1 - reserved.length); a = a.map((hue) => (hue << 4) | lumas[hue]); a = a.sort((a, b) => a - b); @@ -564,20 +568,64 @@ function decimate(input, palette, n, inputError, y) { // popularity? not really working right // first, dither to the total atari palette + /* while (decimated.length > n) { - let {popularity, fitness} = dither(decimated); - // temporarily strip the reserved items //console.log(y); + let {popularity} = dither(decimated); let a = decimated; + // temporarily strip the reserved items a = a.filter((color) => !keepers[color]); a = a.sort((a, b) => popularity[b] - popularity[a]); - //a = a.slice(0, n - reserved.length); a = reserved.concat(a); - decimated = a.slice(0, decimated.length - 1); + decimated = a.slice(0, n); //console.log(decimated); } //console.log('end', decimated); - + */ + + while (decimated.length > n) { + let {popularity, fitness, output} = dither(decimated); + + // Try dropping least used color on each iteration + let least = Infinity; + let pick = -1; + for (let i = 1; i < decimated.length; i++) { + + //let coolFactor = popularity[i]; + + let coolFactor = 0; + if (popularity[i]) { + for (let x = 0; x < width; x++) { + if (output[x] == i) { + // Scale up the scoring for close matches to prioritize + // color accuracy over raw linear usage. + //coolFactor += (fitness[x] ** 2); + coolFactor += (fitness[x] ** 4); + } + } + } + if (coolFactor < least) { + pick = i; + least = coolFactor; + } + decimated = decimated.filter((color, i) => { + if (i == 0) { + return true; + } + if (i == pick) { + return false; + } + // Also drop any non-black unused colors to save trouble. + // However -- this may change dither results. + // Try this with/without later. + if (popularity[i] == 0) { + return false; + } + return true; + }); + } + } + // Palette fits return dither(decimated); } @@ -845,6 +893,7 @@ async function saveImage(width, height, lines, dest) { resolve(image); }); }); + await image.resize(Math.round(width2 * 2 / 1.2), height * 2); await image.writeAsync(dest); }