imagescript 3.2.0 → 3.3.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 (59) hide show
  1. package/README.md +8 -3
  2. package/THIRD_PARTY_NOTICES.md +131 -0
  3. package/dist/animation.d.ts +7 -0
  4. package/dist/animation.js +22 -12
  5. package/dist/codecs/crc.js +5 -0
  6. package/dist/codecs/gif.js +30 -52
  7. package/dist/codecs/jpeg.js +9 -27
  8. package/dist/codecs/png.js +22 -10
  9. package/dist/codecs/tga.js +3 -1
  10. package/dist/codecs/tiff.js +7 -2
  11. package/dist/codecs/vp8.js +3 -1
  12. package/dist/codecs/vp8l.js +126 -58
  13. package/dist/codecs/webp.js +11 -9
  14. package/dist/codecs/zlib.js +81 -69
  15. package/dist/frame.d.ts +1 -0
  16. package/dist/frame.js +4 -2
  17. package/dist/gif.d.ts +1 -0
  18. package/dist/gif.js +2 -2
  19. package/dist/image.d.ts +42 -7
  20. package/dist/image.js +224 -113
  21. package/dist/index.js +4 -4
  22. package/dist/render/bidi.js +350 -0
  23. package/dist/render/buffer.js +362 -0
  24. package/dist/render/color.js +164 -0
  25. package/dist/render/colr.js +523 -0
  26. package/dist/render/font.js +173 -241
  27. package/dist/render/hinting.js +12 -4
  28. package/dist/render/layout.js +1553 -0
  29. package/dist/render/linebreak.js +210 -0
  30. package/dist/render/segment.js +106 -0
  31. package/dist/render/shape.js +984 -0
  32. package/dist/render/shapers/arabic.js +284 -0
  33. package/dist/render/shapers/hangul.js +142 -0
  34. package/dist/render/shapers/hebrew.js +49 -0
  35. package/dist/render/shapers/index.js +52 -0
  36. package/dist/render/shapers/indic.js +579 -0
  37. package/dist/render/shapers/khmer.js +104 -0
  38. package/dist/render/shapers/machine.js +129 -0
  39. package/dist/render/shapers/myanmar.js +121 -0
  40. package/dist/render/shapers/syllabic.js +100 -0
  41. package/dist/render/shapers/thai.js +125 -0
  42. package/dist/render/shapers/use.js +235 -0
  43. package/dist/render/svg.js +101 -22
  44. package/dist/render/text.js +373 -103
  45. package/dist/render/unicode-data.js +63 -0
  46. package/dist/render/unicode.js +58 -0
  47. package/dist/text-layout.d.ts +4 -1
  48. package/dist/text-layout.js +2 -2
  49. package/dist/util/alpha.js +43 -0
  50. package/dist/util/blur.js +80 -5
  51. package/dist/util/bytes.js +31 -0
  52. package/dist/util/limits.js +34 -0
  53. package/dist/util/options.js +7 -0
  54. package/dist/util/resample.js +359 -79
  55. package/dist/util/rotate.js +43 -0
  56. package/dist/version.js +1 -1
  57. package/licenses/HarfBuzz.txt +42 -0
  58. package/licenses/Unicode-3.0.txt +39 -0
  59. package/package.json +1 -1
@@ -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);
@@ -1,4 +1,5 @@
1
1
  import { KF_BMODE_PROBS, COEFF_UPDATE_PROBS, DEFAULT_COEFF_PROBS, DC_QLOOKUP, AC_QLOOKUP, PCAT } from "./vp8-tables.js";
2
+ import { MAX_PIXELS, checkPixels } from "../util/limits.js";
2
3
  class BoolDecoder {
3
4
  data;
4
5
  pos;
@@ -161,9 +162,10 @@ function readHeader(data) {
161
162
  };
162
163
  }
163
164
  const clamp255 = (v) => v < 0 ? 0 : v > 255 ? 255 : v;
164
- export function decode(data) {
165
+ export function decode(data, maxPixels = MAX_PIXELS) {
165
166
  const { header: h, first: d } = readHeader(data);
166
167
  const { width, height } = h;
168
+ checkPixels(width, height, maxPixels);
167
169
  const mbw = (width + 15) >> 4, mbh = (height + 15) >> 4;
168
170
  const ys = mbw * 16 + 1, cs = mbw * 8 + 1;
169
171
  const Y = new Uint8Array(ys * (mbh * 16 + 1));
@@ -3,6 +3,8 @@
3
3
  * table of short distance codes. Copyright (c) 2024 IETF Trust and the persons identified as the document authors,
4
4
  * Revised BSD License (see THIRD_PARTY_NOTICES.md)
5
5
  */
6
+ import { ByteWriter } from "../util/bytes.js";
7
+ import { MAX_PIXELS, checkPixels } from "../util/limits.js";
6
8
  const SIGNATURE = 0x2f;
7
9
  const CODE_LENGTH_ORDER = [17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
8
10
  const DISTANCE_MAP = Int8Array.of(0, 1, 1, 0, 1, 1, -1, 1, 0, 2, 2, 0, 1, 2, -1, 2, 2, 1, -2, 1, 2, 2, -2, 2, 0, 3, 3, 0, 1, 3, -1, 3, 3, 1, -3, 1, 2, 3, -2, 3, 3, 2, -3, 2, 0, 4, 4, 0, 1, 4, -1, 4, 4, 1, -4, 1, 3, 3, -3, 3, 2, 4, -2, 4, 4, 2, -4, 2, 0, 5, 3, 4, -3, 4, 4, 3, -4, 3, 5, 0, 1, 5, -1, 5, 5, 1, -5, 1, 2, 5, -2, 5, 5, 2, -5, 2, 4, 4, -4, 4, 3, 5, -3, 5, 5, 3, -5, 3, 0, 6, 6, 0, 1, 6, -1, 6, 6, 1, -6, 1, 2, 6, -2, 6, 6, 2, -6, 2, 4, 5, -4, 5, 5, 4, -5, 4, 3, 6, -3, 6, 6, 3, -6, 3, 0, 7, 7, 0, 1, 7, -1, 7, 5, 5, -5, 5, 7, 1, -7, 1, 4, 6, -4, 6, 6, 4, -6, 4, 2, 7, -2, 7, 7, 2, -7, 2, 3, 7, -3, 7, 7, 3, -7, 3, 5, 6, -5, 6, 6, 5, -6, 5, 8, 0, 4, 7, -4, 7, 7, 4, -7, 4, 8, 1, 8, 2, 6, 6, -6, 6, 8, 3, 5, 7, -5, 7, 7, 5, -7, 5, 8, 4, 6, 7, -6, 7, 7, 6, -7, 6, 8, 5, 7, 7, -7, 7, 8, 6, 8, 7);
@@ -314,24 +316,82 @@ function select(l, t, tl) {
314
316
  return pl < pt ? l : t;
315
317
  }
316
318
  const addPixels = (a, b) => ((a & 0xff00ff00) + (b & 0xff00ff00) & 0xff00ff00 | (a & 0x00ff00ff) + (b & 0x00ff00ff) & 0x00ff00ff) >>> 0;
317
- function predict(mode, p, i, width) {
318
- const l = p[i - 1], t = p[i - width], tl = p[i - width - 1], tr = p[i - width + 1];
319
+ function predictRun(mode, p, i, n, width, out, o) {
320
+ const restore = out === p;
321
+ const end = i + n;
319
322
  switch (mode) {
320
- case 0: return 0xff000000;
321
- case 1: return l;
322
- case 2: return t;
323
- case 3: return tr;
324
- case 4: return tl;
325
- case 5: return average2(average2(l, tr), t);
326
- case 6: return average2(l, tl);
327
- case 7: return average2(l, t);
328
- case 8: return average2(tl, t);
329
- case 9: return average2(t, tr);
330
- case 10: return average2(average2(l, tl), average2(t, tr));
331
- case 11: return select(l, t, tl);
332
- case 12: return clampAddSubtractFull(l, t, tl);
333
- case 13: return clampAddSubtractHalf(average2(l, t), tl);
334
- default: return 0xff000000;
323
+ case 1:
324
+ for (; i < end; i++, o++)
325
+ out[o] = restore ? addPixels(p[i], p[i - 1]) : p[i - 1];
326
+ return;
327
+ case 2:
328
+ for (; i < end; i++, o++)
329
+ out[o] = restore ? addPixels(p[i], p[i - width]) : p[i - width];
330
+ return;
331
+ case 3:
332
+ for (; i < end; i++, o++)
333
+ out[o] = restore ? addPixels(p[i], p[i - width + 1]) : p[i - width + 1];
334
+ return;
335
+ case 4:
336
+ for (; i < end; i++, o++)
337
+ out[o] = restore ? addPixels(p[i], p[i - width - 1]) : p[i - width - 1];
338
+ return;
339
+ case 5:
340
+ for (; i < end; i++, o++) {
341
+ const v = average2(average2(p[i - 1], p[i - width + 1]), p[i - width]);
342
+ out[o] = restore ? addPixels(p[i], v) : v;
343
+ }
344
+ return;
345
+ case 6:
346
+ for (; i < end; i++, o++) {
347
+ const v = average2(p[i - 1], p[i - width - 1]);
348
+ out[o] = restore ? addPixels(p[i], v) : v;
349
+ }
350
+ return;
351
+ case 7:
352
+ for (; i < end; i++, o++) {
353
+ const v = average2(p[i - 1], p[i - width]);
354
+ out[o] = restore ? addPixels(p[i], v) : v;
355
+ }
356
+ return;
357
+ case 8:
358
+ for (; i < end; i++, o++) {
359
+ const v = average2(p[i - width - 1], p[i - width]);
360
+ out[o] = restore ? addPixels(p[i], v) : v;
361
+ }
362
+ return;
363
+ case 9:
364
+ for (; i < end; i++, o++) {
365
+ const v = average2(p[i - width], p[i - width + 1]);
366
+ out[o] = restore ? addPixels(p[i], v) : v;
367
+ }
368
+ return;
369
+ case 10:
370
+ for (; i < end; i++, o++) {
371
+ const v = average2(average2(p[i - 1], p[i - width - 1]), average2(p[i - width], p[i - width + 1]));
372
+ out[o] = restore ? addPixels(p[i], v) : v;
373
+ }
374
+ return;
375
+ case 11:
376
+ for (; i < end; i++, o++) {
377
+ const v = select(p[i - 1], p[i - width], p[i - width - 1]);
378
+ out[o] = restore ? addPixels(p[i], v) : v;
379
+ }
380
+ return;
381
+ case 12:
382
+ for (; i < end; i++, o++) {
383
+ const v = clampAddSubtractFull(p[i - 1], p[i - width], p[i - width - 1]);
384
+ out[o] = restore ? addPixels(p[i], v) : v;
385
+ }
386
+ return;
387
+ case 13:
388
+ for (; i < end; i++, o++) {
389
+ const v = clampAddSubtractHalf(average2(p[i - 1], p[i - width]), p[i - width - 1]);
390
+ out[o] = restore ? addPixels(p[i], v) : v;
391
+ }
392
+ return;
393
+ default: for (; i < end; i++, o++)
394
+ out[o] = restore ? addPixels(p[i], 0xff000000) : 0xff000000;
335
395
  }
336
396
  }
337
397
  function inversePredictor(p, width, height, bits, modes) {
@@ -343,10 +403,10 @@ function inversePredictor(p, width, height, bits, modes) {
343
403
  const row = y * width;
344
404
  p[row] = addPixels(p[row], p[row - width]);
345
405
  const modeRow = (y >> bits) * blocksPerRow;
346
- for (let x = 1; x < width; x++) {
347
- const mode = modes[modeRow + (x >> bits)] >> 8 & 15;
348
- const i = row + x;
349
- p[i] = addPixels(p[i], predict(mode, p, i, width));
406
+ for (let x = 1; x < width;) {
407
+ const end = Math.min(width, (x >> bits) + 1 << bits);
408
+ predictRun(modes[modeRow + (x >> bits)] >> 8 & 15, p, row + x, end - x, width, p, row + x);
409
+ x = end;
350
410
  }
351
411
  }
352
412
  }
@@ -400,8 +460,9 @@ export function readHeader(data) {
400
460
  throw new Error('VP8L: unsupported version');
401
461
  return { width, height, hasAlpha };
402
462
  }
403
- export function decode(data) {
463
+ export function decode(data, maxPixels = MAX_PIXELS) {
404
464
  const { width, height, hasAlpha } = readHeader(data);
465
+ checkPixels(width, height, maxPixels);
405
466
  const reader = new BitReader(data, 5);
406
467
  return { width, height, hasAlpha, argb: decodeStream(reader, width, height) };
407
468
  }
@@ -446,26 +507,22 @@ export function decodeStream(reader, width, height) {
446
507
  }
447
508
  return pixels;
448
509
  }
449
- class BitWriter {
450
- bytes = new Uint8Array(1 << 16);
451
- length = 0;
452
- buffer = 0;
510
+ class BitWriter extends ByteWriter {
511
+ bits = 0;
453
512
  count = 0;
454
- write(value, bits) {
455
- while (bits > 0) {
456
- const take = Math.min(bits, 24 - this.count);
457
- this.buffer |= (value & (1 << take) - 1) << this.count;
513
+ constructor() {
514
+ super(1 << 16);
515
+ }
516
+ write(value, size) {
517
+ while (size > 0) {
518
+ const take = Math.min(size, 24 - this.count);
519
+ this.bits |= (value & (1 << take) - 1) << this.count;
458
520
  this.count += take;
459
521
  value >>>= take;
460
- bits -= take;
522
+ size -= take;
461
523
  while (this.count >= 8) {
462
- if (this.length === this.bytes.length) {
463
- const grown = new Uint8Array(this.bytes.length * 2);
464
- grown.set(this.bytes);
465
- this.bytes = grown;
466
- }
467
- this.bytes[this.length++] = this.buffer & 255;
468
- this.buffer >>>= 8;
524
+ this.byte(this.bits & 255);
525
+ this.bits >>>= 8;
469
526
  this.count -= 8;
470
527
  }
471
528
  }
@@ -473,7 +530,7 @@ class BitWriter {
473
530
  finish() {
474
531
  if (this.count > 0)
475
532
  this.write(0, 8 - this.count);
476
- return this.bytes.slice(0, this.length);
533
+ return super.finish();
477
534
  }
478
535
  }
479
536
  function codeLengths(counts, maxLength) {
@@ -686,11 +743,13 @@ function tokenize(pixels, width, cacheBits) {
686
743
  const HASH_BITS = 15, CHAIN = 24, MAX_LENGTH = 4096, MIN_LENGTH = 3, WINDOW = (1 << 20) - 120;
687
744
  const head = new Int32Array(1 << HASH_BITS).fill(-1);
688
745
  const chain = new Int32Array(n);
689
- const hash = (i) => Math.imul(Math.imul(pixels[i], 0x1e35a7bd) ^ pixels[i + 1], 0x9e3779b1) >>> 32 - HASH_BITS;
746
+ const hashes = new Int32Array(n);
747
+ for (let i = 0; i + 1 < n; i++)
748
+ hashes[i] = Math.imul(Math.imul(pixels[i], 0x1e35a7bd) ^ pixels[i + 1], 0x9e3779b1) >>> 32 - HASH_BITS;
690
749
  const insert = (i) => {
691
750
  if (i + 1 >= n)
692
751
  return;
693
- const h = hash(i);
752
+ const h = hashes[i];
694
753
  chain[i] = head[h];
695
754
  head[h] = i;
696
755
  };
@@ -721,7 +780,7 @@ function tokenize(pixels, width, cacheBits) {
721
780
  }
722
781
  }
723
782
  if (i + 1 < n && bestLength < MAX_LENGTH) {
724
- for (let j = head[hash(i)], k = 0; j >= 0 && k < CHAIN && i - j <= WINDOW; j = chain[j], k++) {
783
+ for (let j = head[hashes[i]], k = 0; j >= 0 && k < CHAIN && i - j <= WINDOW; j = chain[j], k++) {
725
784
  if (i - j >= bestDistance && (i + bestLength >= n || pixels[j + bestLength] !== pixels[i + bestLength]))
726
785
  continue;
727
786
  const l = matchLength(i, j);
@@ -810,38 +869,47 @@ function writeImage(w, pixels, width, isMain) {
810
869
  }
811
870
  }
812
871
  const subPixels = (a, b) => ((a | 0x00ff00ff) - (b & 0xff00ff00) & 0xff00ff00 | (a | 0xff00ff00) - (b & 0x00ff00ff) & 0x00ff00ff) >>> 0;
813
- function residualCost(r) {
814
- const c0 = r & 255, c1 = r >>> 8 & 255, c2 = r >>> 16 & 255, c3 = r >>> 24;
815
- return (c0 < 128 ? c0 : 256 - c0) + (c1 < 128 ? c1 : 256 - c1) + (c2 < 128 ? c2 : 256 - c2) + (c3 < 128 ? c3 : 256 - c3);
816
- }
872
+ const BYTE_COST = Uint8Array.from({ length: 256 }, (_, c) => c < 128 ? c : 256 - c);
873
+ const residualCost = (r) => BYTE_COST[r & 255] + BYTE_COST[r >>> 8 & 255] + BYTE_COST[r >>> 16 & 255] + BYTE_COST[r >>> 24];
817
874
  function applyPredictor(p, width, height, bits) {
818
875
  const blocksX = subSize(width, bits), blocksY = subSize(height, bits);
819
876
  const modes = new Uint32Array(blocksX * blocksY);
820
877
  const size = 1 << bits;
821
- const src = p.slice();
822
- const predictAt = (mode, i, x, y) => y === 0 ? (x === 0 ? 0xff000000 : src[i - 1]) : x === 0 ? src[i - width] : predict(mode, src, i, width);
878
+ const src = p.slice(), predicted = new Uint32Array(size);
879
+ const predictRow = (mode, x0, x1, y) => {
880
+ const row = y * width;
881
+ if (y === 0) {
882
+ for (let x = x0; x < x1; x++)
883
+ predicted[x - x0] = x === 0 ? 0xff000000 : src[row + x - 1];
884
+ return;
885
+ }
886
+ let x = x0;
887
+ if (x === 0)
888
+ predicted[x++] = src[row - width];
889
+ predictRun(mode, src, row + x, x1 - x, width, predicted, x - x0);
890
+ };
823
891
  for (let by = 0; by < blocksY; by++)
824
892
  for (let bx = 0; bx < blocksX; bx++) {
825
893
  let best = 0, bestCost = Infinity;
826
894
  const x0 = bx * size, y0 = by * size, x1 = Math.min(width, x0 + size), y1 = Math.min(height, y0 + size);
827
895
  for (let mode = 0; mode < 14; mode++) {
828
896
  let cost = 0;
829
- for (let y = y0; y < y1 && cost < bestCost; y++)
830
- for (let x = x0; x < x1; x++) {
831
- const i = y * width + x;
832
- cost += residualCost(subPixels(src[i], predictAt(mode, i, x, y)));
833
- }
897
+ for (let y = y0; y < y1 && cost < bestCost; y++) {
898
+ predictRow(mode, x0, x1, y);
899
+ for (let x = x0, i = y * width + x0; x < x1; x++, i++)
900
+ cost += residualCost(subPixels(src[i], predicted[x - x0]));
901
+ }
834
902
  if (cost < bestCost) {
835
903
  bestCost = cost;
836
904
  best = mode;
837
905
  }
838
906
  }
839
907
  modes[by * blocksX + bx] = (0xff000000 | best << 8) >>> 0;
840
- for (let y = y0; y < y1; y++)
841
- for (let x = x0; x < x1; x++) {
842
- const i = y * width + x;
843
- p[i] = subPixels(src[i], predictAt(best, i, x, y));
844
- }
908
+ for (let y = y0; y < y1; y++) {
909
+ predictRow(best, x0, x1, y);
910
+ for (let x = x0, i = y * width + x0; x < x1; x++, i++)
911
+ p[i] = subPixels(src[i], predicted[x - x0]);
912
+ }
845
913
  }
846
914
  return modes;
847
915
  }
@@ -5,6 +5,7 @@
5
5
  import * as vp8l from "./vp8l.js";
6
6
  import * as vp8 from "./vp8.js";
7
7
  import { orientation as exifOrientation } from "./exif.js";
8
+ import { MAX_PIXELS, checkPixels } from "../util/limits.js";
8
9
  const fourCC = (s) => s.charCodeAt(0) | s.charCodeAt(1) << 8 | s.charCodeAt(2) << 16 | s.charCodeAt(3) << 24;
9
10
  const RIFF = fourCC('RIFF'), WEBP = fourCC('WEBP');
10
11
  const VP8 = fourCC('VP8 '), VP8L = fourCC('VP8L'), VP8X = fourCC('VP8X');
@@ -32,13 +33,13 @@ function container(data) {
32
33
  return chunks(data, 12, Math.min(data.length, 8 + size));
33
34
  }
34
35
  const u24 = (d, o) => d[o] | d[o + 1] << 8 | d[o + 2] << 16;
35
- function decodeBitstream(list) {
36
+ function decodeBitstream(list, maxPixels) {
36
37
  let alpha = null;
37
38
  for (const chunk of list) {
38
39
  if (chunk.type === ALPH)
39
40
  alpha ??= chunk;
40
41
  else if (chunk.type === VP8L) {
41
- const { width, height, argb } = vp8l.decode(chunk.data);
42
+ const { width, height, argb } = vp8l.decode(chunk.data, maxPixels);
42
43
  const pixels = new Uint8Array(width * height * 4);
43
44
  const out = new DataView(pixels.buffer);
44
45
  for (let i = 0; i < argb.length; i++) {
@@ -48,7 +49,7 @@ function decodeBitstream(list) {
48
49
  return { width, height, pixels };
49
50
  }
50
51
  else if (chunk.type === VP8) {
51
- const image = vp8.decode(chunk.data);
52
+ const image = vp8.decode(chunk.data, maxPixels);
52
53
  if (alpha)
53
54
  applyAlpha(image.pixels, image.width, image.height, alpha.data);
54
55
  return image;
@@ -109,7 +110,7 @@ export function isAnimated(data) {
109
110
  const header = list[0];
110
111
  return header && header.type === VP8X && header.data.length >= 10 && (header.data[0] & 2) !== 0 && list.some(c => c.type === ANMF);
111
112
  }
112
- export function decode(data) {
113
+ export function decode(data, maxPixels = MAX_PIXELS) {
113
114
  const list = container(data);
114
115
  if (!list.length)
115
116
  throw new Error('WebP: no image data');
@@ -118,11 +119,11 @@ export function decode(data) {
118
119
  if (exif)
119
120
  orientation = exifOrientation(exif.data);
120
121
  if (list.some(c => c.type === ANMF)) {
121
- const animation = decodeAnimation(data, true);
122
+ const animation = decodeAnimation(data, true, maxPixels);
122
123
  const frame = composite(animation)[0];
123
124
  return { width: animation.width, height: animation.height, pixels: frame.pixels, orientation };
124
125
  }
125
- const image = decodeBitstream(list);
126
+ const image = decodeBitstream(list, maxPixels);
126
127
  if (list[0].type === VP8X && list[0].data.length >= 10) {
127
128
  const width = u24(list[0].data, 4) + 1, height = u24(list[0].data, 7) + 1;
128
129
  if (width !== image.width || height !== image.height)
@@ -130,16 +131,17 @@ export function decode(data) {
130
131
  }
131
132
  return { ...image, orientation };
132
133
  }
133
- export function decodeAnimation(data, onlyFirstFrame = false) {
134
+ export function decodeAnimation(data, onlyFirstFrame = false, maxPixels = MAX_PIXELS) {
134
135
  const list = container(data);
135
136
  const header = list[0];
136
137
  if (!header || header.type !== VP8X || !list.some(c => c.type === ANMF)) {
137
- const { width, height, pixels } = decodeBitstream(list);
138
+ const { width, height, pixels } = decodeBitstream(list, maxPixels);
138
139
  return { width, height, loopCount: 0, background: 0, frames: [{ x: 0, y: 0, width, height, duration: 0, blend: false, dispose: false, pixels }] };
139
140
  }
140
141
  if (header.data.length < 10)
141
142
  throw new Error('WebP: invalid VP8X chunk');
142
143
  const width = u24(header.data, 4) + 1, height = u24(header.data, 7) + 1;
144
+ checkPixels(width, height, maxPixels, onlyFirstFrame ? 1 : list.filter(c => c.type === ANMF).length);
143
145
  let loopCount = 0, background = 0;
144
146
  const frames = [];
145
147
  for (const chunk of list) {
@@ -152,7 +154,7 @@ export function decodeAnimation(data, onlyFirstFrame = false) {
152
154
  const d = chunk.data;
153
155
  if (d.length < 16)
154
156
  throw new Error('WebP: invalid ANMF chunk');
155
- const frame = decodeBitstream(chunks(d, 16, d.length));
157
+ const frame = decodeBitstream(chunks(d, 16, d.length), maxPixels);
156
158
  frames.push({
157
159
  x: u24(d, 0) * 2,
158
160
  y: u24(d, 3) * 2,
@@ -94,75 +94,73 @@ function inflate(dat, buf) {
94
94
  return buf.subarray(0, 0);
95
95
  const tbts = sl * 8;
96
96
  let final = 0, pos = 0, bt = 0;
97
- let lm = null, dm = null, lbt = 0, dbt = 0;
98
97
  do {
99
- if (!lm) {
100
- final = bits(dat, pos, 1);
101
- const type = bits(dat, pos + 1, 3);
102
- pos += 3;
103
- if (!type) {
104
- const s = shft(pos) + 4, l = dat[s - 4] | dat[s - 3] << 8, t = s + l;
105
- if (t > sl)
106
- throw new Error('zlib: unexpected end of data');
107
- if (bt < buf.length)
108
- buf.set(dat.subarray(s, Math.min(t, s + buf.length - bt)), bt);
109
- bt += l;
110
- pos = t * 8;
111
- continue;
112
- }
113
- else if (type === 1) {
114
- lm = flrm;
115
- dm = fdrm;
116
- lbt = 9;
117
- dbt = 5;
118
- }
119
- else if (type === 2) {
120
- const hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4;
121
- const tl = hLit + bits(dat, pos + 5, 31) + 1;
122
- pos += 14;
123
- const ldt = new u8(tl);
124
- const clt = new u8(19);
125
- for (let i = 0; i < hcLen; ++i)
126
- clt[clim[i]] = bits(dat, pos + i * 3, 7);
127
- pos += hcLen * 3;
128
- const clb = max(clt), clbmsk = (1 << clb) - 1;
129
- const clm = hMap(clt, clb, true);
130
- for (let i = 0; i < tl;) {
131
- const r = clm[bits(dat, pos, clbmsk)];
132
- pos += r & 15;
133
- const s = r >> 4;
134
- if (s < 16)
135
- ldt[i++] = s;
136
- else {
137
- let c = 0, n = 0;
138
- if (s === 16) {
139
- n = 3 + bits(dat, pos, 3);
140
- pos += 2;
141
- c = ldt[i - 1];
142
- }
143
- else if (s === 17) {
144
- n = 3 + bits(dat, pos, 7);
145
- pos += 3;
146
- }
147
- else if (s === 18) {
148
- n = 11 + bits(dat, pos, 127);
149
- pos += 7;
150
- }
151
- while (n--)
152
- ldt[i++] = c;
98
+ let lm, dm, lbt, dbt;
99
+ final = bits(dat, pos, 1);
100
+ const type = bits(dat, pos + 1, 3);
101
+ pos += 3;
102
+ if (!type) {
103
+ const s = shft(pos) + 4, l = dat[s - 4] | dat[s - 3] << 8, t = s + l;
104
+ if (t > sl)
105
+ throw new Error('zlib: unexpected end of data');
106
+ if (bt < buf.length)
107
+ buf.set(dat.subarray(s, Math.min(t, s + buf.length - bt)), bt);
108
+ bt += l;
109
+ pos = t * 8;
110
+ continue;
111
+ }
112
+ else if (type === 1) {
113
+ lm = flrm;
114
+ dm = fdrm;
115
+ lbt = 9;
116
+ dbt = 5;
117
+ }
118
+ else if (type === 2) {
119
+ const hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4;
120
+ const tl = hLit + bits(dat, pos + 5, 31) + 1;
121
+ pos += 14;
122
+ const ldt = new u8(tl);
123
+ const clt = new u8(19);
124
+ for (let i = 0; i < hcLen; ++i)
125
+ clt[clim[i]] = bits(dat, pos + i * 3, 7);
126
+ pos += hcLen * 3;
127
+ const clb = max(clt), clbmsk = (1 << clb) - 1;
128
+ const clm = hMap(clt, clb, true);
129
+ for (let i = 0; i < tl;) {
130
+ const r = clm[bits(dat, pos, clbmsk)];
131
+ pos += r & 15;
132
+ const s = r >> 4;
133
+ if (s < 16)
134
+ ldt[i++] = s;
135
+ else {
136
+ let c = 0, n = 0;
137
+ if (s === 16) {
138
+ n = 3 + bits(dat, pos, 3);
139
+ pos += 2;
140
+ c = ldt[i - 1];
141
+ }
142
+ else if (s === 17) {
143
+ n = 3 + bits(dat, pos, 7);
144
+ pos += 3;
153
145
  }
146
+ else if (s === 18) {
147
+ n = 11 + bits(dat, pos, 127);
148
+ pos += 7;
149
+ }
150
+ while (n--)
151
+ ldt[i++] = c;
154
152
  }
155
- const lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);
156
- lbt = max(lt);
157
- dbt = max(dt);
158
- lm = hMap(lt, lbt, true);
159
- dm = hMap(dt, dbt, true);
160
153
  }
161
- else
162
- throw new Error('zlib: invalid block type');
163
- if (pos > tbts)
164
- throw new Error('zlib: unexpected end of data');
154
+ const lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);
155
+ lbt = max(lt);
156
+ dbt = max(dt);
157
+ lm = hMap(lt, lbt, true);
158
+ dm = hMap(dt, dbt, true);
165
159
  }
160
+ else
161
+ throw new Error('zlib: invalid block type');
162
+ if (pos > tbts)
163
+ throw new Error('zlib: unexpected end of data');
166
164
  const lms = (1 << lbt) - 1, dms = (1 << dbt) - 1;
167
165
  for (;;) {
168
166
  const c = lm[bits16(dat, pos) & lms], sym = c >> 4;
@@ -173,10 +171,8 @@ function inflate(dat, buf) {
173
171
  throw new Error('zlib: invalid length/literal');
174
172
  if (sym < 256)
175
173
  buf[bt++] = sym;
176
- else if (sym === 256) {
177
- lm = null;
174
+ else if (sym === 256)
178
175
  break;
179
- }
180
176
  else {
181
177
  let add = sym - 254;
182
178
  if (sym > 264) {
@@ -557,8 +553,8 @@ export function decompress(data, limit) {
557
553
  throw new Error('zlib: preset dictionaries are not supported');
558
554
  return inflate(data.subarray(2), new u8(limit));
559
555
  }
560
- const { process: runtime, DecompressionStream: NativeDecompression, Blob: NativeBlob } = globalThis;
561
- const nodeZlib = (() => {
556
+ const { process: runtime, CompressionStream: NativeCompression, DecompressionStream: NativeDecompression, Blob: NativeBlob } = globalThis;
557
+ export const nodeZlib = (() => {
562
558
  try {
563
559
  return (runtime && runtime.getBuiltinModule && runtime.getBuiltinModule('node:zlib')) || null;
564
560
  }
@@ -571,6 +567,22 @@ export async function compressAsync(data, level) {
571
567
  level = Math.min(9, Math.max(0, level | 0));
572
568
  if (nodeZlib)
573
569
  return plain(nodeZlib.deflateSync(data, { level }));
570
+ if (NativeCompression && NativeBlob && level >= 1 && level <= 6) {
571
+ const reader = new NativeBlob([data]).stream().pipeThrough(new NativeCompression('deflate')).getReader();
572
+ const parts = [];
573
+ let length = 0;
574
+ for (;;) {
575
+ const { done, value } = await reader.read();
576
+ if (done || !value)
577
+ break;
578
+ parts.push(value);
579
+ length += value.length;
580
+ }
581
+ const out = new Uint8Array(length);
582
+ for (let i = 0, offset = 0; i < parts.length; offset += parts[i++].length)
583
+ out.set(parts[i], offset);
584
+ return out;
585
+ }
574
586
  return compress(data, level);
575
587
  }
576
588
  export async function decompressAsync(data, limit) {
package/dist/frame.d.ts CHANGED
@@ -52,6 +52,7 @@ export declare class Frame extends Image {
52
52
  get disposalMode(): FrameDisposalModeName;
53
53
  /** Sets the disposal mode by name or id (0-3) */
54
54
  set disposalMode(disposalMode: FrameDisposalModeName | FrameDisposalModeId);
55
+ /** A short description for debugging, e.g. `Frame<64x64x100ms>` */
55
56
  toString(): string;
56
57
  /**
57
58
  * Converts an Image instance to a Frame, cloning it in the process
package/dist/frame.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Image } from "./image.js";
2
- import { isOptions } from "./util/options.js";
2
+ import { isOptions, finite } from "./util/options.js";
3
3
  const DISPOSAL_MODES = ['any', 'keep', 'previous', 'background'];
4
4
  export class Frame extends Image {
5
5
  duration;
@@ -26,8 +26,10 @@ export class Frame extends Image {
26
26
  }
27
27
  constructor(width, height, durationOrOptions, xOffset, yOffset, disposalMode) {
28
28
  const { duration = 100, xOffset: x = 0, yOffset: y = 0, disposalMode: disposal = Frame.DISPOSAL_KEEP } = isOptions(durationOrOptions) ? durationOrOptions : { duration: durationOrOptions, xOffset, yOffset, disposalMode };
29
- if (isNaN(duration) || duration < 0)
29
+ if (finite(duration, 'frame duration') < 0)
30
30
  throw new RangeError('Invalid frame duration');
31
+ finite(x, 'x offset');
32
+ finite(y, 'y offset');
31
33
  super(width, height);
32
34
  this.duration = duration;
33
35
  this.xOffset = x;
package/dist/gif.d.ts CHANGED
@@ -15,6 +15,7 @@ export declare class GIF extends Animation {
15
15
  * {@link AnimationOptions}
16
16
  */
17
17
  constructor(frames: Frame[], loopCountOrOptions?: number | AnimationOptions);
18
+ /** A short description for debugging, e.g. `GIF<64x64x1000ms>` */
18
19
  toString(): string;
19
20
  /**
20
21
  * Encodes the GIF
package/dist/gif.js CHANGED
@@ -13,10 +13,10 @@ export class GIF extends Animation {
13
13
  return this.encodeGIF(qualityOrOptions);
14
14
  }
15
15
  static async decode(input, onlyFirstFrameOrOptions = false) {
16
- const onlyExtractFirstFrame = isOptions(onlyFirstFrameOrOptions) ? onlyFirstFrameOrOptions.onlyFirstFrame ?? false : onlyFirstFrameOrOptions;
16
+ const { onlyFirstFrame = false, maxPixels } = isOptions(onlyFirstFrameOrOptions) ? onlyFirstFrameOrOptions : { onlyFirstFrame: onlyFirstFrameOrOptions };
17
17
  const data = view(input);
18
18
  if (!ImageType.isGIF(new DataView(data.buffer, data.byteOffset, data.byteLength)))
19
19
  throw new Error('Unsupported image type');
20
- return Animation.__fromGIF__(data, onlyExtractFirstFrame, GIF);
20
+ return Animation.__fromGIF__(data, onlyFirstFrame, GIF, maxPixels);
21
21
  }
22
22
  }