This commit is contained in:
Brooke Vibber 2023-03-20 20:36:12 -07:00
parent f437114be0
commit 2c943419ce

View file

@ -98,10 +98,6 @@ class RGB {
); );
} }
add(other) {
return RGB.add(this, other);
}
difference(other) { difference(other) {
return new RGB( return new RGB(
this.r - other.r, this.r - other.r,
@ -110,6 +106,14 @@ class RGB {
); );
} }
multiply(scalar) {
return new RGB(
this.r * scalar,
this.g * scalar,
this.b * scalar,
);
}
divide(scalar) { divide(scalar) {
return new RGB( return new RGB(
this.r / scalar, this.r / scalar,
@ -472,15 +476,12 @@ function decimate(input, palette, n, inputError, y) {
output[x] = pick; output[x] = pick;
popularity[pick]++; popularity[pick]++;
let shares = 8; let share = (n) => nextError.multiply(n / 16);
let single = nextError.divide(shares);
let double = nextError.divide(shares / 2);
error.cur[x + 1]?.inc(double);
error.cur[x + 2]?.inc(single);
error.next[x - 1]?.inc(single); error.cur[x + 1]?.inc(share(7));
error.next[x]?.inc(double); error.next[x - 1]?.inc(share(3));
error.next[x + 1]?.inc(double); error.next[x ]?.inc(share(5));
error.next[x + 1]?.inc(share(1));
let mag = nextError.magnitude(); let mag = nextError.magnitude();
fitness[x] = maxDist / mag; fitness[x] = maxDist / mag;
@ -533,129 +534,6 @@ function decimate(input, palette, n, inputError, y) {
keepers[i & 0xfe] = 1; // drop that 0 luminance bit! keepers[i & 0xfe] = 1; // drop that 0 luminance bit!
} }
// this takes the top hues, and uses the brightest of each hue
// needs tuning
/*
// first, dither to the total atari palette
while (decimated.length > n) {
let {popularity} = dither(decimated);
let hues = zeros(16);
let lumas = zeros(16);
for (let i = 0; i < decimated.length; i++) {
let color = decimated[i];
let hue = color >> 4;
let luma = color & 0xe;
hues[hue] += popularity[i];
if (luma > lumas[hue]) {
lumas[hue] = luma;
}
}
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.slice(0, decimated.length - 1 - 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);
*/
// popularity? not really working right
// first, dither to the total atari palette
/*
while (decimated.length > n) {
//console.log(y);
let {popularity, fitness, output} = dither(decimated);
let pops = [];
let fits = [];
pops.fill(0, 0, 256);
fits.fill(0, 0, 256);
for (let i = 0; i < decimated.length; i++) {
let c = decimated[i];
pops[c] = popularity[i];
for (let x = 0; x < fitness.length; x++) {
if (output[x] === i) {
fits[c] += fitness[x];
}
}
}
let metric = (c) => {
let rgb = atariRGB[c];
let max = Math.max(rgb.r, rgb.g, rgb.b);
let fit = fits[c];
let pop = pops[c];
return pop;
}
let a = decimated.slice();
// temporarily strip the reserved items
a = a.filter((color) => !keepers[color]);
a = a.filter((color) => popularity[color]);
a.sort((a, b) => {
return metric(b) - metric(a);
});
console.log(a);
a = reserved.concat(a);
decimated = a.slice(0, n);
//decimated = a.slice(0, decimated.length - 1);
//console.log(decimated);
}
*/
//console.log('end', decimated);
// old algo
/*
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;
});
}
}
*/
// Median cut! // Median cut!
// https://en.wikipedia.org/wiki/Median_cut // https://en.wikipedia.org/wiki/Median_cut
//let buckets = [input.slice()]; //let buckets = [input.slice()];
@ -694,13 +572,16 @@ function decimate(input, palette, n, inputError, y) {
} }
decimated = buckets.map((bucket) => { decimated = buckets.map((bucket) => {
// Average the RGB colors in this chunk // Average the RGB colors in this chunk
let rgb = bucket //let rgb = bucket
.reduce((acc, rgb) => acc.inc(rgb), new RGB(0, 0, 0)) // .reduce((acc, rgb) => acc.inc(rgb), new RGB(0, 0, 0))
.divide(bucket.length); // .divide(bucket.length);
// Take the brightest color in the bucket // Take the brightest color in the bucket
//let rgb = bucket[bucket.length - 1]; //let rgb = bucket[bucket.length - 1];
// Take the median color in the bucket
let rgb = bucket[bucket.length >> 1];
// And map into the Atari palette // And map into the Atari palette
let dists = palette.map(( i) => rgb.difference(atariRGB[i]).magnitude()); let dists = palette.map(( i) => rgb.difference(atariRGB[i]).magnitude());
let closest = Math.min(...dists); let closest = Math.min(...dists);