woo
This commit is contained in:
parent
f437114be0
commit
2c943419ce
1 changed files with 19 additions and 138 deletions
157
dither-image.js
157
dither-image.js
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue