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
@@ -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
  }
@@ -4,7 +4,10 @@
4
4
  */
5
5
  import * as vp8l from "./vp8l.js";
6
6
  import * as vp8 from "./vp8.js";
7
+ import { encodeVP8, encodeAlpha } from "./vp8-enc.js";
8
+ import { rgbaToYUV } from "./vp8-enc-dsp.js";
7
9
  import { orientation as exifOrientation } from "./exif.js";
10
+ import { MAX_PIXELS, checkPixels } from "../util/limits.js";
8
11
  const fourCC = (s) => s.charCodeAt(0) | s.charCodeAt(1) << 8 | s.charCodeAt(2) << 16 | s.charCodeAt(3) << 24;
9
12
  const RIFF = fourCC('RIFF'), WEBP = fourCC('WEBP');
10
13
  const VP8 = fourCC('VP8 '), VP8L = fourCC('VP8L'), VP8X = fourCC('VP8X');
@@ -32,13 +35,13 @@ function container(data) {
32
35
  return chunks(data, 12, Math.min(data.length, 8 + size));
33
36
  }
34
37
  const u24 = (d, o) => d[o] | d[o + 1] << 8 | d[o + 2] << 16;
35
- function decodeBitstream(list) {
38
+ function decodeBitstream(list, maxPixels) {
36
39
  let alpha = null;
37
40
  for (const chunk of list) {
38
41
  if (chunk.type === ALPH)
39
42
  alpha ??= chunk;
40
43
  else if (chunk.type === VP8L) {
41
- const { width, height, argb } = vp8l.decode(chunk.data);
44
+ const { width, height, argb } = vp8l.decode(chunk.data, maxPixels);
42
45
  const pixels = new Uint8Array(width * height * 4);
43
46
  const out = new DataView(pixels.buffer);
44
47
  for (let i = 0; i < argb.length; i++) {
@@ -48,7 +51,7 @@ function decodeBitstream(list) {
48
51
  return { width, height, pixels };
49
52
  }
50
53
  else if (chunk.type === VP8) {
51
- const image = vp8.decode(chunk.data);
54
+ const image = vp8.decode(chunk.data, maxPixels);
52
55
  if (alpha)
53
56
  applyAlpha(image.pixels, image.width, image.height, alpha.data);
54
57
  return image;
@@ -109,7 +112,7 @@ export function isAnimated(data) {
109
112
  const header = list[0];
110
113
  return header && header.type === VP8X && header.data.length >= 10 && (header.data[0] & 2) !== 0 && list.some(c => c.type === ANMF);
111
114
  }
112
- export function decode(data) {
115
+ export function decode(data, maxPixels = MAX_PIXELS) {
113
116
  const list = container(data);
114
117
  if (!list.length)
115
118
  throw new Error('WebP: no image data');
@@ -118,11 +121,11 @@ export function decode(data) {
118
121
  if (exif)
119
122
  orientation = exifOrientation(exif.data);
120
123
  if (list.some(c => c.type === ANMF)) {
121
- const animation = decodeAnimation(data, true);
124
+ const animation = decodeAnimation(data, true, maxPixels);
122
125
  const frame = composite(animation)[0];
123
126
  return { width: animation.width, height: animation.height, pixels: frame.pixels, orientation };
124
127
  }
125
- const image = decodeBitstream(list);
128
+ const image = decodeBitstream(list, maxPixels);
126
129
  if (list[0].type === VP8X && list[0].data.length >= 10) {
127
130
  const width = u24(list[0].data, 4) + 1, height = u24(list[0].data, 7) + 1;
128
131
  if (width !== image.width || height !== image.height)
@@ -130,16 +133,17 @@ export function decode(data) {
130
133
  }
131
134
  return { ...image, orientation };
132
135
  }
133
- export function decodeAnimation(data, onlyFirstFrame = false) {
136
+ export function decodeAnimation(data, onlyFirstFrame = false, maxPixels = MAX_PIXELS) {
134
137
  const list = container(data);
135
138
  const header = list[0];
136
139
  if (!header || header.type !== VP8X || !list.some(c => c.type === ANMF)) {
137
- const { width, height, pixels } = decodeBitstream(list);
140
+ const { width, height, pixels } = decodeBitstream(list, maxPixels);
138
141
  return { width, height, loopCount: 0, background: 0, frames: [{ x: 0, y: 0, width, height, duration: 0, blend: false, dispose: false, pixels }] };
139
142
  }
140
143
  if (header.data.length < 10)
141
144
  throw new Error('WebP: invalid VP8X chunk');
142
145
  const width = u24(header.data, 4) + 1, height = u24(header.data, 7) + 1;
146
+ checkPixels(width, height, maxPixels, onlyFirstFrame ? 1 : list.filter(c => c.type === ANMF).length);
143
147
  let loopCount = 0, background = 0;
144
148
  const frames = [];
145
149
  for (const chunk of list) {
@@ -152,7 +156,7 @@ export function decodeAnimation(data, onlyFirstFrame = false) {
152
156
  const d = chunk.data;
153
157
  if (d.length < 16)
154
158
  throw new Error('WebP: invalid ANMF chunk');
155
- const frame = decodeBitstream(chunks(d, 16, d.length));
159
+ const frame = decodeBitstream(chunks(d, 16, d.length), maxPixels);
156
160
  frames.push({
157
161
  x: u24(d, 0) * 2,
158
162
  y: u24(d, 3) * 2,
@@ -216,6 +220,15 @@ function riffChunk(type, body) {
216
220
  out.set(body, 8);
217
221
  return out;
218
222
  }
223
+ function concat(parts) {
224
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
225
+ let offset = 0;
226
+ for (const p of parts) {
227
+ out.set(p, offset);
228
+ offset += p.length;
229
+ }
230
+ return out;
231
+ }
219
232
  function riff(chunks) {
220
233
  let size = 4;
221
234
  for (const c of chunks)
@@ -255,7 +268,29 @@ export function encode(rgba, width, height, exif) {
255
268
  set24(vp8x, 7, height - 1);
256
269
  return riff([riffChunk('VP8X', vp8x), bitstream, riffChunk('EXIF', exif)]);
257
270
  }
258
- export function encodeAnimation(frames, width, height, plays, exif) {
271
+ export function encodeLossy(rgba, width, height, quality, exif, method = 4) {
272
+ const { alpha, chunks } = lossyChunks(rgba, width, height, quality, method);
273
+ if (!alpha && !exif)
274
+ return riff(chunks);
275
+ const vp8x = new Uint8Array(10);
276
+ vp8x[0] = (exif ? 8 : 0) | (alpha ? 16 : 0);
277
+ set24(vp8x, 4, width - 1);
278
+ set24(vp8x, 7, height - 1);
279
+ return riff([riffChunk('VP8X', vp8x), ...chunks, ...exif ? [riffChunk('EXIF', exif)] : []]);
280
+ }
281
+ function lossyChunks(rgba, width, height, quality, method) {
282
+ if (width > 16383 || height > 16383)
283
+ throw new RangeError('WebP: lossy images can be at most 16383 pixels wide and high');
284
+ const picture = rgbaToYUV(rgba, width, height);
285
+ const frame = encodeVP8(picture, quality, method);
286
+ const padded = new Uint8Array(frame.length + (frame.length & 1));
287
+ padded.set(frame);
288
+ const chunks = [riffChunk('VP8 ', padded)];
289
+ if (picture.a)
290
+ chunks.unshift(riffChunk('ALPH', encodeAlpha(picture.a, width, height, method)));
291
+ return { alpha: !!picture.a, chunks };
292
+ }
293
+ export function encodeAnimation(frames, width, height, plays, exif, quality, method = 4) {
259
294
  if (!frames.length)
260
295
  throw new Error('WebP: an animation needs at least one frame');
261
296
  if (width > 1 << 24 || height > 1 << 24)
@@ -274,7 +309,8 @@ export function encodeAnimation(frames, width, height, plays, exif) {
274
309
  for (const f of frames) {
275
310
  if (f.x & 1 || f.y & 1)
276
311
  throw new RangeError('WebP: frame offsets have to be even');
277
- const bitstream = riffChunk('VP8L', vp8l.encode(toARGB(f.pixels, f.width * f.height), f.width, f.height));
312
+ const bitstream = quality === undefined ? riffChunk('VP8L', vp8l.encode(toARGB(f.pixels, f.width * f.height), f.width, f.height))
313
+ : concat(lossyChunks(f.pixels, f.width, f.height, quality, method).chunks);
278
314
  const header = new Uint8Array(16);
279
315
  set24(header, 0, f.x >> 1);
280
316
  set24(header, 3, f.y >> 1);
@@ -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