This commit is contained in:
Brooke Vibber 2023-03-19 16:33:12 -07:00
parent dfd35658ee
commit 6af5d43943

View file

@ -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[]} palette - current working palette, as Atari 8-bit color values (low nybble luminance, high nybble hue)
* @param {number} n - target color count * @param {number} n - target color count
* @param {RGB[]} inputError * @param {RGB[]} inputError
* @param {number} y
* @returns {{output: Uint8Array, palette: number[], error: RGB[]}} * @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: // to brute-force, the possible palettes are:
// 255 * 254 * 253 = 16,386,810 // 255 * 254 * 253 = 16,386,810
// //
@ -408,7 +409,7 @@ function decimate(input, palette, n, inputError) {
let inputPixel = (x, error) => { let inputPixel = (x, error) => {
let rgb = input[x].clone(); let rgb = input[x].clone();
if (error) { if (error) {
rgb.add(error.cur[x]); rgb.inc(error.cur[x]);
} }
if (inputError) { if (inputError) {
rgb.inc(inputError[x]); rgb.inc(inputError[x]);
@ -499,39 +500,52 @@ function decimate(input, palette, n, inputError) {
//reserved = [0, 5, 10, 15]; // grayscale //reserved = [0, 5, 10, 15]; // grayscale
//reserved = [0, 0x48, 0x78, 15]; // vaporwave //reserved = [0, 0x48, 0x78, 15]; // vaporwave
//reserved = [0, 0x3c, 0x78, 15]; // red/blue/white //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); let keepers = new Uint8Array(256);
for (let i of reserved) { for (let i of reserved) {
keepers[i & 0xfe] = 1; // drop that 0 luminance bit! 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) { while (decimated.length > n) {
// Try dithering to every possible subset let {popularity} = dither(decimated);
// See which has the worst overall fit and drop it let hues = zeros(16);
let farthest = -1; let lumas = zeros(16);
let worstIndex = -1;
for (let i = 0; i < decimated.length; i++) { for (let i = 0; i < decimated.length; i++) {
let color = decimated[i]; let color = decimated[i];
if (keepers[color]) { let hue = color >> 4;
continue; let luma = color & 0xe;
hues[hue] += popularity[i];
if (luma > lumas[hue]) {
lumas[hue] = luma;
}
} }
let shorter = decimated.filter((x) => x != i); let a = hues;
let {distance2} = dither(shorter); 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);
if (distance2 >= farthest) { decimated = reserved.concat(a);
farthest = distance2;
worstIndex = i;
}
}
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);
} }
console.log('end', decimated); console.log('end', decimated);
@ -626,10 +640,9 @@ async function convert(source, nbits) {
let lines = []; let lines = [];
for (let y = 0; y < height; y++) { for (let y = 0; y < height; y++) {
console.log(`y ${y}`);
let error = lines[y - 1]?.error; let error = lines[y - 1]?.error;
let inputLine = input.slice(y * width, (y + 1) * width); 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); lines.push(line);
} }
return { return {