@opendisplay/epaper-dithering 2.1.3 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -126
- package/dist/index.cjs +408 -130
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -16
- package/dist/index.d.ts +24 -16
- package/dist/index.js +402 -131
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -14,6 +14,159 @@ var DitherMode = /* @__PURE__ */ ((DitherMode2) => {
|
|
|
14
14
|
return DitherMode2;
|
|
15
15
|
})(DitherMode || {});
|
|
16
16
|
|
|
17
|
+
// src/color_space.ts
|
|
18
|
+
var SRGB_TO_LINEAR_LUT = (() => {
|
|
19
|
+
const lut = new Float64Array(256);
|
|
20
|
+
for (let i = 0; i < 256; i++) {
|
|
21
|
+
const s = i / 255;
|
|
22
|
+
lut[i] = s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
23
|
+
}
|
|
24
|
+
return lut;
|
|
25
|
+
})();
|
|
26
|
+
|
|
27
|
+
// src/color_space_lab.ts
|
|
28
|
+
var M00 = 0.4124564;
|
|
29
|
+
var M01 = 0.3575761;
|
|
30
|
+
var M02 = 0.1804375;
|
|
31
|
+
var M10 = 0.2126729;
|
|
32
|
+
var M11 = 0.7151522;
|
|
33
|
+
var M12 = 0.072175;
|
|
34
|
+
var M20 = 0.0193339;
|
|
35
|
+
var M21 = 0.119192;
|
|
36
|
+
var M22 = 0.9503041;
|
|
37
|
+
var XN = 0.95047;
|
|
38
|
+
var YN = 1;
|
|
39
|
+
var ZN = 1.08883;
|
|
40
|
+
var EPSILON = 216 / 24389;
|
|
41
|
+
var KAPPA = 24389 / 27;
|
|
42
|
+
var WL2 = 0.25;
|
|
43
|
+
var WC2 = 1;
|
|
44
|
+
var WH2 = 4;
|
|
45
|
+
function labF(t) {
|
|
46
|
+
return t > EPSILON ? Math.cbrt(t) : (KAPPA * t + 16) / 116;
|
|
47
|
+
}
|
|
48
|
+
function rgbToLabScalar(r, g, b) {
|
|
49
|
+
const x = M00 * r + M01 * g + M02 * b;
|
|
50
|
+
const y = M10 * r + M11 * g + M12 * b;
|
|
51
|
+
const z = M20 * r + M21 * g + M22 * b;
|
|
52
|
+
const fx = labF(x / XN);
|
|
53
|
+
const fy = labF(y / YN);
|
|
54
|
+
const fz = labF(z / ZN);
|
|
55
|
+
return [116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz)];
|
|
56
|
+
}
|
|
57
|
+
function precomputePaletteLab(paletteLinear) {
|
|
58
|
+
const n = paletteLinear.length;
|
|
59
|
+
const L = new Float64Array(n);
|
|
60
|
+
const a = new Float64Array(n);
|
|
61
|
+
const b = new Float64Array(n);
|
|
62
|
+
const C = new Float64Array(n);
|
|
63
|
+
for (let i = 0; i < n; i++) {
|
|
64
|
+
const [pl, pa, pb] = rgbToLabScalar(paletteLinear[i][0], paletteLinear[i][1], paletteLinear[i][2]);
|
|
65
|
+
L[i] = pl;
|
|
66
|
+
a[i] = pa;
|
|
67
|
+
b[i] = pb;
|
|
68
|
+
C[i] = Math.sqrt(pa * pa + pb * pb);
|
|
69
|
+
}
|
|
70
|
+
return { L, a, b, C };
|
|
71
|
+
}
|
|
72
|
+
function matchPixelLch(r, g, b, paletteL, paletteA, paletteB, paletteC) {
|
|
73
|
+
const [pL, pa, pb] = rgbToLabScalar(r, g, b);
|
|
74
|
+
const pC = Math.sqrt(pa * pa + pb * pb);
|
|
75
|
+
let bestIdx = 0;
|
|
76
|
+
let bestDist = Infinity;
|
|
77
|
+
const n = paletteL.length;
|
|
78
|
+
for (let i = 0; i < n; i++) {
|
|
79
|
+
const dL = pL - paletteL[i];
|
|
80
|
+
const da = pa - paletteA[i];
|
|
81
|
+
const db = pb - paletteB[i];
|
|
82
|
+
const dC = pC - paletteC[i];
|
|
83
|
+
let dHsq = da * da + db * db - dC * dC;
|
|
84
|
+
if (dHsq < 0) dHsq = 0;
|
|
85
|
+
const dist = WL2 * dL * dL + WC2 * dC * dC + WH2 * dHsq;
|
|
86
|
+
if (dist < bestDist) {
|
|
87
|
+
bestDist = dist;
|
|
88
|
+
bestIdx = i;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return bestIdx;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/tone_map.ts
|
|
95
|
+
var LUM_R = 0.2126729;
|
|
96
|
+
var LUM_G = 0.7151522;
|
|
97
|
+
var LUM_B = 0.072175;
|
|
98
|
+
function compressDynamicRange(pixels, width, height, paletteLinear, strength = 1) {
|
|
99
|
+
if (strength <= 0) return;
|
|
100
|
+
const [br, bg, bb] = paletteLinear[0];
|
|
101
|
+
const [wr, wg, wb] = paletteLinear[1];
|
|
102
|
+
const blackY = LUM_R * br + LUM_G * bg + LUM_B * bb;
|
|
103
|
+
const whiteY = LUM_R * wr + LUM_G * wg + LUM_B * wb;
|
|
104
|
+
const displayRange = whiteY - blackY;
|
|
105
|
+
if (displayRange <= 0) return;
|
|
106
|
+
const n = width * height;
|
|
107
|
+
const blackLevel = blackY * strength;
|
|
108
|
+
for (let i = 0; i < n; i++) {
|
|
109
|
+
const base = i * 3;
|
|
110
|
+
const r = pixels[base];
|
|
111
|
+
const g = pixels[base + 1];
|
|
112
|
+
const b = pixels[base + 2];
|
|
113
|
+
const Y = LUM_R * r + LUM_G * g + LUM_B * b;
|
|
114
|
+
if (Y > 1e-6) {
|
|
115
|
+
const compressedY = blackY + Y * displayRange;
|
|
116
|
+
const targetY = strength < 1 ? Y + strength * (compressedY - Y) : compressedY;
|
|
117
|
+
const scale = targetY / Y;
|
|
118
|
+
pixels[base] = Math.max(0, Math.min(1, r * scale));
|
|
119
|
+
pixels[base + 1] = Math.max(0, Math.min(1, g * scale));
|
|
120
|
+
pixels[base + 2] = Math.max(0, Math.min(1, b * scale));
|
|
121
|
+
} else {
|
|
122
|
+
pixels[base] = blackLevel;
|
|
123
|
+
pixels[base + 1] = blackLevel;
|
|
124
|
+
pixels[base + 2] = blackLevel;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function autoCompressDynamicRange(pixels, width, height, paletteLinear) {
|
|
129
|
+
const [br, bg, bb] = paletteLinear[0];
|
|
130
|
+
const [wr, wg, wb] = paletteLinear[1];
|
|
131
|
+
const blackY = LUM_R * br + LUM_G * bg + LUM_B * bb;
|
|
132
|
+
const whiteY = LUM_R * wr + LUM_G * wg + LUM_B * wb;
|
|
133
|
+
const displayRange = whiteY - blackY;
|
|
134
|
+
if (displayRange <= 0) return;
|
|
135
|
+
const n = width * height;
|
|
136
|
+
const luminances = new Float32Array(n);
|
|
137
|
+
for (let i = 0; i < n; i++) {
|
|
138
|
+
const base = i * 3;
|
|
139
|
+
luminances[i] = LUM_R * pixels[base] + LUM_G * pixels[base + 1] + LUM_B * pixels[base + 2];
|
|
140
|
+
}
|
|
141
|
+
luminances.sort();
|
|
142
|
+
const pLow = luminances[Math.floor(n * 0.02)];
|
|
143
|
+
const pHigh = luminances[Math.floor(n * 0.98)];
|
|
144
|
+
const imageRange = pHigh - pLow;
|
|
145
|
+
if (imageRange < 1e-6) {
|
|
146
|
+
compressDynamicRange(pixels, width, height, paletteLinear, 1);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
for (let i = 0; i < n; i++) {
|
|
150
|
+
const base = i * 3;
|
|
151
|
+
const r = pixels[base];
|
|
152
|
+
const g = pixels[base + 1];
|
|
153
|
+
const b = pixels[base + 2];
|
|
154
|
+
const Y = LUM_R * r + LUM_G * g + LUM_B * b;
|
|
155
|
+
if (Y > 1e-6) {
|
|
156
|
+
const normalizedY = (Y - pLow) / imageRange;
|
|
157
|
+
const targetY = blackY + normalizedY * displayRange;
|
|
158
|
+
const scale = targetY / Y;
|
|
159
|
+
pixels[base] = Math.max(0, Math.min(1, r * scale));
|
|
160
|
+
pixels[base + 1] = Math.max(0, Math.min(1, g * scale));
|
|
161
|
+
pixels[base + 2] = Math.max(0, Math.min(1, b * scale));
|
|
162
|
+
} else {
|
|
163
|
+
pixels[base] = blackY;
|
|
164
|
+
pixels[base + 1] = blackY;
|
|
165
|
+
pixels[base + 2] = blackY;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
17
170
|
// src/palettes.ts
|
|
18
171
|
var ColorScheme = /* @__PURE__ */ ((ColorScheme3) => {
|
|
19
172
|
ColorScheme3[ColorScheme3["MONO"] = 0] = "MONO";
|
|
@@ -22,6 +175,8 @@ var ColorScheme = /* @__PURE__ */ ((ColorScheme3) => {
|
|
|
22
175
|
ColorScheme3[ColorScheme3["BWRY"] = 3] = "BWRY";
|
|
23
176
|
ColorScheme3[ColorScheme3["BWGBRY"] = 4] = "BWGBRY";
|
|
24
177
|
ColorScheme3[ColorScheme3["GRAYSCALE_4"] = 5] = "GRAYSCALE_4";
|
|
178
|
+
ColorScheme3[ColorScheme3["GRAYSCALE_8"] = 6] = "GRAYSCALE_8";
|
|
179
|
+
ColorScheme3[ColorScheme3["GRAYSCALE_16"] = 7] = "GRAYSCALE_16";
|
|
25
180
|
return ColorScheme3;
|
|
26
181
|
})(ColorScheme || {});
|
|
27
182
|
var PALETTES = {
|
|
@@ -52,8 +207,8 @@ var PALETTES = {
|
|
|
52
207
|
colors: {
|
|
53
208
|
black: { r: 0, g: 0, b: 0 },
|
|
54
209
|
white: { r: 255, g: 255, b: 255 },
|
|
55
|
-
|
|
56
|
-
|
|
210
|
+
yellow: { r: 255, g: 255, b: 0 },
|
|
211
|
+
red: { r: 255, g: 0, b: 0 }
|
|
57
212
|
},
|
|
58
213
|
accent: "red"
|
|
59
214
|
},
|
|
@@ -76,6 +231,40 @@ var PALETTES = {
|
|
|
76
231
|
white: { r: 255, g: 255, b: 255 }
|
|
77
232
|
},
|
|
78
233
|
accent: "black"
|
|
234
|
+
},
|
|
235
|
+
[6 /* GRAYSCALE_8 */]: {
|
|
236
|
+
colors: {
|
|
237
|
+
black: { r: 0, g: 0, b: 0 },
|
|
238
|
+
gray1: { r: 36, g: 36, b: 36 },
|
|
239
|
+
gray2: { r: 73, g: 73, b: 73 },
|
|
240
|
+
gray3: { r: 109, g: 109, b: 109 },
|
|
241
|
+
gray4: { r: 146, g: 146, b: 146 },
|
|
242
|
+
gray5: { r: 182, g: 182, b: 182 },
|
|
243
|
+
gray6: { r: 219, g: 219, b: 219 },
|
|
244
|
+
white: { r: 255, g: 255, b: 255 }
|
|
245
|
+
},
|
|
246
|
+
accent: "black"
|
|
247
|
+
},
|
|
248
|
+
[7 /* GRAYSCALE_16 */]: {
|
|
249
|
+
colors: {
|
|
250
|
+
black: { r: 0, g: 0, b: 0 },
|
|
251
|
+
gray1: { r: 17, g: 17, b: 17 },
|
|
252
|
+
gray2: { r: 34, g: 34, b: 34 },
|
|
253
|
+
gray3: { r: 51, g: 51, b: 51 },
|
|
254
|
+
gray4: { r: 68, g: 68, b: 68 },
|
|
255
|
+
gray5: { r: 85, g: 85, b: 85 },
|
|
256
|
+
gray6: { r: 102, g: 102, b: 102 },
|
|
257
|
+
gray7: { r: 119, g: 119, b: 119 },
|
|
258
|
+
gray8: { r: 136, g: 136, b: 136 },
|
|
259
|
+
gray9: { r: 153, g: 153, b: 153 },
|
|
260
|
+
gray10: { r: 170, g: 170, b: 170 },
|
|
261
|
+
gray11: { r: 187, g: 187, b: 187 },
|
|
262
|
+
gray12: { r: 204, g: 204, b: 204 },
|
|
263
|
+
gray13: { r: 221, g: 221, b: 221 },
|
|
264
|
+
gray14: { r: 238, g: 238, b: 238 },
|
|
265
|
+
white: { r: 255, g: 255, b: 255 }
|
|
266
|
+
},
|
|
267
|
+
accent: "black"
|
|
79
268
|
}
|
|
80
269
|
};
|
|
81
270
|
function getPalette(scheme) {
|
|
@@ -85,143 +274,230 @@ function getColorCount(scheme) {
|
|
|
85
274
|
return Object.keys(PALETTES[scheme].colors).length;
|
|
86
275
|
}
|
|
87
276
|
function fromValue(value) {
|
|
88
|
-
if (value < 0 || value >
|
|
277
|
+
if (value < 0 || value > 7) {
|
|
89
278
|
throw new Error(`Invalid color scheme value: ${value}`);
|
|
90
279
|
}
|
|
91
280
|
return value;
|
|
92
281
|
}
|
|
282
|
+
var SPECTRA_7_3_6COLOR = {
|
|
283
|
+
colors: {
|
|
284
|
+
black: { r: 26, g: 13, b: 35 },
|
|
285
|
+
white: { r: 185, g: 202, b: 205 },
|
|
286
|
+
yellow: { r: 202, g: 184, b: 0 },
|
|
287
|
+
red: { r: 121, g: 9, b: 0 },
|
|
288
|
+
blue: { r: 0, g: 69, b: 139 },
|
|
289
|
+
green: { r: 40, g: 82, b: 57 }
|
|
290
|
+
},
|
|
291
|
+
accent: "red"
|
|
292
|
+
};
|
|
293
|
+
var MONO_4_26 = {
|
|
294
|
+
colors: {
|
|
295
|
+
black: { r: 5, g: 5, b: 5 },
|
|
296
|
+
white: { r: 220, g: 220, b: 220 }
|
|
297
|
+
},
|
|
298
|
+
accent: "black"
|
|
299
|
+
};
|
|
300
|
+
var BWRY_4_2 = {
|
|
301
|
+
colors: {
|
|
302
|
+
black: { r: 5, g: 5, b: 5 },
|
|
303
|
+
white: { r: 200, g: 200, b: 200 },
|
|
304
|
+
yellow: { r: 200, g: 180, b: 0 },
|
|
305
|
+
red: { r: 120, g: 15, b: 5 }
|
|
306
|
+
},
|
|
307
|
+
accent: "red"
|
|
308
|
+
};
|
|
309
|
+
var SOLUM_BWR = {
|
|
310
|
+
colors: {
|
|
311
|
+
black: { r: 5, g: 5, b: 5 },
|
|
312
|
+
white: { r: 200, g: 200, b: 200 },
|
|
313
|
+
red: { r: 120, g: 15, b: 5 }
|
|
314
|
+
},
|
|
315
|
+
accent: "red"
|
|
316
|
+
};
|
|
317
|
+
var HANSHOW_BWR = {
|
|
318
|
+
colors: {
|
|
319
|
+
black: { r: 5, g: 5, b: 5 },
|
|
320
|
+
white: { r: 200, g: 200, b: 200 },
|
|
321
|
+
red: { r: 120, g: 15, b: 5 }
|
|
322
|
+
},
|
|
323
|
+
accent: "red"
|
|
324
|
+
};
|
|
325
|
+
var HANSHOW_BWY = {
|
|
326
|
+
colors: {
|
|
327
|
+
black: { r: 5, g: 5, b: 5 },
|
|
328
|
+
white: { r: 200, g: 200, b: 200 },
|
|
329
|
+
yellow: { r: 200, g: 180, b: 0 }
|
|
330
|
+
},
|
|
331
|
+
accent: "yellow"
|
|
332
|
+
};
|
|
333
|
+
var BWRY_3_97 = {
|
|
334
|
+
colors: {
|
|
335
|
+
black: { r: 10, g: 7, b: 14 },
|
|
336
|
+
white: { r: 173, g: 178, b: 174 },
|
|
337
|
+
yellow: { r: 172, g: 128, b: 0 },
|
|
338
|
+
red: { r: 85, g: 24, b: 14 }
|
|
339
|
+
},
|
|
340
|
+
accent: "red"
|
|
341
|
+
};
|
|
93
342
|
|
|
94
343
|
// src/algorithms.ts
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
344
|
+
var BAYER_4X4 = new Float32Array([
|
|
345
|
+
0,
|
|
346
|
+
8,
|
|
347
|
+
2,
|
|
348
|
+
10,
|
|
349
|
+
12,
|
|
350
|
+
4,
|
|
351
|
+
14,
|
|
352
|
+
6,
|
|
353
|
+
3,
|
|
354
|
+
11,
|
|
355
|
+
1,
|
|
356
|
+
9,
|
|
357
|
+
15,
|
|
358
|
+
7,
|
|
359
|
+
13,
|
|
360
|
+
5
|
|
361
|
+
].map((v) => v / 16 - 0.5));
|
|
362
|
+
function resolvePalette(scheme) {
|
|
363
|
+
return typeof scheme === "number" ? getPalette(scheme) : scheme;
|
|
98
364
|
}
|
|
99
|
-
function
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
365
|
+
function paletteToLinear(palette) {
|
|
366
|
+
const paletteRgb = Object.values(palette.colors);
|
|
367
|
+
const paletteLinear = paletteRgb.map((c) => [
|
|
368
|
+
SRGB_TO_LINEAR_LUT[c.r],
|
|
369
|
+
SRGB_TO_LINEAR_LUT[c.g],
|
|
370
|
+
SRGB_TO_LINEAR_LUT[c.b]
|
|
371
|
+
]);
|
|
372
|
+
return { paletteRgb, paletteLinear };
|
|
373
|
+
}
|
|
374
|
+
function buildLinearBuffer(image) {
|
|
375
|
+
const n = image.width * image.height;
|
|
376
|
+
const pixels = new Float32Array(n * 3);
|
|
377
|
+
const { data } = image;
|
|
378
|
+
for (let i = 0; i < n; i++) {
|
|
379
|
+
const base4 = i * 4;
|
|
380
|
+
const base3 = i * 3;
|
|
381
|
+
const alpha = data[base4 + 3] / 255;
|
|
382
|
+
const inv = 1 - alpha;
|
|
383
|
+
pixels[base3] = SRGB_TO_LINEAR_LUT[data[base4]] * alpha + inv;
|
|
384
|
+
pixels[base3 + 1] = SRGB_TO_LINEAR_LUT[data[base4 + 1]] * alpha + inv;
|
|
385
|
+
pixels[base3 + 2] = SRGB_TO_LINEAR_LUT[data[base4 + 2]] * alpha + inv;
|
|
109
386
|
}
|
|
110
|
-
return
|
|
387
|
+
return pixels;
|
|
111
388
|
}
|
|
112
|
-
function errorDiffusionDither(image,
|
|
389
|
+
function errorDiffusionDither(image, scheme, kernel, serpentine) {
|
|
113
390
|
const { width, height } = image;
|
|
114
|
-
const palette =
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
pixels
|
|
391
|
+
const palette = resolvePalette(scheme);
|
|
392
|
+
const { paletteRgb, paletteLinear } = paletteToLinear(palette);
|
|
393
|
+
const numColors = paletteRgb.length;
|
|
394
|
+
const pixels = buildLinearBuffer(image);
|
|
395
|
+
if (typeof scheme !== "number") {
|
|
396
|
+
autoCompressDynamicRange(pixels, width, height, paletteLinear);
|
|
397
|
+
}
|
|
398
|
+
const { L: palL, a: palA, b: palB, C: palC } = precomputePaletteLab(paletteLinear);
|
|
399
|
+
const palR = new Float64Array(numColors);
|
|
400
|
+
const palG = new Float64Array(numColors);
|
|
401
|
+
const palBl = new Float64Array(numColors);
|
|
402
|
+
for (let i = 0; i < numColors; i++) {
|
|
403
|
+
palR[i] = paletteLinear[i][0];
|
|
404
|
+
palG[i] = paletteLinear[i][1];
|
|
405
|
+
palBl[i] = paletteLinear[i][2];
|
|
120
406
|
}
|
|
121
407
|
const indices = new Uint8Array(width * height);
|
|
122
408
|
for (let y = 0; y < height; y++) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
indices[
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
for (
|
|
138
|
-
const
|
|
139
|
-
const
|
|
409
|
+
const leftToRight = !serpentine || y % 2 === 0;
|
|
410
|
+
const xStart = leftToRight ? 0 : width - 1;
|
|
411
|
+
const xEnd = leftToRight ? width : -1;
|
|
412
|
+
const xStep = leftToRight ? 1 : -1;
|
|
413
|
+
for (let x = xStart; x !== xEnd; x += xStep) {
|
|
414
|
+
const pixIdx = (y * width + x) * 3;
|
|
415
|
+
const r = Math.max(0, Math.min(1, pixels[pixIdx]));
|
|
416
|
+
const g = Math.max(0, Math.min(1, pixels[pixIdx + 1]));
|
|
417
|
+
const b = Math.max(0, Math.min(1, pixels[pixIdx + 2]));
|
|
418
|
+
const newIdx = matchPixelLch(r, g, b, palL, palA, palB, palC);
|
|
419
|
+
indices[y * width + x] = newIdx;
|
|
420
|
+
const errR = r - palR[newIdx];
|
|
421
|
+
const errG = g - palG[newIdx];
|
|
422
|
+
const errB = b - palBl[newIdx];
|
|
423
|
+
for (let k = 0; k < kernel.length; k++) {
|
|
424
|
+
const kEntry = kernel[k];
|
|
425
|
+
const nx = x + (leftToRight ? kEntry.dx : -kEntry.dx);
|
|
426
|
+
const ny = y + kEntry.dy;
|
|
140
427
|
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
|
|
141
|
-
const
|
|
142
|
-
pixels[
|
|
143
|
-
pixels[
|
|
144
|
-
pixels[
|
|
428
|
+
const nIdx = (ny * width + nx) * 3;
|
|
429
|
+
pixels[nIdx] += errR * kEntry.weight;
|
|
430
|
+
pixels[nIdx + 1] += errG * kEntry.weight;
|
|
431
|
+
pixels[nIdx + 2] += errB * kEntry.weight;
|
|
145
432
|
}
|
|
146
433
|
}
|
|
147
434
|
}
|
|
148
435
|
}
|
|
149
|
-
return { width, height, indices, palette };
|
|
436
|
+
return { width, height, indices, palette: paletteRgb };
|
|
150
437
|
}
|
|
151
|
-
function directPaletteMap(image,
|
|
438
|
+
function directPaletteMap(image, scheme) {
|
|
152
439
|
const { width, height } = image;
|
|
153
|
-
const palette =
|
|
440
|
+
const palette = resolvePalette(scheme);
|
|
441
|
+
const { paletteRgb, paletteLinear } = paletteToLinear(palette);
|
|
442
|
+
const pixels = buildLinearBuffer(image);
|
|
443
|
+
if (typeof scheme !== "number") {
|
|
444
|
+
autoCompressDynamicRange(pixels, width, height, paletteLinear);
|
|
445
|
+
}
|
|
446
|
+
const { L: palL, a: palA, b: palB, C: palC } = precomputePaletteLab(paletteLinear);
|
|
154
447
|
const indices = new Uint8Array(width * height);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
indices[i] =
|
|
448
|
+
const n = width * height;
|
|
449
|
+
for (let i = 0; i < n; i++) {
|
|
450
|
+
const base = i * 3;
|
|
451
|
+
const r = Math.max(0, Math.min(1, pixels[base]));
|
|
452
|
+
const g = Math.max(0, Math.min(1, pixels[base + 1]));
|
|
453
|
+
const b = Math.max(0, Math.min(1, pixels[base + 2]));
|
|
454
|
+
indices[i] = matchPixelLch(r, g, b, palL, palA, palB, palC);
|
|
162
455
|
}
|
|
163
|
-
return { width, height, indices, palette };
|
|
456
|
+
return { width, height, indices, palette: paletteRgb };
|
|
164
457
|
}
|
|
165
|
-
function orderedDither(image,
|
|
166
|
-
const bayerMatrix = new Uint8Array([
|
|
167
|
-
0,
|
|
168
|
-
8,
|
|
169
|
-
2,
|
|
170
|
-
10,
|
|
171
|
-
12,
|
|
172
|
-
4,
|
|
173
|
-
14,
|
|
174
|
-
6,
|
|
175
|
-
3,
|
|
176
|
-
11,
|
|
177
|
-
1,
|
|
178
|
-
9,
|
|
179
|
-
15,
|
|
180
|
-
7,
|
|
181
|
-
13,
|
|
182
|
-
5
|
|
183
|
-
]).map((v) => v * 16);
|
|
458
|
+
function orderedDither(image, scheme) {
|
|
184
459
|
const { width, height } = image;
|
|
185
|
-
const palette =
|
|
460
|
+
const palette = resolvePalette(scheme);
|
|
461
|
+
const { paletteRgb, paletteLinear } = paletteToLinear(palette);
|
|
462
|
+
const pixels = buildLinearBuffer(image);
|
|
463
|
+
if (typeof scheme !== "number") {
|
|
464
|
+
autoCompressDynamicRange(pixels, width, height, paletteLinear);
|
|
465
|
+
}
|
|
466
|
+
const { L: palL, a: palA, b: palB, C: palC } = precomputePaletteLab(paletteLinear);
|
|
186
467
|
const indices = new Uint8Array(width * height);
|
|
187
468
|
for (let y = 0; y < height; y++) {
|
|
188
469
|
for (let x = 0; x < width; x++) {
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
b: Math.max(0, Math.min(255, Math.trunc(image.data[dataIdx + 2] + threshold)))
|
|
196
|
-
};
|
|
197
|
-
indices[idx] = findClosestPaletteColor(rgb, palette);
|
|
470
|
+
const base = (y * width + x) * 3;
|
|
471
|
+
const threshold = BAYER_4X4[y % 4 * 4 + x % 4];
|
|
472
|
+
const r = Math.max(0, Math.min(1, pixels[base] + threshold));
|
|
473
|
+
const g = Math.max(0, Math.min(1, pixels[base + 1] + threshold));
|
|
474
|
+
const b = Math.max(0, Math.min(1, pixels[base + 2] + threshold));
|
|
475
|
+
indices[y * width + x] = matchPixelLch(r, g, b, palL, palA, palB, palC);
|
|
198
476
|
}
|
|
199
477
|
}
|
|
200
|
-
return { width, height, indices, palette };
|
|
478
|
+
return { width, height, indices, palette: paletteRgb };
|
|
201
479
|
}
|
|
202
|
-
function
|
|
203
|
-
|
|
204
|
-
{ dx: 1, dy: 0, weight: 32 / 200 },
|
|
205
|
-
{ dx: 2, dy: 0, weight: 12 / 200 },
|
|
206
|
-
{ dx: -2, dy: 1, weight: 5 / 200 },
|
|
207
|
-
{ dx: -1, dy: 1, weight: 12 / 200 },
|
|
208
|
-
{ dx: 0, dy: 1, weight: 26 / 200 },
|
|
209
|
-
{ dx: 1, dy: 1, weight: 12 / 200 },
|
|
210
|
-
{ dx: 2, dy: 1, weight: 5 / 200 }
|
|
211
|
-
];
|
|
212
|
-
return errorDiffusionDither(image, colorScheme, kernel);
|
|
213
|
-
}
|
|
214
|
-
function floydSteinbergDither(image, colorScheme) {
|
|
215
|
-
const kernel = [
|
|
480
|
+
function floydSteinbergDither(image, scheme, serpentine = true) {
|
|
481
|
+
return errorDiffusionDither(image, scheme, [
|
|
216
482
|
{ dx: 1, dy: 0, weight: 7 / 16 },
|
|
217
483
|
{ dx: -1, dy: 1, weight: 3 / 16 },
|
|
218
484
|
{ dx: 0, dy: 1, weight: 5 / 16 },
|
|
219
485
|
{ dx: 1, dy: 1, weight: 1 / 16 }
|
|
220
|
-
];
|
|
221
|
-
|
|
486
|
+
], serpentine);
|
|
487
|
+
}
|
|
488
|
+
function burkesDither(image, scheme, serpentine = true) {
|
|
489
|
+
return errorDiffusionDither(image, scheme, [
|
|
490
|
+
{ dx: 1, dy: 0, weight: 8 / 32 },
|
|
491
|
+
{ dx: 2, dy: 0, weight: 4 / 32 },
|
|
492
|
+
{ dx: -2, dy: 1, weight: 2 / 32 },
|
|
493
|
+
{ dx: -1, dy: 1, weight: 4 / 32 },
|
|
494
|
+
{ dx: 0, dy: 1, weight: 8 / 32 },
|
|
495
|
+
{ dx: 1, dy: 1, weight: 4 / 32 },
|
|
496
|
+
{ dx: 2, dy: 1, weight: 2 / 32 }
|
|
497
|
+
], serpentine);
|
|
222
498
|
}
|
|
223
|
-
function sierraDither(image,
|
|
224
|
-
|
|
499
|
+
function sierraDither(image, scheme, serpentine = true) {
|
|
500
|
+
return errorDiffusionDither(image, scheme, [
|
|
225
501
|
{ dx: 1, dy: 0, weight: 5 / 32 },
|
|
226
502
|
{ dx: 2, dy: 0, weight: 3 / 32 },
|
|
227
503
|
{ dx: -2, dy: 1, weight: 2 / 32 },
|
|
@@ -232,30 +508,27 @@ function sierraDither(image, colorScheme) {
|
|
|
232
508
|
{ dx: -1, dy: 2, weight: 2 / 32 },
|
|
233
509
|
{ dx: 0, dy: 2, weight: 3 / 32 },
|
|
234
510
|
{ dx: 1, dy: 2, weight: 2 / 32 }
|
|
235
|
-
];
|
|
236
|
-
return errorDiffusionDither(image, colorScheme, kernel);
|
|
511
|
+
], serpentine);
|
|
237
512
|
}
|
|
238
|
-
function sierraLiteDither(image,
|
|
239
|
-
|
|
513
|
+
function sierraLiteDither(image, scheme, serpentine = true) {
|
|
514
|
+
return errorDiffusionDither(image, scheme, [
|
|
240
515
|
{ dx: 1, dy: 0, weight: 2 / 4 },
|
|
241
516
|
{ dx: -1, dy: 1, weight: 1 / 4 },
|
|
242
517
|
{ dx: 0, dy: 1, weight: 1 / 4 }
|
|
243
|
-
];
|
|
244
|
-
return errorDiffusionDither(image, colorScheme, kernel);
|
|
518
|
+
], serpentine);
|
|
245
519
|
}
|
|
246
|
-
function atkinsonDither(image,
|
|
247
|
-
|
|
520
|
+
function atkinsonDither(image, scheme, serpentine = true) {
|
|
521
|
+
return errorDiffusionDither(image, scheme, [
|
|
248
522
|
{ dx: 1, dy: 0, weight: 1 / 8 },
|
|
249
523
|
{ dx: 2, dy: 0, weight: 1 / 8 },
|
|
250
524
|
{ dx: -1, dy: 1, weight: 1 / 8 },
|
|
251
525
|
{ dx: 0, dy: 1, weight: 1 / 8 },
|
|
252
526
|
{ dx: 1, dy: 1, weight: 1 / 8 },
|
|
253
527
|
{ dx: 0, dy: 2, weight: 1 / 8 }
|
|
254
|
-
];
|
|
255
|
-
return errorDiffusionDither(image, colorScheme, kernel);
|
|
528
|
+
], serpentine);
|
|
256
529
|
}
|
|
257
|
-
function stuckiDither(image,
|
|
258
|
-
|
|
530
|
+
function stuckiDither(image, scheme, serpentine = true) {
|
|
531
|
+
return errorDiffusionDither(image, scheme, [
|
|
259
532
|
{ dx: 1, dy: 0, weight: 8 / 42 },
|
|
260
533
|
{ dx: 2, dy: 0, weight: 4 / 42 },
|
|
261
534
|
{ dx: -2, dy: 1, weight: 2 / 42 },
|
|
@@ -268,11 +541,10 @@ function stuckiDither(image, colorScheme) {
|
|
|
268
541
|
{ dx: 0, dy: 2, weight: 4 / 42 },
|
|
269
542
|
{ dx: 1, dy: 2, weight: 2 / 42 },
|
|
270
543
|
{ dx: 2, dy: 2, weight: 1 / 42 }
|
|
271
|
-
];
|
|
272
|
-
return errorDiffusionDither(image, colorScheme, kernel);
|
|
544
|
+
], serpentine);
|
|
273
545
|
}
|
|
274
|
-
function jarvisJudiceNinkeDither(image,
|
|
275
|
-
|
|
546
|
+
function jarvisJudiceNinkeDither(image, scheme, serpentine = true) {
|
|
547
|
+
return errorDiffusionDither(image, scheme, [
|
|
276
548
|
{ dx: 1, dy: 0, weight: 7 / 48 },
|
|
277
549
|
{ dx: 2, dy: 0, weight: 5 / 48 },
|
|
278
550
|
{ dx: -2, dy: 1, weight: 3 / 48 },
|
|
@@ -285,40 +557,46 @@ function jarvisJudiceNinkeDither(image, colorScheme) {
|
|
|
285
557
|
{ dx: 0, dy: 2, weight: 5 / 48 },
|
|
286
558
|
{ dx: 1, dy: 2, weight: 3 / 48 },
|
|
287
559
|
{ dx: 2, dy: 2, weight: 1 / 48 }
|
|
288
|
-
];
|
|
289
|
-
return errorDiffusionDither(image, colorScheme, kernel);
|
|
560
|
+
], serpentine);
|
|
290
561
|
}
|
|
291
562
|
|
|
292
563
|
// src/core.ts
|
|
293
|
-
function ditherImage(image, colorScheme, mode = 1 /* BURKES
|
|
564
|
+
function ditherImage(image, colorScheme, mode = 1 /* BURKES */, serpentine = true) {
|
|
294
565
|
switch (mode) {
|
|
295
566
|
case 0 /* NONE */:
|
|
296
567
|
return directPaletteMap(image, colorScheme);
|
|
297
568
|
case 2 /* ORDERED */:
|
|
298
569
|
return orderedDither(image, colorScheme);
|
|
299
570
|
case 3 /* FLOYD_STEINBERG */:
|
|
300
|
-
return floydSteinbergDither(image, colorScheme);
|
|
571
|
+
return floydSteinbergDither(image, colorScheme, serpentine);
|
|
301
572
|
case 4 /* ATKINSON */:
|
|
302
|
-
return atkinsonDither(image, colorScheme);
|
|
573
|
+
return atkinsonDither(image, colorScheme, serpentine);
|
|
303
574
|
case 5 /* STUCKI */:
|
|
304
|
-
return stuckiDither(image, colorScheme);
|
|
575
|
+
return stuckiDither(image, colorScheme, serpentine);
|
|
305
576
|
case 6 /* SIERRA */:
|
|
306
|
-
return sierraDither(image, colorScheme);
|
|
577
|
+
return sierraDither(image, colorScheme, serpentine);
|
|
307
578
|
case 7 /* SIERRA_LITE */:
|
|
308
|
-
return sierraLiteDither(image, colorScheme);
|
|
579
|
+
return sierraLiteDither(image, colorScheme, serpentine);
|
|
309
580
|
case 8 /* JARVIS_JUDICE_NINKE */:
|
|
310
|
-
return jarvisJudiceNinkeDither(image, colorScheme);
|
|
581
|
+
return jarvisJudiceNinkeDither(image, colorScheme, serpentine);
|
|
311
582
|
case 1 /* BURKES */:
|
|
312
583
|
default:
|
|
313
|
-
return burkesDither(image, colorScheme);
|
|
584
|
+
return burkesDither(image, colorScheme, serpentine);
|
|
314
585
|
}
|
|
315
586
|
}
|
|
316
587
|
|
|
317
588
|
// src/index.ts
|
|
318
589
|
var VERSION = "0.1.0";
|
|
319
590
|
|
|
591
|
+
exports.BWRY_3_97 = BWRY_3_97;
|
|
592
|
+
exports.BWRY_4_2 = BWRY_4_2;
|
|
320
593
|
exports.ColorScheme = ColorScheme;
|
|
321
594
|
exports.DitherMode = DitherMode;
|
|
595
|
+
exports.HANSHOW_BWR = HANSHOW_BWR;
|
|
596
|
+
exports.HANSHOW_BWY = HANSHOW_BWY;
|
|
597
|
+
exports.MONO_4_26 = MONO_4_26;
|
|
598
|
+
exports.SOLUM_BWR = SOLUM_BWR;
|
|
599
|
+
exports.SPECTRA_7_3_6COLOR = SPECTRA_7_3_6COLOR;
|
|
322
600
|
exports.VERSION = VERSION;
|
|
323
601
|
exports.ditherImage = ditherImage;
|
|
324
602
|
exports.fromValue = fromValue;
|