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.
Files changed (101) hide show
  1. package/README.md +35 -10
  2. package/THIRD_PARTY_NOTICES.md +298 -2
  3. package/dist/animation.d.ts +25 -8
  4. package/dist/animation.js +44 -13
  5. package/dist/codecs/av1/cdef.js +197 -0
  6. package/dist/codecs/av1/data.js +141 -0
  7. package/dist/codecs/av1/decode.js +1769 -0
  8. package/dist/codecs/av1/filmgrain.js +219 -0
  9. package/dist/codecs/av1/frame.js +114 -0
  10. package/dist/codecs/av1/ipred.js +491 -0
  11. package/dist/codecs/av1/itx.js +950 -0
  12. package/dist/codecs/av1/loopfilter.js +149 -0
  13. package/dist/codecs/av1/msac.js +161 -0
  14. package/dist/codecs/av1/obu.js +516 -0
  15. package/dist/codecs/av1/postfilter.js +28 -0
  16. package/dist/codecs/av1/restoration.js +193 -0
  17. package/dist/codecs/av1/tables.js +160 -0
  18. package/dist/codecs/avif.js +581 -0
  19. package/dist/codecs/bmp.js +317 -0
  20. package/dist/codecs/crc.js +5 -0
  21. package/dist/codecs/gif.js +10 -35
  22. package/dist/codecs/hdr.d.ts +25 -0
  23. package/dist/codecs/hdr.js +201 -0
  24. package/dist/codecs/ico.js +48 -0
  25. package/dist/codecs/jpeg.js +11 -28
  26. package/dist/codecs/jxl/bits.js +126 -0
  27. package/dist/codecs/jxl/blend.js +117 -0
  28. package/dist/codecs/jxl/color.js +116 -0
  29. package/dist/codecs/jxl/dct.js +404 -0
  30. package/dist/codecs/jxl/entropy.js +556 -0
  31. package/dist/codecs/jxl/features.js +325 -0
  32. package/dist/codecs/jxl/filters.js +119 -0
  33. package/dist/codecs/jxl/frame-header.js +195 -0
  34. package/dist/codecs/jxl/frame.js +319 -0
  35. package/dist/codecs/jxl/headers.js +249 -0
  36. package/dist/codecs/jxl/idct.js +484 -0
  37. package/dist/codecs/jxl/modular.js +688 -0
  38. package/dist/codecs/jxl/quant.js +219 -0
  39. package/dist/codecs/jxl/upsample.js +52 -0
  40. package/dist/codecs/jxl/vardct.js +472 -0
  41. package/dist/codecs/jxl.js +89 -0
  42. package/dist/codecs/netpbm.js +239 -0
  43. package/dist/codecs/png.js +22 -10
  44. package/dist/codecs/qoi.js +150 -0
  45. package/dist/codecs/tga.js +3 -1
  46. package/dist/codecs/tiff.js +7 -2
  47. package/dist/codecs/vp8-enc-dsp.js +932 -0
  48. package/dist/codecs/vp8-enc-tables.js +78 -0
  49. package/dist/codecs/vp8-enc.js +1930 -0
  50. package/dist/codecs/vp8.js +3 -1
  51. package/dist/codecs/vp8l.js +126 -58
  52. package/dist/codecs/webp.js +47 -11
  53. package/dist/codecs/zlib.js +81 -69
  54. package/dist/frame.d.ts +1 -0
  55. package/dist/frame.js +4 -2
  56. package/dist/gif.d.ts +1 -0
  57. package/dist/gif.js +2 -2
  58. package/dist/image-type.d.ts +15 -1
  59. package/dist/image-type.js +45 -2
  60. package/dist/image.d.ts +89 -13
  61. package/dist/image.js +266 -113
  62. package/dist/index.d.ts +3 -2
  63. package/dist/index.js +4 -4
  64. package/dist/render/bidi.js +350 -0
  65. package/dist/render/buffer.js +362 -0
  66. package/dist/render/color.js +164 -0
  67. package/dist/render/colr.js +523 -0
  68. package/dist/render/font.js +173 -241
  69. package/dist/render/hinting.js +38 -22
  70. package/dist/render/layout.js +1553 -0
  71. package/dist/render/linebreak.js +210 -0
  72. package/dist/render/segment.js +106 -0
  73. package/dist/render/shape.js +984 -0
  74. package/dist/render/shapers/arabic.js +284 -0
  75. package/dist/render/shapers/hangul.js +142 -0
  76. package/dist/render/shapers/hebrew.js +49 -0
  77. package/dist/render/shapers/index.js +52 -0
  78. package/dist/render/shapers/indic.js +579 -0
  79. package/dist/render/shapers/khmer.js +104 -0
  80. package/dist/render/shapers/machine.js +129 -0
  81. package/dist/render/shapers/myanmar.js +121 -0
  82. package/dist/render/shapers/syllabic.js +100 -0
  83. package/dist/render/shapers/thai.js +125 -0
  84. package/dist/render/shapers/use.js +235 -0
  85. package/dist/render/svg.js +102 -23
  86. package/dist/render/text.js +373 -103
  87. package/dist/render/unicode-data.js +63 -0
  88. package/dist/render/unicode.js +50 -0
  89. package/dist/text-layout.d.ts +4 -1
  90. package/dist/text-layout.js +2 -2
  91. package/dist/util/alpha.js +43 -0
  92. package/dist/util/blur.js +20 -33
  93. package/dist/util/bytes.js +40 -0
  94. package/dist/util/limits.js +34 -0
  95. package/dist/util/options.js +7 -0
  96. package/dist/util/resample.js +126 -7
  97. package/dist/util/rotate.js +43 -0
  98. package/dist/version.js +1 -1
  99. package/licenses/HarfBuzz.txt +42 -0
  100. package/licenses/Unicode-3.0.txt +39 -0
  101. 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
+ }
@@ -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 (f.blend === 0 || sa === 255 || da === 0) {
493
- if (f.blend === 0 || sa !== 0)
494
- canvas.set(f.pixels.subarray(s, s + 4), d);
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.substr(i * 2, 2), 16);
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
+ }
@@ -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;
@@ -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 tables = tags.has(TAGS.JPEGTables) ? data.subarray(tags.get(TAGS.JPEGTables).offset, tags.get(TAGS.JPEGTables).offset + tags.get(TAGS.JPEGTables).count) : null;
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);