1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 |
/**
* Dithering - error diffusion applied ONCE against the exact target palette,
* before rendering. This keeps it coherent with the renderer (the previous
* version dithered and then re-quantized differently, wasting the effect).
*
* For truecolor the cache returns colors unchanged, so dithering is a no-op.
*/
import type { RGB, DitherMode } from '../types.js';
import { getPixel, setPixel, cloneImageData, type ImageData } from '../core/image-loader.js';
import { ColorCache } from '../core/color.js';
interface Weight { dx: number; dy: number; w: number }
const FLOYD_STEINBERG: Weight[] = [
{ dx: 1, dy: 0, w: 7 / 16 },
{ dx: -1, dy: 1, w: 3 / 16 },
{ dx: 0, dy: 1, w: 5 / 16 },
{ dx: 1, dy: 1, w: 1 / 16 },
];
const ATKINSON: Weight[] = [
{ dx: 1, dy: 0, w: 1 / 8 },
{ dx: 2, dy: 0, w: 1 / 8 },
{ dx: -1, dy: 1, w: 1 / 8 },
{ dx: 0, dy: 1, w: 1 / 8 },
{ dx: 1, dy: 1, w: 1 / 8 },
{ dx: 0, dy: 2, w: 1 / 8 },
];
/**
* Return a dithered copy of the image, quantized to the cache's palette.
* `mode === 'none'` (or truecolor cache) returns the image unchanged.
*/
export function applyDithering(
image: ImageData,
mode: DitherMode,
cache: ColorCache
): ImageData {
if (mode === 'none' || cache.isTruecolor) return image;
const matrix = mode === 'atkinson' ? ATKINSON : FLOYD_STEINBERG;
const out = cloneImageData(image);
for (let y = 0; y < out.height; y++) {
for (let x = 0; x < out.width; x++) {
const oldColor = getPixel(out, x, y);
const newColor = cache.quantize(oldColor).color;
setPixel(out, x, y, newColor);
const er = oldColor.r - newColor.r;
const eg = oldColor.g - newColor.g;
const eb = oldColor.b - newColor.b;
for (const { dx, dy, w } of matrix) {
const nx = x + dx;
const ny = y + dy;
if (nx < 0 || nx >= out.width || ny < 0 || ny >= out.height) continue;
const n = getPixel(out, nx, ny);
setPixel(out, nx, ny, {
r: n.r + er * w,
g: n.g + eg * w,
b: n.b + eb * w,
});
}
}
}
return out;
}
|