From 6af5d43943dfe4823fe3373938060659f09fe0ec Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Sun, 19 Mar 2023 16:33:12 -0700 Subject: [PATCH] wip --- dither-image.js | 65 +++++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/dither-image.js b/dither-image.js index fc523e1..0157f2b 100644 --- a/dither-image.js +++ b/dither-image.js @@ -386,9 +386,10 @@ let atariRGB = [ * @param {number[]} palette - current working palette, as Atari 8-bit color values (low nybble luminance, high nybble hue) * @param {number} n - target color count * @param {RGB[]} inputError + * @param {number} y * @returns {{output: Uint8Array, palette: number[], error: RGB[]}} */ -function decimate(input, palette, n, inputError) { +function decimate(input, palette, n, inputError, y) { // to brute-force, the possible palettes are: // 255 * 254 * 253 = 16,386,810 // @@ -408,7 +409,7 @@ function decimate(input, palette, n, inputError) { let inputPixel = (x, error) => { let rgb = input[x].clone(); if (error) { - rgb.add(error.cur[x]); + rgb.inc(error.cur[x]); } if (inputError) { rgb.inc(inputError[x]); @@ -499,39 +500,52 @@ function decimate(input, palette, n, inputError) { //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 + } else { + reserved = [0, 0x76, 0x9a, 15]; // blue/cyan/white + } + */ let keepers = new Uint8Array(256); for (let i of reserved) { keepers[i & 0xfe] = 1; // drop that 0 luminance bit! } + let zeros = (n) => { + let arr = []; + for (let i = 0; i < n; i++) { + arr.push(0); + } + return arr; + }; + + // first, dither to the total atari palette while (decimated.length > n) { - // Try dithering to every possible subset - // See which has the worst overall fit and drop it - let farthest = -1; - let worstIndex = -1; + let {popularity} = dither(decimated); + let hues = zeros(16); + let lumas = zeros(16); for (let i = 0; i < decimated.length; i++) { let color = decimated[i]; - if (keepers[color]) { - continue; - } - - let shorter = decimated.filter((x) => x != i); - let {distance2} = dither(shorter); - - if (distance2 >= farthest) { - farthest = distance2; - worstIndex = i; + let hue = color >> 4; + let luma = color & 0xe; + hues[hue] += popularity[i]; + if (luma > lumas[hue]) { + lumas[hue] = luma; } } - console.log(`worstIndex ${worstIndex} farthest ${farthest}`); - let foo = decimated.length; - decimated.splice(worstIndex, 1); - //console.log(decimated.length); - if (decimated.length == foo) { - throw new Error('this should not happen'); - } - //console.log('n ', decimated); + + let a = hues; + a = a.map((count, hue) => { return {count, hue} }); + 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.map((hue) => (hue << 4) | lumas[hue]); + a = a.sort((a, b) => a - b); + + decimated = reserved.concat(a); } console.log('end', decimated); @@ -626,10 +640,9 @@ async function convert(source, nbits) { let lines = []; for (let y = 0; y < height; y++) { - console.log(`y ${y}`); let error = lines[y - 1]?.error; let inputLine = input.slice(y * width, (y + 1) * width); - let line = decimate(inputLine, allColors, 4, error); + let line = decimate(inputLine, allColors, 4, error, y); lines.push(line); } return {