imagescript 3.2.1 → 3.4.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/README.md +35 -10
- package/THIRD_PARTY_NOTICES.md +298 -2
- package/dist/animation.d.ts +25 -8
- package/dist/animation.js +44 -13
- package/dist/codecs/av1/cdef.js +197 -0
- package/dist/codecs/av1/data.js +141 -0
- package/dist/codecs/av1/decode.js +1769 -0
- package/dist/codecs/av1/filmgrain.js +219 -0
- package/dist/codecs/av1/frame.js +114 -0
- package/dist/codecs/av1/ipred.js +491 -0
- package/dist/codecs/av1/itx.js +950 -0
- package/dist/codecs/av1/loopfilter.js +149 -0
- package/dist/codecs/av1/msac.js +161 -0
- package/dist/codecs/av1/obu.js +516 -0
- package/dist/codecs/av1/postfilter.js +28 -0
- package/dist/codecs/av1/restoration.js +193 -0
- package/dist/codecs/av1/tables.js +160 -0
- package/dist/codecs/avif.js +581 -0
- package/dist/codecs/bmp.js +317 -0
- package/dist/codecs/crc.js +5 -0
- package/dist/codecs/gif.js +10 -35
- package/dist/codecs/hdr.d.ts +25 -0
- package/dist/codecs/hdr.js +201 -0
- package/dist/codecs/ico.js +48 -0
- package/dist/codecs/jpeg.js +11 -28
- package/dist/codecs/jxl/bits.js +126 -0
- package/dist/codecs/jxl/blend.js +117 -0
- package/dist/codecs/jxl/color.js +116 -0
- package/dist/codecs/jxl/dct.js +404 -0
- package/dist/codecs/jxl/entropy.js +556 -0
- package/dist/codecs/jxl/features.js +325 -0
- package/dist/codecs/jxl/filters.js +119 -0
- package/dist/codecs/jxl/frame-header.js +195 -0
- package/dist/codecs/jxl/frame.js +319 -0
- package/dist/codecs/jxl/headers.js +249 -0
- package/dist/codecs/jxl/idct.js +484 -0
- package/dist/codecs/jxl/modular.js +688 -0
- package/dist/codecs/jxl/quant.js +219 -0
- package/dist/codecs/jxl/upsample.js +52 -0
- package/dist/codecs/jxl/vardct.js +472 -0
- package/dist/codecs/jxl.js +89 -0
- package/dist/codecs/netpbm.js +239 -0
- package/dist/codecs/png.js +22 -10
- package/dist/codecs/qoi.js +150 -0
- package/dist/codecs/tga.js +3 -1
- package/dist/codecs/tiff.js +7 -2
- package/dist/codecs/vp8-enc-dsp.js +932 -0
- package/dist/codecs/vp8-enc-tables.js +78 -0
- package/dist/codecs/vp8-enc.js +1930 -0
- package/dist/codecs/vp8.js +3 -1
- package/dist/codecs/vp8l.js +126 -58
- package/dist/codecs/webp.js +47 -11
- package/dist/codecs/zlib.js +81 -69
- package/dist/frame.d.ts +1 -0
- package/dist/frame.js +4 -2
- package/dist/gif.d.ts +1 -0
- package/dist/gif.js +2 -2
- package/dist/image-type.d.ts +15 -1
- package/dist/image-type.js +45 -2
- package/dist/image.d.ts +89 -13
- package/dist/image.js +266 -113
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -4
- package/dist/render/bidi.js +350 -0
- package/dist/render/buffer.js +362 -0
- package/dist/render/color.js +164 -0
- package/dist/render/colr.js +523 -0
- package/dist/render/font.js +173 -241
- package/dist/render/hinting.js +38 -22
- package/dist/render/layout.js +1553 -0
- package/dist/render/linebreak.js +210 -0
- package/dist/render/segment.js +106 -0
- package/dist/render/shape.js +984 -0
- package/dist/render/shapers/arabic.js +284 -0
- package/dist/render/shapers/hangul.js +142 -0
- package/dist/render/shapers/hebrew.js +49 -0
- package/dist/render/shapers/index.js +52 -0
- package/dist/render/shapers/indic.js +579 -0
- package/dist/render/shapers/khmer.js +104 -0
- package/dist/render/shapers/machine.js +129 -0
- package/dist/render/shapers/myanmar.js +121 -0
- package/dist/render/shapers/syllabic.js +100 -0
- package/dist/render/shapers/thai.js +125 -0
- package/dist/render/shapers/use.js +235 -0
- package/dist/render/svg.js +102 -23
- package/dist/render/text.js +373 -103
- package/dist/render/unicode-data.js +63 -0
- package/dist/render/unicode.js +50 -0
- package/dist/text-layout.d.ts +4 -1
- package/dist/text-layout.js +2 -2
- package/dist/util/alpha.js +43 -0
- package/dist/util/blur.js +20 -33
- package/dist/util/bytes.js +40 -0
- package/dist/util/limits.js +34 -0
- package/dist/util/options.js +7 -0
- package/dist/util/resample.js +126 -7
- package/dist/util/rotate.js +43 -0
- package/dist/version.js +1 -1
- package/licenses/HarfBuzz.txt +42 -0
- package/licenses/Unicode-3.0.txt +39 -0
- package/package.json +25 -2
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { MAX_PIXELS, checkPixels } from "../util/limits.js";
|
|
2
|
+
import { toSRGB } from "./hdr.js";
|
|
3
|
+
const isSpace = (c) => c === 0x20 || (c >= 0x09 && c <= 0x0d);
|
|
4
|
+
export function isNetpbm(d) {
|
|
5
|
+
if (d.length < 3 || d[0] !== 0x50)
|
|
6
|
+
return false;
|
|
7
|
+
if (d[1] === 0x37)
|
|
8
|
+
return d[2] === 0x0a;
|
|
9
|
+
return (d[1] >= 0x31 && d[1] <= 0x36 || d[1] === 0x46 || d[1] === 0x66) && isSpace(d[2]);
|
|
10
|
+
}
|
|
11
|
+
class Reader {
|
|
12
|
+
d;
|
|
13
|
+
o;
|
|
14
|
+
constructor(d, o) {
|
|
15
|
+
this.d = d;
|
|
16
|
+
this.o = o;
|
|
17
|
+
}
|
|
18
|
+
skip() {
|
|
19
|
+
const d = this.d;
|
|
20
|
+
while (this.o < d.length) {
|
|
21
|
+
if (d[this.o] === 0x23)
|
|
22
|
+
while (this.o < d.length && d[this.o] !== 0x0a && d[this.o] !== 0x0d)
|
|
23
|
+
this.o++;
|
|
24
|
+
else if (isSpace(d[this.o]))
|
|
25
|
+
this.o++;
|
|
26
|
+
else
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
token() {
|
|
31
|
+
this.skip();
|
|
32
|
+
const start = this.o;
|
|
33
|
+
while (this.o < this.d.length && !isSpace(this.d[this.o]) && this.d[this.o] !== 0x23)
|
|
34
|
+
this.o++;
|
|
35
|
+
if (start === this.o)
|
|
36
|
+
throw new Error('Netpbm: truncated header');
|
|
37
|
+
return String.fromCharCode(...this.d.subarray(start, this.o));
|
|
38
|
+
}
|
|
39
|
+
integer(name) {
|
|
40
|
+
const token = this.token();
|
|
41
|
+
if (!/^\d{1,10}$/.test(token))
|
|
42
|
+
throw new Error(`Netpbm: invalid ${name} ${token.slice(0, 20)}`);
|
|
43
|
+
return +token;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function pamHeader(d) {
|
|
47
|
+
const fields = {};
|
|
48
|
+
const types = [];
|
|
49
|
+
let o = 3;
|
|
50
|
+
for (;;) {
|
|
51
|
+
let end = o;
|
|
52
|
+
while (end < d.length && d[end] !== 0x0a)
|
|
53
|
+
end++;
|
|
54
|
+
if (end >= d.length)
|
|
55
|
+
throw new Error('PAM: truncated header');
|
|
56
|
+
const line = String.fromCharCode(...d.subarray(o, end)).trim();
|
|
57
|
+
o = end + 1;
|
|
58
|
+
if (line === 'ENDHDR')
|
|
59
|
+
break;
|
|
60
|
+
if (!line || line[0] === '#')
|
|
61
|
+
continue;
|
|
62
|
+
const [name, ...value] = line.split(/\s+/);
|
|
63
|
+
if (name === 'TUPLTYPE')
|
|
64
|
+
types.push(value.join(' '));
|
|
65
|
+
else
|
|
66
|
+
fields[name] = value.join(' ');
|
|
67
|
+
}
|
|
68
|
+
const number = (name) => {
|
|
69
|
+
if (!/^\d{1,10}$/.test(fields[name] ?? ''))
|
|
70
|
+
throw new Error(`PAM: invalid ${name}`);
|
|
71
|
+
return +fields[name];
|
|
72
|
+
};
|
|
73
|
+
return { width: number('WIDTH'), height: number('HEIGHT'), depth: number('DEPTH'), maxval: number('MAXVAL'), tupleType: types.join(' '), data: o };
|
|
74
|
+
}
|
|
75
|
+
export function decode(d, maxPixels = MAX_PIXELS, toneMapping = 'reinhard') {
|
|
76
|
+
if (!isNetpbm(d))
|
|
77
|
+
throw new Error('Netpbm: invalid header');
|
|
78
|
+
const kind = String.fromCharCode(d[1]);
|
|
79
|
+
if (kind === 'F' || kind === 'f')
|
|
80
|
+
return decodePFM(d, kind === 'F' ? 3 : 1, maxPixels, toneMapping);
|
|
81
|
+
let header;
|
|
82
|
+
const reader = new Reader(d, 2);
|
|
83
|
+
if (kind === '7')
|
|
84
|
+
header = pamHeader(d);
|
|
85
|
+
else {
|
|
86
|
+
const width = reader.integer('width'), height = reader.integer('height');
|
|
87
|
+
const bitmap = kind === '1' || kind === '4';
|
|
88
|
+
const maxval = bitmap ? 1 : reader.integer('maxval');
|
|
89
|
+
if (kind >= '4' && reader.o >= d.length)
|
|
90
|
+
throw new Error('Netpbm: truncated header');
|
|
91
|
+
header = { width, height, depth: kind === '3' || kind === '6' ? 3 : 1, maxval, tupleType: bitmap ? 'PBM' : '', data: reader.o + 1 };
|
|
92
|
+
}
|
|
93
|
+
const { width, height, depth, maxval } = header;
|
|
94
|
+
if (!width || !height)
|
|
95
|
+
throw new Error(`Netpbm: invalid dimensions ${width}x${height}`);
|
|
96
|
+
if (!maxval || maxval > 65535)
|
|
97
|
+
throw new Error(`Netpbm: invalid maxval ${maxval}`);
|
|
98
|
+
if (depth < 1 || depth > 4)
|
|
99
|
+
throw new Error(`PAM: unsupported depth ${depth}`);
|
|
100
|
+
checkPixels(width, height, maxPixels);
|
|
101
|
+
const count = width * height, samples = new Uint8Array(count * depth);
|
|
102
|
+
const scale = (v) => v >= maxval ? 255 : Math.round(v * 255 / maxval);
|
|
103
|
+
if (kind === '1' || kind === '2' || kind === '3') {
|
|
104
|
+
let o = header.data - 1;
|
|
105
|
+
for (let i = 0; i < samples.length; i++) {
|
|
106
|
+
while (o < d.length && (isSpace(d[o]) || d[o] === 0x23)) {
|
|
107
|
+
if (d[o] === 0x23)
|
|
108
|
+
while (o < d.length && d[o] !== 0x0a)
|
|
109
|
+
o++;
|
|
110
|
+
else
|
|
111
|
+
o++;
|
|
112
|
+
}
|
|
113
|
+
if (o >= d.length)
|
|
114
|
+
throw new Error('Netpbm: truncated image data');
|
|
115
|
+
let v = 0;
|
|
116
|
+
if (kind === '1')
|
|
117
|
+
v = d[o++] - 0x30;
|
|
118
|
+
else {
|
|
119
|
+
const start = o;
|
|
120
|
+
while (o < d.length && d[o] >= 0x30 && d[o] <= 0x39 && o - start < 6)
|
|
121
|
+
v = v * 10 + d[o++] - 0x30;
|
|
122
|
+
if (start === o)
|
|
123
|
+
throw new Error('Netpbm: invalid sample');
|
|
124
|
+
}
|
|
125
|
+
if (v < 0 || v > maxval)
|
|
126
|
+
throw new Error('Netpbm: invalid sample');
|
|
127
|
+
samples[i] = kind === '1' ? (v ? 0 : 255) : scale(v);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else if (kind === '4') {
|
|
131
|
+
const stride = width + 7 >> 3;
|
|
132
|
+
if (header.data + stride * height > d.length)
|
|
133
|
+
throw new Error('Netpbm: truncated image data');
|
|
134
|
+
for (let y = 0; y < height; y++)
|
|
135
|
+
for (let x = 0; x < width; x++)
|
|
136
|
+
samples[y * width + x] = d[header.data + y * stride + (x >> 3)] >> (7 - (x & 7)) & 1 ? 0 : 255;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const wide = maxval > 255;
|
|
140
|
+
if (header.data + samples.length * (wide ? 2 : 1) > d.length)
|
|
141
|
+
throw new Error('Netpbm: truncated image data');
|
|
142
|
+
for (let i = 0, o = header.data; i < samples.length; i++, o += wide ? 2 : 1)
|
|
143
|
+
samples[i] = scale(wide ? d[o] << 8 | d[o + 1] : d[o]);
|
|
144
|
+
}
|
|
145
|
+
const pixels = new Uint8Array(count * 4);
|
|
146
|
+
for (let i = 0; i < count; i++) {
|
|
147
|
+
const s = i * depth, p = i * 4;
|
|
148
|
+
if (depth < 3) {
|
|
149
|
+
pixels[p] = pixels[p + 1] = pixels[p + 2] = samples[s];
|
|
150
|
+
pixels[p + 3] = depth === 2 ? samples[s + 1] : 255;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
pixels[p] = samples[s];
|
|
154
|
+
pixels[p + 1] = samples[s + 1];
|
|
155
|
+
pixels[p + 2] = samples[s + 2];
|
|
156
|
+
pixels[p + 3] = depth === 4 ? samples[s + 3] : 255;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { width, height, pixels };
|
|
160
|
+
}
|
|
161
|
+
function decodePFM(d, channels, maxPixels, toneMapping) {
|
|
162
|
+
const reader = new Reader(d, 2);
|
|
163
|
+
const width = reader.integer('width'), height = reader.integer('height');
|
|
164
|
+
const scale = +reader.token();
|
|
165
|
+
if (!width || !height)
|
|
166
|
+
throw new Error(`PFM: invalid dimensions ${width}x${height}`);
|
|
167
|
+
if (!Number.isFinite(scale) || !scale)
|
|
168
|
+
throw new Error('PFM: invalid scale');
|
|
169
|
+
checkPixels(width, height, maxPixels);
|
|
170
|
+
const start = reader.o + 1;
|
|
171
|
+
if (start + width * height * channels * 4 > d.length)
|
|
172
|
+
throw new Error('PFM: truncated image data');
|
|
173
|
+
const view = new DataView(d.buffer, d.byteOffset + start);
|
|
174
|
+
const little = scale < 0;
|
|
175
|
+
const rgb = new Float32Array(width * height * 3);
|
|
176
|
+
for (let y = 0; y < height; y++) {
|
|
177
|
+
for (let x = 0; x < width; x++) {
|
|
178
|
+
const s = ((height - 1 - y) * width + x) * channels * 4, p = (y * width + x) * 3;
|
|
179
|
+
for (let c = 0; c < 3; c++)
|
|
180
|
+
rgb[p + c] = view.getFloat32(s + (channels === 3 ? c * 4 : 0), little);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return { width, height, pixels: toSRGB(rgb, toneMapping) };
|
|
184
|
+
}
|
|
185
|
+
export function encode(rgba, width, height) {
|
|
186
|
+
if (width < 1 || height < 1)
|
|
187
|
+
throw new RangeError('Netpbm: dimensions must be at least 1');
|
|
188
|
+
const count = width * height;
|
|
189
|
+
let opaque = true, grey = true, bitmap = true;
|
|
190
|
+
for (let p = 0; p < count * 4; p += 4) {
|
|
191
|
+
const r = rgba[p], g = rgba[p + 1], b = rgba[p + 2], a = rgba[p + 3];
|
|
192
|
+
if (a !== 255)
|
|
193
|
+
opaque = false;
|
|
194
|
+
if (r !== g || g !== b)
|
|
195
|
+
grey = false;
|
|
196
|
+
if (r !== 0 && r !== 255 || r !== g || g !== b)
|
|
197
|
+
bitmap = false;
|
|
198
|
+
if (!opaque && !grey)
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
const ascii = (text) => Uint8Array.from(text, c => c.charCodeAt(0));
|
|
202
|
+
let header, depth;
|
|
203
|
+
if (!opaque) {
|
|
204
|
+
depth = grey ? 2 : 4;
|
|
205
|
+
header = ascii(`P7\nWIDTH ${width}\nHEIGHT ${height}\nDEPTH ${depth}\nMAXVAL 255\nTUPLTYPE ${grey ? 'GRAYSCALE_ALPHA' : 'RGB_ALPHA'}\nENDHDR\n`);
|
|
206
|
+
}
|
|
207
|
+
else if (bitmap) {
|
|
208
|
+
depth = 0;
|
|
209
|
+
header = ascii(`P4\n${width} ${height}\n`);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
depth = grey ? 1 : 3;
|
|
213
|
+
header = ascii(`P${grey ? 5 : 6}\n${width} ${height}\n255\n`);
|
|
214
|
+
}
|
|
215
|
+
const stride = depth ? width * depth : width + 7 >> 3;
|
|
216
|
+
const out = new Uint8Array(header.length + stride * height);
|
|
217
|
+
out.set(header);
|
|
218
|
+
let o = header.length;
|
|
219
|
+
if (!depth) {
|
|
220
|
+
for (let y = 0; y < height; y++, o += stride)
|
|
221
|
+
for (let x = 0; x < width; x++)
|
|
222
|
+
if (!rgba[(y * width + x) * 4])
|
|
223
|
+
out[o + (x >> 3)] |= 128 >> (x & 7);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
for (let p = 0; p < count * 4; p += 4) {
|
|
227
|
+
if (depth === 1 || depth === 2)
|
|
228
|
+
out[o++] = rgba[p];
|
|
229
|
+
else {
|
|
230
|
+
out[o++] = rgba[p];
|
|
231
|
+
out[o++] = rgba[p + 1];
|
|
232
|
+
out[o++] = rgba[p + 2];
|
|
233
|
+
}
|
|
234
|
+
if (depth === 2 || depth === 4)
|
|
235
|
+
out[o++] = rgba[p + 3];
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return out;
|
|
239
|
+
}
|
package/dist/codecs/png.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { crc32 } from "./crc.js";
|
|
2
|
+
import { MAX_PIXELS, checkPixels } from "../util/limits.js";
|
|
2
3
|
import { concat } from "../util/mem.js";
|
|
3
4
|
import { encode as utf8, decode as utf8Decode } from "../util/utf8.js";
|
|
4
5
|
import { orientation as exifOrientation, xmpOrientation } from "./exif.js";
|
|
@@ -437,15 +438,16 @@ async function decodeImageData(p, chunks, width, height) {
|
|
|
437
438
|
continue;
|
|
438
439
|
const rowBytes = Math.ceil(pw * bitsPerPixel / 8);
|
|
439
440
|
const size = ph * rowBytes;
|
|
440
|
-
const pixels = unfilter(data, dataOffset, ph, rowBytes, bpp);
|
|
441
|
+
const pixels = unfilter(data, dataOffset, ph, rowBytes, bpp, !interlace);
|
|
441
442
|
dataOffset += size + ph;
|
|
442
443
|
toRGBA(pixels, rowBytes, pw, ph, rgba, { row: y => ((ys + y * dy) * width + xs) * 4, step: dx * 4 });
|
|
443
444
|
}
|
|
444
445
|
return rgba;
|
|
445
446
|
}
|
|
446
|
-
export async function decode(array) {
|
|
447
|
+
export async function decode(array, maxPixels = MAX_PIXELS) {
|
|
447
448
|
const p = parse(array);
|
|
448
449
|
const { width, height } = p.header;
|
|
450
|
+
checkPixels(width, height, maxPixels);
|
|
449
451
|
const pixels = await decodeImageData(p, p.idat, width, height);
|
|
450
452
|
return { width, height, pixels, orientation: p.exif ? exifOrientation(p.exif) : await textOrientation(p.text) };
|
|
451
453
|
}
|
|
@@ -458,9 +460,10 @@ export function isAnimated(array) {
|
|
|
458
460
|
return false;
|
|
459
461
|
}
|
|
460
462
|
}
|
|
461
|
-
export async function decodeAnimation(array, onlyFirstFrame = false) {
|
|
463
|
+
export async function decodeAnimation(array, onlyFirstFrame = false, maxPixels = MAX_PIXELS) {
|
|
462
464
|
const p = parse(array);
|
|
463
465
|
const { width, height } = p.header;
|
|
466
|
+
checkPixels(width, height, maxPixels, !p.animation || p.animationError || onlyFirstFrame ? 1 : Math.max(1, p.frames.length));
|
|
464
467
|
if (!p.animation || p.animationError) {
|
|
465
468
|
const pixels = await decodeImageData(p, p.idat, width, height);
|
|
466
469
|
return { width, height, plays: 0, frames: [{ x: 0, y: 0, width, height, duration: 0, dispose: 0, blend: 0, pixels }] };
|
|
@@ -485,13 +488,21 @@ export function compositeAnimation({ width, height, frames }) {
|
|
|
485
488
|
const output = [];
|
|
486
489
|
for (const f of frames) {
|
|
487
490
|
const previous = f.dispose === 2 ? canvas.slice() : null;
|
|
488
|
-
for (let y = 0; y < f.height; y++)
|
|
491
|
+
for (let y = 0; y < f.height; y++) {
|
|
492
|
+
if (f.blend === 0) {
|
|
493
|
+
canvas.set(f.pixels.subarray(y * f.width * 4, (y + 1) * f.width * 4), ((f.y + y) * width + f.x) * 4);
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
489
496
|
for (let x = 0; x < f.width; x++) {
|
|
490
497
|
const s = (y * f.width + x) * 4, d = ((f.y + y) * width + f.x + x) * 4;
|
|
491
498
|
const sa = f.pixels[s + 3], da = canvas[d + 3];
|
|
492
|
-
if (
|
|
493
|
-
if (
|
|
494
|
-
canvas
|
|
499
|
+
if (sa === 255 || da === 0) {
|
|
500
|
+
if (sa !== 0) {
|
|
501
|
+
canvas[d] = f.pixels[s];
|
|
502
|
+
canvas[d + 1] = f.pixels[s + 1];
|
|
503
|
+
canvas[d + 2] = f.pixels[s + 2];
|
|
504
|
+
canvas[d + 3] = sa;
|
|
505
|
+
}
|
|
495
506
|
}
|
|
496
507
|
else if (sa !== 0) {
|
|
497
508
|
const u = sa * 255, v = (255 - sa) * da, al = u + v;
|
|
@@ -500,6 +511,7 @@ export function compositeAnimation({ width, height, frames }) {
|
|
|
500
511
|
canvas[d + 3] = al / 255 | 0;
|
|
501
512
|
}
|
|
502
513
|
}
|
|
514
|
+
}
|
|
503
515
|
output.push({ pixels: canvas.slice(), duration: f.duration });
|
|
504
516
|
if (f.dispose === 1)
|
|
505
517
|
for (let y = 0; y < f.height; y++)
|
|
@@ -550,11 +562,11 @@ function hexProfile(text) {
|
|
|
550
562
|
const digits = match[2].replace(/\s+/g, '');
|
|
551
563
|
const out = new Uint8Array(Math.min(+match[1], digits.length >> 1));
|
|
552
564
|
for (let i = 0; i < out.length; i++)
|
|
553
|
-
out[i] = parseInt(digits.
|
|
565
|
+
out[i] = parseInt(digits.slice(i * 2, i * 2 + 2), 16);
|
|
554
566
|
return out;
|
|
555
567
|
}
|
|
556
|
-
function unfilter(data, offset, height, rowBytes, bpp) {
|
|
557
|
-
const out = new Uint8Array(height * rowBytes);
|
|
568
|
+
function unfilter(data, offset, height, rowBytes, bpp, inPlace = false) {
|
|
569
|
+
const out = inPlace ? data.subarray(offset, offset + height * rowBytes) : new Uint8Array(height * rowBytes);
|
|
558
570
|
const first = Math.min(bpp, rowBytes);
|
|
559
571
|
for (let y = 0; y < height; y++) {
|
|
560
572
|
const filter = data[offset++];
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* The Quite OK Image format (https://qoiformat.org/qoi-specification.pdf). The encoder makes the choices of the
|
|
3
|
+
* reference encoder qoi.h, so it writes the same bytes. Copyright (c) 2021 Dominic Szablewski, MIT License (see
|
|
4
|
+
* THIRD_PARTY_NOTICES.md)
|
|
5
|
+
*/
|
|
6
|
+
import { MAX_PIXELS, checkPixels } from "../util/limits.js";
|
|
7
|
+
const OP_INDEX = 0x00, OP_DIFF = 0x40, OP_LUMA = 0x80, OP_RUN = 0xc0, OP_RGB = 0xfe, OP_RGBA = 0xff;
|
|
8
|
+
export function isQOI(d) {
|
|
9
|
+
return d.length >= 14 && d[0] === 0x71 && d[1] === 0x6f && d[2] === 0x69 && d[3] === 0x66;
|
|
10
|
+
}
|
|
11
|
+
const hash = (r, g, b, a) => (r * 3 + g * 5 + b * 7 + a * 11) & 63;
|
|
12
|
+
export function decode(d, maxPixels = MAX_PIXELS) {
|
|
13
|
+
if (!isQOI(d))
|
|
14
|
+
throw new Error('QOI: invalid header');
|
|
15
|
+
const view = new DataView(d.buffer, d.byteOffset, d.byteLength);
|
|
16
|
+
const width = view.getUint32(4), height = view.getUint32(8);
|
|
17
|
+
if (!width || !height)
|
|
18
|
+
throw new Error(`QOI: invalid dimensions ${width}x${height}`);
|
|
19
|
+
checkPixels(width, height, maxPixels);
|
|
20
|
+
const pixels = new Uint8Array(width * height * 4);
|
|
21
|
+
const index = new Uint8Array(256);
|
|
22
|
+
let r = 0, g = 0, b = 0, a = 255;
|
|
23
|
+
for (let p = 0, o = 14; p < pixels.length;) {
|
|
24
|
+
if (o >= d.length)
|
|
25
|
+
throw new Error('QOI: truncated image data');
|
|
26
|
+
const op = d[o++];
|
|
27
|
+
let run = 1;
|
|
28
|
+
if (op === OP_RGB) {
|
|
29
|
+
r = d[o];
|
|
30
|
+
g = d[o + 1];
|
|
31
|
+
b = d[o + 2];
|
|
32
|
+
o += 3;
|
|
33
|
+
}
|
|
34
|
+
else if (op === OP_RGBA) {
|
|
35
|
+
r = d[o];
|
|
36
|
+
g = d[o + 1];
|
|
37
|
+
b = d[o + 2];
|
|
38
|
+
a = d[o + 3];
|
|
39
|
+
o += 4;
|
|
40
|
+
}
|
|
41
|
+
else if ((op & 0xc0) === OP_INDEX) {
|
|
42
|
+
const i = op * 4;
|
|
43
|
+
r = index[i];
|
|
44
|
+
g = index[i + 1];
|
|
45
|
+
b = index[i + 2];
|
|
46
|
+
a = index[i + 3];
|
|
47
|
+
}
|
|
48
|
+
else if ((op & 0xc0) === OP_DIFF) {
|
|
49
|
+
r = r + (op >> 4 & 3) - 2 & 255;
|
|
50
|
+
g = g + (op >> 2 & 3) - 2 & 255;
|
|
51
|
+
b = b + (op & 3) - 2 & 255;
|
|
52
|
+
}
|
|
53
|
+
else if ((op & 0xc0) === OP_LUMA) {
|
|
54
|
+
const dg = (op & 63) - 32, next = d[o++];
|
|
55
|
+
r = r + dg - 8 + (next >> 4) & 255;
|
|
56
|
+
g = g + dg & 255;
|
|
57
|
+
b = b + dg - 8 + (next & 15) & 255;
|
|
58
|
+
}
|
|
59
|
+
else
|
|
60
|
+
run = (op & 63) + 1;
|
|
61
|
+
if (o > d.length)
|
|
62
|
+
throw new Error('QOI: truncated image data');
|
|
63
|
+
const i = hash(r, g, b, a) * 4;
|
|
64
|
+
index[i] = r;
|
|
65
|
+
index[i + 1] = g;
|
|
66
|
+
index[i + 2] = b;
|
|
67
|
+
index[i + 3] = a;
|
|
68
|
+
for (const stop = Math.min(pixels.length, p + run * 4); p < stop; p += 4) {
|
|
69
|
+
pixels[p] = r;
|
|
70
|
+
pixels[p + 1] = g;
|
|
71
|
+
pixels[p + 2] = b;
|
|
72
|
+
pixels[p + 3] = a;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { width, height, pixels };
|
|
76
|
+
}
|
|
77
|
+
export function encode(rgba, width, height) {
|
|
78
|
+
if (width < 1 || height < 1)
|
|
79
|
+
throw new RangeError('QOI: dimensions must be at least 1');
|
|
80
|
+
const count = width * height;
|
|
81
|
+
let opaque = true;
|
|
82
|
+
for (let i = 3; i < count * 4; i += 4)
|
|
83
|
+
if (rgba[i] !== 255) {
|
|
84
|
+
opaque = false;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
const out = new Uint8Array(14 + count * (opaque ? 4 : 5) + 8);
|
|
88
|
+
const view = new DataView(out.buffer);
|
|
89
|
+
out.set([0x71, 0x6f, 0x69, 0x66]);
|
|
90
|
+
view.setUint32(4, width);
|
|
91
|
+
view.setUint32(8, height);
|
|
92
|
+
out[12] = opaque ? 3 : 4;
|
|
93
|
+
out[13] = 0;
|
|
94
|
+
const index = new Uint8Array(256);
|
|
95
|
+
let o = 14, run = 0;
|
|
96
|
+
let pr = 0, pg = 0, pb = 0, pa = 255;
|
|
97
|
+
for (let p = 0, last = count * 4 - 4; p <= last; p += 4) {
|
|
98
|
+
const r = rgba[p], g = rgba[p + 1], b = rgba[p + 2], a = rgba[p + 3];
|
|
99
|
+
if (r === pr && g === pg && b === pb && a === pa) {
|
|
100
|
+
run++;
|
|
101
|
+
if (run === 62 || p === last) {
|
|
102
|
+
out[o++] = OP_RUN | run - 1;
|
|
103
|
+
run = 0;
|
|
104
|
+
}
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (run) {
|
|
108
|
+
out[o++] = OP_RUN | run - 1;
|
|
109
|
+
run = 0;
|
|
110
|
+
}
|
|
111
|
+
const h = hash(r, g, b, a), i = h * 4;
|
|
112
|
+
if (index[i] === r && index[i + 1] === g && index[i + 2] === b && index[i + 3] === a)
|
|
113
|
+
out[o++] = OP_INDEX | h;
|
|
114
|
+
else {
|
|
115
|
+
index[i] = r;
|
|
116
|
+
index[i + 1] = g;
|
|
117
|
+
index[i + 2] = b;
|
|
118
|
+
index[i + 3] = a;
|
|
119
|
+
if (a === pa) {
|
|
120
|
+
const vr = (r - pr) << 24 >> 24, vg = (g - pg) << 24 >> 24, vb = (b - pb) << 24 >> 24;
|
|
121
|
+
const vgr = vr - vg, vgb = vb - vg;
|
|
122
|
+
if (vr > -3 && vr < 2 && vg > -3 && vg < 2 && vb > -3 && vb < 2)
|
|
123
|
+
out[o++] = OP_DIFF | vr + 2 << 4 | vg + 2 << 2 | vb + 2;
|
|
124
|
+
else if (vgr > -9 && vgr < 8 && vg > -33 && vg < 32 && vgb > -9 && vgb < 8) {
|
|
125
|
+
out[o++] = OP_LUMA | vg + 32;
|
|
126
|
+
out[o++] = vgr + 8 << 4 | vgb + 8;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
out[o++] = OP_RGB;
|
|
130
|
+
out[o++] = r;
|
|
131
|
+
out[o++] = g;
|
|
132
|
+
out[o++] = b;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
out[o++] = OP_RGBA;
|
|
137
|
+
out[o++] = r;
|
|
138
|
+
out[o++] = g;
|
|
139
|
+
out[o++] = b;
|
|
140
|
+
out[o++] = a;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
pr = r;
|
|
144
|
+
pg = g;
|
|
145
|
+
pb = b;
|
|
146
|
+
pa = a;
|
|
147
|
+
}
|
|
148
|
+
out[o + 7] = 1;
|
|
149
|
+
return out.slice(0, o + 8);
|
|
150
|
+
}
|
package/dist/codecs/tga.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { encode as utf8 } from "../util/utf8.js";
|
|
2
|
+
import { MAX_PIXELS, checkPixels } from "../util/limits.js";
|
|
2
3
|
const SIGNATURE = 'TRUEVISION-XFILE.\0';
|
|
3
4
|
function readHeader(d) {
|
|
4
5
|
if (d.length < 18)
|
|
@@ -69,11 +70,12 @@ function readColor(d, o, bits, alphaBits, out, p) {
|
|
|
69
70
|
out[p + 3] = bits === 32 && alphaBits ? d[o + 3] : 255;
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
|
-
export function decode(d) {
|
|
73
|
+
export function decode(d, maxPixels = MAX_PIXELS) {
|
|
73
74
|
const h = readHeader(d);
|
|
74
75
|
if (!h || !isTGA(d))
|
|
75
76
|
throw new Error('TGA: invalid header');
|
|
76
77
|
const { width, height, depth } = h;
|
|
78
|
+
checkPixels(width, height, maxPixels);
|
|
77
79
|
const base = h.imageType & ~8, rle = (h.imageType & 8) !== 0;
|
|
78
80
|
const bytesPerPixel = Math.ceil(depth / 8);
|
|
79
81
|
let offset = 18 + h.idLength;
|
package/dist/codecs/tiff.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { decompressAsync } from "./zlib.js";
|
|
2
2
|
import * as jpeg from "./jpeg.js";
|
|
3
|
+
import { MAX_PIXELS, checkPixels, checkBytes } from "../util/limits.js";
|
|
3
4
|
const TYPE_SIZES = { 1: 1, 2: 1, 3: 2, 4: 4, 5: 8, 6: 1, 7: 1, 8: 2, 9: 4, 10: 8, 11: 4, 12: 8, 13: 4, 16: 8, 17: 8, 18: 8 };
|
|
4
5
|
const TAGS = {
|
|
5
6
|
ImageWidth: 256,
|
|
@@ -28,7 +29,7 @@ const TAGS = {
|
|
|
28
29
|
ReferenceBlackWhite: 532
|
|
29
30
|
};
|
|
30
31
|
const COMPRESSION_NAMES = { 2: 'CCITT RLE', 3: 'CCITT fax 3', 4: 'CCITT fax 4', 6: 'old-style JPEG', 32809: 'ThunderScan', 34676: 'SGI LogLuv', 34677: 'SGI LogL' };
|
|
31
|
-
export async function decode(data) {
|
|
32
|
+
export async function decode(data, maxPixels = MAX_PIXELS) {
|
|
32
33
|
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
33
34
|
let little;
|
|
34
35
|
if (data[0] === 0x49 && data[1] === 0x49)
|
|
@@ -154,9 +155,13 @@ export async function decode(data) {
|
|
|
154
155
|
throw new Error('TIFF: missing image data');
|
|
155
156
|
return data.subarray(offset, Math.min(data.length, offset + count));
|
|
156
157
|
};
|
|
158
|
+
checkPixels(width, height, maxPixels);
|
|
159
|
+
checkPixels(segmentWidth, segmentHeight, Math.max(4 * width * height, 1 << 20));
|
|
160
|
+
checkBytes(Math.ceil(width * samplesPerPixel * bps / 8) * height, maxPixels);
|
|
157
161
|
const pixels = new Uint8Array(width * height * 4);
|
|
158
162
|
if (compression === 7) {
|
|
159
|
-
const
|
|
163
|
+
const jpegTables = tags.get(TAGS.JPEGTables);
|
|
164
|
+
const tables = jpegTables ? data.subarray(jpegTables.offset, jpegTables.offset + jpegTables.count) : null;
|
|
160
165
|
const transform = photometric === 6 ? 'ycc' : photometric === 2 ? 'rgb' : photometric === 5 ? 'cmyk' : null;
|
|
161
166
|
for (let s = 0; s < segmentsPerPlane; s++) {
|
|
162
167
|
let stream = segment(s);
|