imagescript 1.3.1 → 3.1.0
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/LICENSE +4 -1
- package/README.md +76 -26
- package/THIRD_PARTY_NOTICES.md +156 -0
- package/dist/animation.d.ts +55 -0
- package/dist/animation.js +167 -0
- package/dist/codecs/crc.d.ts +9 -0
- package/dist/codecs/crc.js +29 -0
- package/dist/codecs/exif.d.ts +11 -0
- package/dist/codecs/exif.js +27 -0
- package/dist/codecs/gif.d.ts +44 -0
- package/dist/codecs/gif.js +401 -0
- package/dist/codecs/jpeg.d.ts +35 -0
- package/dist/codecs/jpeg.js +980 -0
- package/dist/codecs/png.d.ts +97 -0
- package/dist/codecs/png.js +664 -0
- package/dist/codecs/quantize.d.ts +25 -0
- package/dist/codecs/quantize.js +302 -0
- package/dist/codecs/tga.d.ts +16 -0
- package/dist/codecs/tga.js +203 -0
- package/dist/codecs/tiff.d.ts +6 -0
- package/dist/codecs/tiff.js +546 -0
- package/dist/codecs/vp8-tables.d.ts +10 -0
- package/dist/codecs/vp8-tables.js +17 -0
- package/dist/codecs/vp8.d.ts +8 -0
- package/dist/codecs/vp8.js +711 -0
- package/dist/codecs/vp8l.d.ts +62 -0
- package/dist/codecs/vp8l.js +909 -0
- package/dist/codecs/webp.d.ts +67 -0
- package/dist/codecs/webp.js +280 -0
- package/dist/codecs/zlib.d.ts +22 -0
- package/dist/codecs/zlib.js +607 -0
- package/dist/frame.d.ts +57 -0
- package/dist/frame.js +65 -0
- package/dist/gif.d.ts +31 -0
- package/dist/gif.js +20 -0
- package/dist/image-type.d.ts +24 -0
- package/dist/image-type.js +64 -0
- package/dist/image.d.ts +553 -0
- package/dist/image.js +1156 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +20 -0
- package/dist/package.json +1 -0
- package/dist/render/font.d.ts +92 -0
- package/dist/render/font.js +954 -0
- package/dist/render/hinting.d.ts +110 -0
- package/dist/render/hinting.js +1236 -0
- package/dist/render/svg.d.ts +11 -0
- package/dist/render/svg.js +1717 -0
- package/dist/render/text.d.ts +63 -0
- package/dist/render/text.js +342 -0
- package/dist/text-layout.d.ts +46 -0
- package/dist/text-layout.js +35 -0
- package/dist/util/blur.d.ts +13 -0
- package/dist/util/blur.js +146 -0
- package/dist/util/mem.d.ts +10 -0
- package/dist/util/mem.js +23 -0
- package/dist/util/resample.d.ts +12 -0
- package/dist/util/resample.js +99 -0
- package/dist/util/utf8.d.ts +5 -0
- package/dist/util/utf8.js +55 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/licenses/Apache-2.0.txt +202 -0
- package/licenses/FTL.txt +169 -0
- package/licenses/README.ijg +260 -0
- package/package.json +31 -7
- package/ImageScript.d.ts +0 -954
- package/ImageScript.js +0 -1669
- package/bench/_.mjs +0 -166
- package/bench/all.mjs +0 -4
- package/bench/blur.mjs +0 -59
- package/bench/flip.mjs +0 -63
- package/bench/overlay.mjs +0 -35
- package/bench/package.json +0 -7
- package/bench/readme.md +0 -64
- package/bench/resize.mjs +0 -52
- package/codecs/LICENSE +0 -21
- package/codecs/lib.d.ts +0 -50
- package/codecs/lib.js +0 -6
- package/codecs/lib.mjs +0 -1
- package/codecs/node/bin/arm64-darwin.node +0 -0
- package/codecs/node/bin/arm64-linux.node +0 -0
- package/codecs/node/bin/x64-darwin.node +0 -0
- package/codecs/node/bin/x64-linux.node +0 -0
- package/codecs/node/bin/x64-win32.node +0 -0
- package/codecs/node/index.js +0 -3
- package/codecs/node/index.mjs +0 -6
- package/codecs/package.json +0 -14
- package/codecs/readme.md +0 -38
- package/codecs/wasm/bin/codecs.wasm +0 -0
- package/codecs/wasm/bin/simd.wasm +0 -0
- package/codecs/wasm/bundle/browser.js +0 -1
- package/codecs/wasm/index.js +0 -1
- package/codecs/wasm/index.mjs +0 -1
- package/png/README.md +0 -1
- package/png/node.js +0 -1137
- package/png/src/crc.mjs +0 -49
- package/png/src/mem.mjs +0 -24
- package/png/src/png.mjs +0 -254
- package/png/src/zlib.mjs +0 -618
- package/tsconfig.json +0 -21
- package/utils/mem.js +0 -26
- package/v2/codecs/magic.js +0 -25
- package/v2/framebuffer.d.ts +0 -67
- package/v2/framebuffer.js +0 -2352
- package/v2/framebuffer.mjs +0 -126
- package/v2/framebuffer.rs +0 -1264
- package/v2/index.d.ts +0 -84
- package/v2/index.js +0 -261
- package/v2/ops/blur.mjs +0 -303
- package/v2/ops/color.mjs +0 -277
- package/v2/ops/crop.mjs +0 -64
- package/v2/ops/fill.mjs +0 -33
- package/v2/ops/flip.mjs +0 -33
- package/v2/ops/index.mjs +0 -9
- package/v2/ops/iterator.mjs +0 -34
- package/v2/ops/overlay.mjs +0 -63
- package/v2/ops/resize.mjs +0 -166
- package/v2/ops/rotate.mjs +0 -117
- package/v2/util/mem.mjs +0 -24
- package/wasm/any/font.wasm +0 -0
- package/wasm/any/gif.wasm +0 -0
- package/wasm/any/jpeg.wasm +0 -0
- package/wasm/any/png.wasm +0 -0
- package/wasm/any/svg.wasm +0 -0
- package/wasm/any/tiff.wasm +0 -0
- package/wasm/node/font.js +0 -142
- package/wasm/node/gif.js +0 -76
- package/wasm/node/jpeg.js +0 -92
- package/wasm/node/png.js +0 -49
- package/wasm/node/svg.js +0 -56
- package/wasm/node/tiff.js +0 -47
|
@@ -0,0 +1,664 @@
|
|
|
1
|
+
import { crc32 } from "./crc.js";
|
|
2
|
+
import { concat } from "../util/mem.js";
|
|
3
|
+
import { encode as utf8, decode as utf8Decode } from "../util/utf8.js";
|
|
4
|
+
import { orientation as exifOrientation, xmpOrientation } from "./exif.js";
|
|
5
|
+
import { compressAsync, decompressAsync } from "./zlib.js";
|
|
6
|
+
const HEAD = Uint8Array.of(137, 80, 78, 71, 13, 10, 26, 10);
|
|
7
|
+
const IHDR = 0x49484452;
|
|
8
|
+
const PLTE = 0x504c5445;
|
|
9
|
+
const tRNS = 0x74524e53;
|
|
10
|
+
const IDAT = 0x49444154;
|
|
11
|
+
const IEND = 0x49454e44;
|
|
12
|
+
const eXIf = 0x65584966;
|
|
13
|
+
const tEXt = 0x74455874;
|
|
14
|
+
const zTXt = 0x7a545874;
|
|
15
|
+
const iTXt = 0x69545874;
|
|
16
|
+
const acTL = 0x6163544c;
|
|
17
|
+
const fcTL = 0x6663544c;
|
|
18
|
+
const fdAT = 0x66644154;
|
|
19
|
+
const COLOR_TYPE_CHANNELS = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 };
|
|
20
|
+
const CHANNELS_TO_COLOR_TYPE = { 1: 0, 2: 4, 3: 2, 4: 6 };
|
|
21
|
+
const VALID_DEPTHS = { 0: [1, 2, 4, 8, 16], 2: [8, 16], 3: [1, 2, 4, 8], 4: [8, 16], 6: [8, 16] };
|
|
22
|
+
const ADAM7 = [[0, 0, 8, 8], [4, 0, 8, 8], [0, 4, 4, 8], [2, 0, 4, 4], [0, 2, 2, 4], [1, 0, 2, 2], [0, 1, 1, 2]];
|
|
23
|
+
export async function encode(data, { text, width, height, channels, depth = 8, level = 0, palette, transparency, filter: filterRows = false }) {
|
|
24
|
+
const rowBytes = Math.ceil(width * channels * depth / 8);
|
|
25
|
+
let filtered;
|
|
26
|
+
if (filterRows)
|
|
27
|
+
filtered = filter(data, rowBytes, height, Math.max(1, channels * depth / 8));
|
|
28
|
+
else {
|
|
29
|
+
filtered = new Uint8Array(height * (rowBytes + 1));
|
|
30
|
+
for (let y = 0; y < height; y++)
|
|
31
|
+
filtered.set(data.subarray(y * rowBytes, (y + 1) * rowBytes), y * (rowBytes + 1) + 1);
|
|
32
|
+
}
|
|
33
|
+
const header = new Uint8Array(13);
|
|
34
|
+
const view = new DataView(header.buffer);
|
|
35
|
+
view.setUint32(0, width);
|
|
36
|
+
view.setUint32(4, height);
|
|
37
|
+
header[8] = depth;
|
|
38
|
+
header[9] = palette ? 3 : CHANNELS_TO_COLOR_TYPE[channels];
|
|
39
|
+
const chunks = [chunk(IHDR, header)];
|
|
40
|
+
if (palette)
|
|
41
|
+
chunks.push(chunk(PLTE, palette));
|
|
42
|
+
if (transparency)
|
|
43
|
+
chunks.push(chunk(tRNS, transparency));
|
|
44
|
+
chunks.push(chunk(IDAT, await compressAsync(filtered, level)));
|
|
45
|
+
for (const key in text) {
|
|
46
|
+
const value = text[key];
|
|
47
|
+
if (!value)
|
|
48
|
+
continue;
|
|
49
|
+
chunks.push(chunk(0x74455874, concat([utf8(key), [0], utf8(value)])));
|
|
50
|
+
}
|
|
51
|
+
chunks.push(chunk(IEND, new Uint8Array(0)));
|
|
52
|
+
return concat([HEAD, ...chunks]);
|
|
53
|
+
}
|
|
54
|
+
function filter(data, rowBytes, height, bpp) {
|
|
55
|
+
const out = new Uint8Array(height * (rowBytes + 1));
|
|
56
|
+
for (let y = 0; y < height; y++) {
|
|
57
|
+
const row = y * rowBytes, o = y * (rowBytes + 1) + 1;
|
|
58
|
+
out[o - 1] = 4;
|
|
59
|
+
if (y === 0) {
|
|
60
|
+
for (let i = 0; i < rowBytes; i++)
|
|
61
|
+
out[o + i] = data[row + i] - (i >= bpp ? data[row + i - bpp] : 0);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
for (let i = 0; i < bpp; i++)
|
|
65
|
+
out[o + i] = data[row + i] - data[row + i - rowBytes];
|
|
66
|
+
for (let i = bpp; i < rowBytes; i++) {
|
|
67
|
+
const a = data[row + i - bpp], b = data[row + i - rowBytes], c = data[row + i - rowBytes - bpp];
|
|
68
|
+
const pa = Math.abs(b - c), pb = Math.abs(a - c), pc = Math.abs(a + b - 2 * c);
|
|
69
|
+
out[o + i] = data[row + i] - (pa <= pb && pa <= pc ? a : pb <= pc ? b : c);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
function chunk(type, body) {
|
|
75
|
+
const out = new Uint8Array(body.length + 12);
|
|
76
|
+
const view = new DataView(out.buffer);
|
|
77
|
+
view.setUint32(0, body.length);
|
|
78
|
+
view.setUint32(4, type);
|
|
79
|
+
out.set(body, 8);
|
|
80
|
+
view.setUint32(body.length + 8, crc32(out.subarray(4, body.length + 8)));
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
function pixelFormat(images) {
|
|
84
|
+
let opaque = true, grey = true;
|
|
85
|
+
let colors = new Map();
|
|
86
|
+
for (const rgba of images) {
|
|
87
|
+
const pixels = new Uint32Array(rgba.buffer, rgba.byteOffset, rgba.length >> 2);
|
|
88
|
+
let previous = -1;
|
|
89
|
+
for (let i = 0; i < pixels.length; i++) {
|
|
90
|
+
const value = pixels[i];
|
|
91
|
+
if (value === previous)
|
|
92
|
+
continue;
|
|
93
|
+
previous = value;
|
|
94
|
+
const o = i * 4;
|
|
95
|
+
if (rgba[o + 3] !== 255)
|
|
96
|
+
opaque = false;
|
|
97
|
+
if (rgba[o] !== rgba[o + 1] || rgba[o] !== rgba[o + 2])
|
|
98
|
+
grey = false;
|
|
99
|
+
if (colors && !colors.has(value)) {
|
|
100
|
+
if (colors.size === 256)
|
|
101
|
+
colors = null;
|
|
102
|
+
else
|
|
103
|
+
colors.set(value, colors.size);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (colors) {
|
|
108
|
+
const entries = [...colors.keys()].map(value => new Uint8Array(new Uint32Array([value]).buffer));
|
|
109
|
+
entries.sort((a, b) => a[3] - b[3]);
|
|
110
|
+
const index = new Map();
|
|
111
|
+
entries.forEach((bytes, i) => index.set(new Uint32Array(bytes.buffer)[0], i));
|
|
112
|
+
const palette = new Uint8Array(entries.length * 3);
|
|
113
|
+
entries.forEach((bytes, i) => palette.set(bytes.subarray(0, 3), i * 3));
|
|
114
|
+
const translucent = entries.filter(bytes => bytes[3] !== 255).length;
|
|
115
|
+
const transparency = translucent ? Uint8Array.from(entries.slice(0, translucent), bytes => bytes[3]) : undefined;
|
|
116
|
+
const depth = entries.length <= 2 ? 1 : entries.length <= 4 ? 2 : entries.length <= 16 ? 4 : 8;
|
|
117
|
+
return {
|
|
118
|
+
channels: 1, depth, palette, transparency,
|
|
119
|
+
convert(rgba, width, height) {
|
|
120
|
+
const pixels = new Uint32Array(rgba.buffer, rgba.byteOffset, width * height);
|
|
121
|
+
const rowBytes = Math.ceil(width * depth / 8);
|
|
122
|
+
const perByte = 8 / depth;
|
|
123
|
+
const data = new Uint8Array(rowBytes * height);
|
|
124
|
+
let last = -1, lastIndex = 0;
|
|
125
|
+
for (let y = 0; y < height; y++) {
|
|
126
|
+
for (let x = 0; x < width; x++) {
|
|
127
|
+
const value = pixels[y * width + x];
|
|
128
|
+
if (value !== last) {
|
|
129
|
+
last = value;
|
|
130
|
+
lastIndex = index.get(value);
|
|
131
|
+
}
|
|
132
|
+
data[y * rowBytes + (x / perByte | 0)] |= lastIndex << (8 - depth - (x % perByte) * depth);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return data;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const channels = (grey ? 1 : 3) + (opaque ? 0 : 1);
|
|
140
|
+
return {
|
|
141
|
+
channels, depth: 8,
|
|
142
|
+
convert(rgba, width, height) {
|
|
143
|
+
if (channels === 4)
|
|
144
|
+
return rgba;
|
|
145
|
+
const count = width * height;
|
|
146
|
+
const data = new Uint8Array(count * channels);
|
|
147
|
+
for (let i = 0, o = 0; i < count; i++) {
|
|
148
|
+
const s = i * 4;
|
|
149
|
+
if (grey)
|
|
150
|
+
data[o++] = rgba[s];
|
|
151
|
+
else {
|
|
152
|
+
data[o++] = rgba[s];
|
|
153
|
+
data[o++] = rgba[s + 1];
|
|
154
|
+
data[o++] = rgba[s + 2];
|
|
155
|
+
}
|
|
156
|
+
if (!opaque)
|
|
157
|
+
data[o++] = rgba[s + 3];
|
|
158
|
+
}
|
|
159
|
+
return data;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const aligned = (rgba) => rgba.byteOffset % 4 ? rgba.slice() : rgba;
|
|
164
|
+
export async function encodeRGBA(rgba, width, height, level, text) {
|
|
165
|
+
rgba = aligned(rgba.subarray(0, width * height * 4));
|
|
166
|
+
const format = pixelFormat([rgba]);
|
|
167
|
+
const { channels, depth, palette, transparency } = format;
|
|
168
|
+
return encode(format.convert(rgba, width, height), { width, height, level, text, channels, depth, palette, transparency, filter: level > 0 && !palette });
|
|
169
|
+
}
|
|
170
|
+
async function compressRows(data, width, height, channels, depth, level, filterRows) {
|
|
171
|
+
const rowBytes = Math.ceil(width * channels * depth / 8);
|
|
172
|
+
let filtered;
|
|
173
|
+
if (filterRows)
|
|
174
|
+
filtered = filter(data, rowBytes, height, Math.max(1, channels * depth / 8));
|
|
175
|
+
else {
|
|
176
|
+
filtered = new Uint8Array(height * (rowBytes + 1));
|
|
177
|
+
for (let y = 0; y < height; y++)
|
|
178
|
+
filtered.set(data.subarray(y * rowBytes, (y + 1) * rowBytes), y * (rowBytes + 1) + 1);
|
|
179
|
+
}
|
|
180
|
+
return compressAsync(filtered, level);
|
|
181
|
+
}
|
|
182
|
+
export async function encodeAnimation(frames, width, height, plays, level) {
|
|
183
|
+
if (!frames.length)
|
|
184
|
+
throw new Error('PNG: an animation needs at least one frame');
|
|
185
|
+
const first = frames[0];
|
|
186
|
+
if (first.x !== 0 || first.y !== 0 || first.width !== width || first.height !== height) {
|
|
187
|
+
const pixels = new Uint8Array(width * height * 4);
|
|
188
|
+
for (let y = 0; y < first.height && first.y + y < height; y++) {
|
|
189
|
+
const row = first.pixels.subarray(y * first.width * 4, (y * first.width + Math.min(first.width, width - first.x)) * 4);
|
|
190
|
+
pixels.set(row, ((first.y + y) * width + first.x) * 4);
|
|
191
|
+
}
|
|
192
|
+
frames = [{ ...first, x: 0, y: 0, width, height, pixels }, ...frames.slice(1)];
|
|
193
|
+
}
|
|
194
|
+
for (const f of frames)
|
|
195
|
+
if (f.x < 0 || f.y < 0 || f.x + f.width > width || f.y + f.height > height || !f.width || !f.height)
|
|
196
|
+
throw new RangeError('PNG: APNG frames have to lie within the canvas');
|
|
197
|
+
const images = frames.map(f => aligned(f.pixels.subarray(0, f.width * f.height * 4)));
|
|
198
|
+
const format = pixelFormat(images);
|
|
199
|
+
const { channels, depth, palette, transparency } = format;
|
|
200
|
+
const filterRows = level > 0 && !palette;
|
|
201
|
+
const header = new Uint8Array(13);
|
|
202
|
+
const hv = new DataView(header.buffer);
|
|
203
|
+
hv.setUint32(0, width);
|
|
204
|
+
hv.setUint32(4, height);
|
|
205
|
+
header[8] = depth;
|
|
206
|
+
header[9] = palette ? 3 : CHANNELS_TO_COLOR_TYPE[channels];
|
|
207
|
+
const actl = new Uint8Array(8);
|
|
208
|
+
new DataView(actl.buffer).setUint32(0, frames.length);
|
|
209
|
+
new DataView(actl.buffer).setUint32(4, plays);
|
|
210
|
+
const chunks = [chunk(IHDR, header), chunk(acTL, actl)];
|
|
211
|
+
if (palette)
|
|
212
|
+
chunks.push(chunk(PLTE, palette));
|
|
213
|
+
if (transparency)
|
|
214
|
+
chunks.push(chunk(tRNS, transparency));
|
|
215
|
+
let sequence = 0;
|
|
216
|
+
for (let i = 0; i < frames.length; i++) {
|
|
217
|
+
const f = frames[i];
|
|
218
|
+
const fctl = new Uint8Array(26);
|
|
219
|
+
const v = new DataView(fctl.buffer);
|
|
220
|
+
v.setUint32(0, sequence++);
|
|
221
|
+
v.setUint32(4, f.width);
|
|
222
|
+
v.setUint32(8, f.height);
|
|
223
|
+
v.setUint32(12, f.x);
|
|
224
|
+
v.setUint32(16, f.y);
|
|
225
|
+
const ms = Math.max(0, Math.round(f.duration));
|
|
226
|
+
if (ms <= 65535) {
|
|
227
|
+
v.setUint16(20, ms);
|
|
228
|
+
v.setUint16(22, 1000);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
v.setUint16(20, Math.min(65535, Math.round(ms / 10)));
|
|
232
|
+
v.setUint16(22, 100);
|
|
233
|
+
}
|
|
234
|
+
fctl[24] = f.dispose;
|
|
235
|
+
fctl[25] = 1;
|
|
236
|
+
chunks.push(chunk(fcTL, fctl));
|
|
237
|
+
const data = await compressRows(format.convert(images[i], f.width, f.height), f.width, f.height, channels, depth, level, filterRows);
|
|
238
|
+
if (i === 0)
|
|
239
|
+
chunks.push(chunk(IDAT, data));
|
|
240
|
+
else {
|
|
241
|
+
const body = new Uint8Array(data.length + 4);
|
|
242
|
+
new DataView(body.buffer).setUint32(0, sequence++);
|
|
243
|
+
body.set(data, 4);
|
|
244
|
+
chunks.push(chunk(fdAT, body));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
chunks.push(chunk(IEND, new Uint8Array(0)));
|
|
248
|
+
return concat([HEAD, ...chunks]);
|
|
249
|
+
}
|
|
250
|
+
function parse(array) {
|
|
251
|
+
const view = new DataView(array.buffer, array.byteOffset, array.byteLength);
|
|
252
|
+
for (let i = 0; i < HEAD.length; i++)
|
|
253
|
+
if (array[i] !== HEAD[i])
|
|
254
|
+
throw new Error('PNG: invalid signature');
|
|
255
|
+
let header = null;
|
|
256
|
+
let palette = null;
|
|
257
|
+
let transparency = null;
|
|
258
|
+
const idat = [];
|
|
259
|
+
let exif = null;
|
|
260
|
+
const text = [];
|
|
261
|
+
let animation = null;
|
|
262
|
+
const frames = [];
|
|
263
|
+
let current = null;
|
|
264
|
+
let sequence = 0;
|
|
265
|
+
let animationError = false;
|
|
266
|
+
let offset = 8;
|
|
267
|
+
while (offset + 8 <= array.length) {
|
|
268
|
+
const length = view.getUint32(offset);
|
|
269
|
+
const type = view.getUint32(offset + 4);
|
|
270
|
+
const start = offset + 8;
|
|
271
|
+
const end = start + length;
|
|
272
|
+
if (end > array.length) {
|
|
273
|
+
if (type === IDAT)
|
|
274
|
+
idat.push(array.subarray(start));
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
const chunk = array.subarray(start, end);
|
|
278
|
+
if (end + 4 <= array.length && crc32(array.subarray(offset + 4, end)) !== view.getUint32(end))
|
|
279
|
+
throw new Error('PNG: chunk checksum mismatch');
|
|
280
|
+
if (type === IHDR) {
|
|
281
|
+
if (length < 13)
|
|
282
|
+
throw new Error('PNG: invalid IHDR');
|
|
283
|
+
header = {
|
|
284
|
+
width: view.getUint32(start),
|
|
285
|
+
height: view.getUint32(start + 4),
|
|
286
|
+
depth: chunk[8],
|
|
287
|
+
colorType: chunk[9],
|
|
288
|
+
interlace: chunk[12]
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
else if (type === PLTE) {
|
|
292
|
+
if (palette)
|
|
293
|
+
throw new Error('PNG: PLTE can only occur once in an image');
|
|
294
|
+
palette = chunk;
|
|
295
|
+
}
|
|
296
|
+
else if (type === tRNS) {
|
|
297
|
+
if (transparency)
|
|
298
|
+
throw new Error('PNG: tRNS can only occur once in an image');
|
|
299
|
+
transparency = chunk;
|
|
300
|
+
}
|
|
301
|
+
else if (type === IDAT) {
|
|
302
|
+
idat.push(chunk);
|
|
303
|
+
if (current && !frames.length)
|
|
304
|
+
frames.push(current);
|
|
305
|
+
}
|
|
306
|
+
else if (type === acTL) {
|
|
307
|
+
if (animation || idat.length || length < 8)
|
|
308
|
+
animationError = true;
|
|
309
|
+
else
|
|
310
|
+
animation = { frames: view.getUint32(start), plays: view.getUint32(start + 4) };
|
|
311
|
+
}
|
|
312
|
+
else if (type === fcTL) {
|
|
313
|
+
if (length < 26 || view.getUint32(start) !== sequence++)
|
|
314
|
+
animationError = true;
|
|
315
|
+
if (animationError) {
|
|
316
|
+
offset = end + 4;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const num = view.getUint16(start + 20), den = view.getUint16(start + 22) || 100;
|
|
320
|
+
current = {
|
|
321
|
+
control: {
|
|
322
|
+
width: view.getUint32(start + 4),
|
|
323
|
+
height: view.getUint32(start + 8),
|
|
324
|
+
x: view.getUint32(start + 12),
|
|
325
|
+
y: view.getUint32(start + 16),
|
|
326
|
+
delay: num * 1000 / den,
|
|
327
|
+
dispose: chunk[24],
|
|
328
|
+
blend: chunk[25]
|
|
329
|
+
},
|
|
330
|
+
data: idat.length ? [] : idat
|
|
331
|
+
};
|
|
332
|
+
if (idat.length)
|
|
333
|
+
frames.push(current);
|
|
334
|
+
}
|
|
335
|
+
else if (type === fdAT) {
|
|
336
|
+
if (length < 4 || view.getUint32(start) !== sequence++ || !current || current.data === idat)
|
|
337
|
+
animationError = true;
|
|
338
|
+
else
|
|
339
|
+
current.data.push(chunk.subarray(4));
|
|
340
|
+
}
|
|
341
|
+
else if (type === eXIf)
|
|
342
|
+
exif ??= chunk;
|
|
343
|
+
else if (type === tEXt || type === zTXt || type === iTXt)
|
|
344
|
+
text.push({ type, chunk });
|
|
345
|
+
else if (type === IEND)
|
|
346
|
+
break;
|
|
347
|
+
offset = end + 4;
|
|
348
|
+
}
|
|
349
|
+
if (!header)
|
|
350
|
+
throw new Error('PNG: missing IHDR');
|
|
351
|
+
if (!idat.length)
|
|
352
|
+
throw new Error('PNG: missing IDAT');
|
|
353
|
+
const { width, height, depth, colorType } = header;
|
|
354
|
+
const channels = COLOR_TYPE_CHANNELS[colorType];
|
|
355
|
+
if (!channels)
|
|
356
|
+
throw new Error(`PNG: invalid color type ${colorType}`);
|
|
357
|
+
if (!VALID_DEPTHS[colorType].includes(depth))
|
|
358
|
+
throw new Error(`PNG: invalid bit depth ${depth} for color type ${colorType}`);
|
|
359
|
+
if (width === 0 || height === 0)
|
|
360
|
+
throw new Error('PNG: invalid dimensions');
|
|
361
|
+
if (colorType === 3 && !palette)
|
|
362
|
+
throw new Error('PNG: indexed color image has no PLTE');
|
|
363
|
+
if (animation) {
|
|
364
|
+
if (frames.length > animation.frames)
|
|
365
|
+
frames.length = animation.frames;
|
|
366
|
+
if (!frames.length || frames.length < animation.frames || frames.some(f => !f.data.length))
|
|
367
|
+
animationError = true;
|
|
368
|
+
}
|
|
369
|
+
return { header, palette, transparency, idat, exif, text, animation, frames, animationError };
|
|
370
|
+
}
|
|
371
|
+
async function decodeImageData(p, chunks, width, height) {
|
|
372
|
+
const { depth, colorType, interlace } = p.header;
|
|
373
|
+
const channels = COLOR_TYPE_CHANNELS[colorType];
|
|
374
|
+
const bitsPerPixel = channels * depth;
|
|
375
|
+
const bpp = Math.max(1, bitsPerPixel >> 3);
|
|
376
|
+
const passes = interlace ? ADAM7.map(([xs, ys, dx, dy]) => [xs, ys, dx, dy, Math.ceil((width - xs) / dx), Math.ceil((height - ys) / dy)]) : [[0, 0, 1, 1, width, height]];
|
|
377
|
+
let expected = 0;
|
|
378
|
+
for (const pass of passes) {
|
|
379
|
+
const [, , , , pw, ph] = pass;
|
|
380
|
+
if (pw > 0 && ph > 0)
|
|
381
|
+
expected += ph * (1 + Math.ceil(pw * bitsPerPixel / 8));
|
|
382
|
+
}
|
|
383
|
+
if (!chunks.length)
|
|
384
|
+
throw new Error('PNG: missing image data');
|
|
385
|
+
const data = await decompressAsync(chunks.length === 1 ? chunks[0] : concat(chunks), expected);
|
|
386
|
+
if (data.length < expected)
|
|
387
|
+
throw new Error('PNG: image data is truncated');
|
|
388
|
+
const rgba = new Uint8Array(width * height * 4);
|
|
389
|
+
const toRGBA = createConverter(colorType, depth, p.palette, p.transparency);
|
|
390
|
+
let dataOffset = 0;
|
|
391
|
+
for (const [xs, ys, dx, dy, pw, ph] of passes) {
|
|
392
|
+
if (pw <= 0 || ph <= 0)
|
|
393
|
+
continue;
|
|
394
|
+
const rowBytes = Math.ceil(pw * bitsPerPixel / 8);
|
|
395
|
+
const size = ph * rowBytes;
|
|
396
|
+
const pixels = unfilter(data, dataOffset, ph, rowBytes, bpp);
|
|
397
|
+
dataOffset += size + ph;
|
|
398
|
+
toRGBA(pixels, rowBytes, pw, ph, rgba, { row: y => ((ys + y * dy) * width + xs) * 4, step: dx * 4 });
|
|
399
|
+
}
|
|
400
|
+
return rgba;
|
|
401
|
+
}
|
|
402
|
+
export async function decode(array) {
|
|
403
|
+
const p = parse(array);
|
|
404
|
+
const { width, height } = p.header;
|
|
405
|
+
const pixels = await decodeImageData(p, p.idat, width, height);
|
|
406
|
+
return { width, height, pixels, orientation: p.exif ? exifOrientation(p.exif) : await textOrientation(p.text) };
|
|
407
|
+
}
|
|
408
|
+
export function isAnimated(array) {
|
|
409
|
+
try {
|
|
410
|
+
const p = parse(array);
|
|
411
|
+
return p.animation !== null && !p.animationError;
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
export async function decodeAnimation(array, onlyFirstFrame = false) {
|
|
418
|
+
const p = parse(array);
|
|
419
|
+
const { width, height } = p.header;
|
|
420
|
+
if (!p.animation || p.animationError) {
|
|
421
|
+
const pixels = await decodeImageData(p, p.idat, width, height);
|
|
422
|
+
return { width, height, plays: 0, frames: [{ x: 0, y: 0, width, height, duration: 0, dispose: 0, blend: 0, pixels }] };
|
|
423
|
+
}
|
|
424
|
+
const frames = [];
|
|
425
|
+
for (const { control: c, data } of p.frames) {
|
|
426
|
+
if (!c.width || !c.height || c.x + c.width > width || c.y + c.height > height)
|
|
427
|
+
throw new Error('PNG: invalid APNG frame region');
|
|
428
|
+
frames.push({
|
|
429
|
+
x: c.x, y: c.y, width: c.width, height: c.height, duration: c.delay,
|
|
430
|
+
dispose: !frames.length && c.dispose === 2 ? 1 : c.dispose,
|
|
431
|
+
blend: c.blend,
|
|
432
|
+
pixels: await decodeImageData(p, data, c.width, c.height)
|
|
433
|
+
});
|
|
434
|
+
if (onlyFirstFrame)
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
return { width, height, plays: p.animation.plays, frames };
|
|
438
|
+
}
|
|
439
|
+
export function compositeAnimation({ width, height, frames }) {
|
|
440
|
+
const canvas = new Uint8Array(width * height * 4);
|
|
441
|
+
const output = [];
|
|
442
|
+
for (const f of frames) {
|
|
443
|
+
const previous = f.dispose === 2 ? canvas.slice() : null;
|
|
444
|
+
for (let y = 0; y < f.height; y++)
|
|
445
|
+
for (let x = 0; x < f.width; x++) {
|
|
446
|
+
const s = (y * f.width + x) * 4, d = ((f.y + y) * width + f.x + x) * 4;
|
|
447
|
+
const sa = f.pixels[s + 3], da = canvas[d + 3];
|
|
448
|
+
if (f.blend === 0 || sa === 255 || da === 0) {
|
|
449
|
+
if (f.blend === 0 || sa !== 0)
|
|
450
|
+
canvas.set(f.pixels.subarray(s, s + 4), d);
|
|
451
|
+
}
|
|
452
|
+
else if (sa !== 0) {
|
|
453
|
+
const u = sa * 255, v = (255 - sa) * da, al = u + v;
|
|
454
|
+
for (let c = 0; c < 3; c++)
|
|
455
|
+
canvas[d + c] = (f.pixels[s + c] * u + canvas[d + c] * v) / al | 0;
|
|
456
|
+
canvas[d + 3] = al / 255 | 0;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
output.push({ pixels: canvas.slice(), duration: f.duration });
|
|
460
|
+
if (f.dispose === 1)
|
|
461
|
+
for (let y = 0; y < f.height; y++)
|
|
462
|
+
canvas.fill(0, ((f.y + y) * width + f.x) * 4, ((f.y + y) * width + f.x + f.width) * 4);
|
|
463
|
+
else if (previous)
|
|
464
|
+
canvas.set(previous);
|
|
465
|
+
}
|
|
466
|
+
return output;
|
|
467
|
+
}
|
|
468
|
+
const RAW_EXIF = /^Raw profile type (exif|APP1)$/;
|
|
469
|
+
const XMP = 'XML:com.adobe.xmp';
|
|
470
|
+
async function textOrientation(chunks) {
|
|
471
|
+
for (const { type, chunk } of chunks) {
|
|
472
|
+
const separator = chunk.indexOf(0);
|
|
473
|
+
if (separator < 1)
|
|
474
|
+
continue;
|
|
475
|
+
const keyword = utf8Decode(chunk.subarray(0, separator));
|
|
476
|
+
if (!RAW_EXIF.test(keyword) && keyword !== XMP)
|
|
477
|
+
continue;
|
|
478
|
+
let body;
|
|
479
|
+
try {
|
|
480
|
+
if (type === tEXt)
|
|
481
|
+
body = chunk.subarray(separator + 1);
|
|
482
|
+
else if (type === zTXt)
|
|
483
|
+
body = await decompressAsync(chunk.subarray(separator + 2), 1 << 20);
|
|
484
|
+
else {
|
|
485
|
+
const language = chunk.indexOf(0, separator + 3);
|
|
486
|
+
const translated = language < 0 ? -1 : chunk.indexOf(0, language + 1);
|
|
487
|
+
if (translated < 0)
|
|
488
|
+
continue;
|
|
489
|
+
body = chunk[separator + 1] ? await decompressAsync(chunk.subarray(translated + 1), 1 << 20) : chunk.subarray(translated + 1);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
const value = utf8Decode(body);
|
|
496
|
+
const orientation = keyword === XMP ? xmpOrientation(value) : exifOrientation(hexProfile(value));
|
|
497
|
+
if (orientation !== 1)
|
|
498
|
+
return orientation;
|
|
499
|
+
}
|
|
500
|
+
return 1;
|
|
501
|
+
}
|
|
502
|
+
function hexProfile(text) {
|
|
503
|
+
const match = /^\s*\S+\s+(\d+)\s+([\da-fA-F\s]+)/.exec(text);
|
|
504
|
+
if (!match)
|
|
505
|
+
return new Uint8Array(0);
|
|
506
|
+
const digits = match[2].replace(/\s+/g, '');
|
|
507
|
+
const out = new Uint8Array(Math.min(+match[1], digits.length >> 1));
|
|
508
|
+
for (let i = 0; i < out.length; i++)
|
|
509
|
+
out[i] = parseInt(digits.substr(i * 2, 2), 16);
|
|
510
|
+
return out;
|
|
511
|
+
}
|
|
512
|
+
function unfilter(data, offset, height, rowBytes, bpp) {
|
|
513
|
+
const out = new Uint8Array(height * rowBytes);
|
|
514
|
+
const first = Math.min(bpp, rowBytes);
|
|
515
|
+
for (let y = 0; y < height; y++) {
|
|
516
|
+
const filter = data[offset++];
|
|
517
|
+
const row = y * rowBytes;
|
|
518
|
+
const src = offset - row;
|
|
519
|
+
if (filter === 0 || (filter === 2 && y === 0))
|
|
520
|
+
out.set(data.subarray(offset, offset + rowBytes), row);
|
|
521
|
+
else if (filter === 1) {
|
|
522
|
+
out.set(data.subarray(offset, offset + first), row);
|
|
523
|
+
for (let i = row + bpp; i < row + rowBytes; i++)
|
|
524
|
+
out[i] = data[src + i] + out[i - bpp];
|
|
525
|
+
}
|
|
526
|
+
else if (filter === 2) {
|
|
527
|
+
for (let i = row; i < row + rowBytes; i++)
|
|
528
|
+
out[i] = data[src + i] + out[i - rowBytes];
|
|
529
|
+
}
|
|
530
|
+
else if (filter === 3) {
|
|
531
|
+
if (y === 0) {
|
|
532
|
+
out.set(data.subarray(offset, offset + first), row);
|
|
533
|
+
for (let i = row + bpp; i < row + rowBytes; i++)
|
|
534
|
+
out[i] = data[src + i] + (out[i - bpp] >> 1);
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
for (let i = row; i < row + first; i++)
|
|
538
|
+
out[i] = data[src + i] + (out[i - rowBytes] >> 1);
|
|
539
|
+
for (let i = row + bpp; i < row + rowBytes; i++)
|
|
540
|
+
out[i] = data[src + i] + (out[i - bpp] + out[i - rowBytes] >> 1);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
else if (filter === 4) {
|
|
544
|
+
if (y === 0) {
|
|
545
|
+
out.set(data.subarray(offset, offset + first), row);
|
|
546
|
+
for (let i = row + bpp; i < row + rowBytes; i++)
|
|
547
|
+
out[i] = data[src + i] + out[i - bpp];
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
for (let i = row; i < row + first; i++)
|
|
551
|
+
out[i] = data[src + i] + out[i - rowBytes];
|
|
552
|
+
for (let i = row + bpp; i < row + rowBytes; i++) {
|
|
553
|
+
const a = out[i - bpp], b = out[i - rowBytes], c = out[i - rowBytes - bpp];
|
|
554
|
+
const pa = Math.abs(b - c), pb = Math.abs(a - c), pc = Math.abs(a + b - 2 * c);
|
|
555
|
+
out[i] = data[src + i] + (pa <= pb && pa <= pc ? a : pb <= pc ? b : c);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
else
|
|
560
|
+
throw new Error(`PNG: invalid filter type ${filter}`);
|
|
561
|
+
offset += rowBytes;
|
|
562
|
+
}
|
|
563
|
+
return out;
|
|
564
|
+
}
|
|
565
|
+
function createConverter(colorType, depth, palette, transparency) {
|
|
566
|
+
const maxValue = (1 << depth) - 1;
|
|
567
|
+
const sample = depth === 8 ? (row, n) => row[n]
|
|
568
|
+
: depth === 16 ? (row, n) => row[n * 2] << 8 | row[n * 2 + 1]
|
|
569
|
+
: (row, n) => {
|
|
570
|
+
const bit = n * depth;
|
|
571
|
+
return (row[bit >> 3] >> (8 - depth - (bit & 7))) & maxValue;
|
|
572
|
+
};
|
|
573
|
+
const scale = depth === 8 ? v => v : depth === 16 ? v => v >> 8 : v => Math.round(v * 255 / maxValue);
|
|
574
|
+
if (colorType === 3) {
|
|
575
|
+
const pal = palette;
|
|
576
|
+
const entries = pal.length / 3 | 0;
|
|
577
|
+
const lut = new Uint8Array(256 * 4);
|
|
578
|
+
for (let i = 0; i < 256; i++) {
|
|
579
|
+
if (i < entries) {
|
|
580
|
+
lut[i * 4] = pal[i * 3];
|
|
581
|
+
lut[i * 4 + 1] = pal[i * 3 + 1];
|
|
582
|
+
lut[i * 4 + 2] = pal[i * 3 + 2];
|
|
583
|
+
}
|
|
584
|
+
lut[i * 4 + 3] = transparency && i < transparency.length ? transparency[i] : 0xff;
|
|
585
|
+
}
|
|
586
|
+
return (pixels, rowBytes, w, h, out, place) => {
|
|
587
|
+
for (let y = 0; y < h; y++) {
|
|
588
|
+
const row = pixels.subarray(y * rowBytes, (y + 1) * rowBytes);
|
|
589
|
+
for (let x = 0, o = place.row(y); x < w; x++, o += place.step) {
|
|
590
|
+
const i = sample(row, x) * 4;
|
|
591
|
+
out[o] = lut[i];
|
|
592
|
+
out[o + 1] = lut[i + 1];
|
|
593
|
+
out[o + 2] = lut[i + 2];
|
|
594
|
+
out[o + 3] = lut[i + 3];
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
if (colorType === 0) {
|
|
600
|
+
const key = transparency && transparency.length >= 2 ? (transparency[0] << 8 | transparency[1]) & maxValue : -1;
|
|
601
|
+
return (pixels, rowBytes, w, h, out, place) => {
|
|
602
|
+
for (let y = 0; y < h; y++) {
|
|
603
|
+
const row = pixels.subarray(y * rowBytes, (y + 1) * rowBytes);
|
|
604
|
+
for (let x = 0, o = place.row(y); x < w; x++, o += place.step) {
|
|
605
|
+
const raw = sample(row, x);
|
|
606
|
+
const v = scale(raw);
|
|
607
|
+
out[o] = out[o + 1] = out[o + 2] = v;
|
|
608
|
+
out[o + 3] = raw === key ? 0 : 0xff;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
if (colorType === 2) {
|
|
614
|
+
const key = transparency && transparency.length >= 6
|
|
615
|
+
? [transparency[0] << 8 | transparency[1], transparency[2] << 8 | transparency[3], transparency[4] << 8 | transparency[5]].map(v => v & maxValue)
|
|
616
|
+
: null;
|
|
617
|
+
return (pixels, rowBytes, w, h, out, place) => {
|
|
618
|
+
for (let y = 0; y < h; y++) {
|
|
619
|
+
const row = pixels.subarray(y * rowBytes, (y + 1) * rowBytes);
|
|
620
|
+
for (let x = 0, o = place.row(y); x < w; x++, o += place.step) {
|
|
621
|
+
const r = sample(row, x * 3);
|
|
622
|
+
const g = sample(row, x * 3 + 1);
|
|
623
|
+
const b = sample(row, x * 3 + 2);
|
|
624
|
+
out[o] = scale(r);
|
|
625
|
+
out[o + 1] = scale(g);
|
|
626
|
+
out[o + 2] = scale(b);
|
|
627
|
+
out[o + 3] = key && r === key[0] && g === key[1] && b === key[2] ? 0 : 0xff;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
if (colorType === 4) {
|
|
633
|
+
return (pixels, rowBytes, w, h, out, place) => {
|
|
634
|
+
for (let y = 0; y < h; y++) {
|
|
635
|
+
const row = pixels.subarray(y * rowBytes, (y + 1) * rowBytes);
|
|
636
|
+
for (let x = 0, o = place.row(y); x < w; x++, o += place.step) {
|
|
637
|
+
const v = scale(sample(row, x * 2));
|
|
638
|
+
out[o] = out[o + 1] = out[o + 2] = v;
|
|
639
|
+
out[o + 3] = scale(sample(row, x * 2 + 1));
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
if (depth === 8) {
|
|
645
|
+
return (pixels, rowBytes, w, h, out, place) => {
|
|
646
|
+
if (place.step === 4 && w * 4 === rowBytes && place.row(0) === 0 && place.row(1) === rowBytes)
|
|
647
|
+
return out.set(pixels);
|
|
648
|
+
for (let y = 0; y < h; y++)
|
|
649
|
+
for (let x = 0, o = place.row(y); x < w; x++, o += place.step)
|
|
650
|
+
out.set(pixels.subarray(y * rowBytes + x * 4, y * rowBytes + x * 4 + 4), o);
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
return (pixels, rowBytes, w, h, out, place) => {
|
|
654
|
+
for (let y = 0; y < h; y++) {
|
|
655
|
+
const row = pixels.subarray(y * rowBytes, (y + 1) * rowBytes);
|
|
656
|
+
for (let x = 0, o = place.row(y); x < w; x++, o += place.step) {
|
|
657
|
+
out[o] = scale(sample(row, x * 4));
|
|
658
|
+
out[o + 1] = scale(sample(row, x * 4 + 1));
|
|
659
|
+
out[o + 2] = scale(sample(row, x * 4 + 2));
|
|
660
|
+
out[o + 3] = scale(sample(row, x * 4 + 3));
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reduces RGBA pixel data to an indexed palette
|
|
3
|
+
* Pixels with alpha < 128 are mapped to a dedicated transparent index
|
|
4
|
+
* @param {Uint8Array} pixels RGBA pixel data
|
|
5
|
+
* @param {number} width Image width in pixels (needed for dithering)
|
|
6
|
+
* @param {{maxColors?: number, quality?: number}} [options] quality (1-100) scales the palette size exponentially (1 = 8 colors, 50 = 55, 90 and above = 256)
|
|
7
|
+
* @return {{palette: Uint8Array, indices: Uint8Array, transparentIndex: number}} palette is packed RGB, transparentIndex is -1 if unused
|
|
8
|
+
*/
|
|
9
|
+
export interface QuantizeOptions {
|
|
10
|
+
maxColors?: number;
|
|
11
|
+
quality?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface Quantized {
|
|
14
|
+
/** packed RGB */
|
|
15
|
+
palette: Uint8Array;
|
|
16
|
+
indices: Uint8Array;
|
|
17
|
+
/** -1 if no pixel is transparent */
|
|
18
|
+
transparentIndex: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Finds the color of the largest cluster among the opaque pixels that pass the filter
|
|
22
|
+
* @return packed RGB, or null if no pixel qualifies
|
|
23
|
+
*/
|
|
24
|
+
export declare function dominantColor(pixels: Uint8Array, include: (r: number, g: number, b: number) => boolean, clusters?: number): [number, number, number] | null;
|
|
25
|
+
export declare function quantize(pixels: Uint8Array, width: number, { maxColors, quality }?: QuantizeOptions): Quantized;
|