diff --git a/dither-image.js b/dither-image.js index b0c81d1..fc523e1 100644 --- a/dither-image.js +++ b/dither-image.js @@ -110,16 +110,10 @@ class RGB { ); } - magnitude() { - return Math.sqrt( - this.r * this.r + + magnitude2() { + return this.r * this.r + this.g * this.g + - this.b * this.b - ); - } - - distance(other) { - return this.difference(other).magnitude(); + this.b * this.b; } } @@ -437,6 +431,7 @@ function decimate(input, palette, n, inputError) { let output = new Uint8Array(width); let popularity = new Int32Array(width); + let distance2 = 0; let nextError = new RGB(0, 0, 0); @@ -451,7 +446,7 @@ function decimate(input, palette, n, inputError) { for (let i = 0; i < palette.length; i++) { let diff = rgb.difference(atariRGB[palette[i]]); - let dist = diff.magnitude(); + let dist = diff.magnitude2(); if (dist < shortest) { nextError = diff; shortest = dist; @@ -472,15 +467,16 @@ function decimate(input, palette, n, inputError) { error.next[x]?.inc(double); error.next[x + 1]?.inc(double); - // 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 { output, palette, fitness, + distance2, popularity, error: error.next }; @@ -510,43 +506,34 @@ function decimate(input, palette, n, inputError) { } 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++) { + // Try dithering to every possible subset + // See which has the worst overall fit and drop it + let farthest = -1; + let worstIndex = -1; + for (let i = 0; i < decimated.length; i++) { let color = decimated[i]; if (keepers[color]) { continue; } - let coolFactor = fitness[i] * popularity[i]; - //let coolFactor = popularity[i]; - //let coolFactor = (fitness[i] ** 2) * popularity[i]; + let shorter = decimated.filter((x) => x != i); + let {distance2} = dither(shorter); - if (coolFactor < least) { - pick = i; - least = coolFactor; + if (distance2 >= farthest) { + farthest = distance2; + worstIndex = i; } } - - let last = decimated.length; - decimated = decimated.filter((color, i) => { - if (keepers[color]) { - return true; - } - if (i == pick) { - return false; - } - return true; - }); - - if (decimated.length == last && last > n) { - console.log(decimated); + 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); // Palette fits return dither(decimated); @@ -639,6 +626,7 @@ 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);