cross-image 0.2.4 → 0.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 (105) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +507 -333
  3. package/esm/mod.d.ts +4 -4
  4. package/esm/mod.js +2 -2
  5. package/esm/src/formats/apng.d.ts +5 -5
  6. package/esm/src/formats/apng.js +7 -9
  7. package/esm/src/formats/ascii.d.ts +3 -3
  8. package/esm/src/formats/ascii.js +1 -1
  9. package/esm/src/formats/avif.d.ts +3 -3
  10. package/esm/src/formats/avif.js +7 -7
  11. package/esm/src/formats/bmp.d.ts +3 -3
  12. package/esm/src/formats/bmp.js +2 -2
  13. package/esm/src/formats/dng.d.ts +1 -1
  14. package/esm/src/formats/dng.js +1 -1
  15. package/esm/src/formats/gif.d.ts +4 -4
  16. package/esm/src/formats/gif.js +14 -10
  17. package/esm/src/formats/heic.d.ts +3 -3
  18. package/esm/src/formats/heic.js +7 -7
  19. package/esm/src/formats/ico.d.ts +3 -3
  20. package/esm/src/formats/ico.js +4 -4
  21. package/esm/src/formats/jpeg.d.ts +3 -3
  22. package/esm/src/formats/jpeg.js +23 -11
  23. package/esm/src/formats/pam.d.ts +3 -3
  24. package/esm/src/formats/pam.js +2 -2
  25. package/esm/src/formats/pcx.d.ts +3 -3
  26. package/esm/src/formats/pcx.js +2 -2
  27. package/esm/src/formats/png.d.ts +3 -3
  28. package/esm/src/formats/png.js +2 -2
  29. package/esm/src/formats/png_base.js +2 -5
  30. package/esm/src/formats/ppm.d.ts +3 -3
  31. package/esm/src/formats/ppm.js +2 -2
  32. package/esm/src/formats/tiff.d.ts +7 -18
  33. package/esm/src/formats/tiff.js +86 -21
  34. package/esm/src/formats/webp.d.ts +3 -3
  35. package/esm/src/formats/webp.js +11 -8
  36. package/esm/src/image.d.ts +11 -3
  37. package/esm/src/image.js +37 -21
  38. package/esm/src/types.d.ts +56 -4
  39. package/esm/src/utils/gif_decoder.d.ts +4 -1
  40. package/esm/src/utils/gif_decoder.js +91 -65
  41. package/esm/src/utils/image_processing.js +144 -70
  42. package/esm/src/utils/jpeg_decoder.d.ts +17 -4
  43. package/esm/src/utils/jpeg_decoder.js +448 -83
  44. package/esm/src/utils/jpeg_encoder.d.ts +15 -1
  45. package/esm/src/utils/jpeg_encoder.js +263 -24
  46. package/esm/src/utils/resize.js +51 -20
  47. package/esm/src/utils/tiff_deflate.d.ts +18 -0
  48. package/esm/src/utils/tiff_deflate.js +27 -0
  49. package/esm/src/utils/tiff_packbits.d.ts +24 -0
  50. package/esm/src/utils/tiff_packbits.js +90 -0
  51. package/esm/src/utils/webp_decoder.d.ts +3 -1
  52. package/esm/src/utils/webp_decoder.js +144 -63
  53. package/esm/src/utils/webp_encoder.js +5 -11
  54. package/package.json +1 -1
  55. package/script/mod.d.ts +4 -4
  56. package/script/mod.js +2 -2
  57. package/script/src/formats/apng.d.ts +5 -5
  58. package/script/src/formats/apng.js +7 -9
  59. package/script/src/formats/ascii.d.ts +3 -3
  60. package/script/src/formats/ascii.js +1 -1
  61. package/script/src/formats/avif.d.ts +3 -3
  62. package/script/src/formats/avif.js +7 -7
  63. package/script/src/formats/bmp.d.ts +3 -3
  64. package/script/src/formats/bmp.js +2 -2
  65. package/script/src/formats/dng.d.ts +1 -1
  66. package/script/src/formats/dng.js +1 -1
  67. package/script/src/formats/gif.d.ts +4 -4
  68. package/script/src/formats/gif.js +14 -10
  69. package/script/src/formats/heic.d.ts +3 -3
  70. package/script/src/formats/heic.js +7 -7
  71. package/script/src/formats/ico.d.ts +3 -3
  72. package/script/src/formats/ico.js +4 -4
  73. package/script/src/formats/jpeg.d.ts +3 -3
  74. package/script/src/formats/jpeg.js +23 -11
  75. package/script/src/formats/pam.d.ts +3 -3
  76. package/script/src/formats/pam.js +2 -2
  77. package/script/src/formats/pcx.d.ts +3 -3
  78. package/script/src/formats/pcx.js +2 -2
  79. package/script/src/formats/png.d.ts +3 -3
  80. package/script/src/formats/png.js +2 -2
  81. package/script/src/formats/png_base.js +2 -5
  82. package/script/src/formats/ppm.d.ts +3 -3
  83. package/script/src/formats/ppm.js +2 -2
  84. package/script/src/formats/tiff.d.ts +7 -18
  85. package/script/src/formats/tiff.js +86 -21
  86. package/script/src/formats/webp.d.ts +3 -3
  87. package/script/src/formats/webp.js +11 -8
  88. package/script/src/image.d.ts +11 -3
  89. package/script/src/image.js +36 -20
  90. package/script/src/types.d.ts +56 -4
  91. package/script/src/utils/gif_decoder.d.ts +4 -1
  92. package/script/src/utils/gif_decoder.js +91 -65
  93. package/script/src/utils/image_processing.js +144 -70
  94. package/script/src/utils/jpeg_decoder.d.ts +17 -4
  95. package/script/src/utils/jpeg_decoder.js +448 -83
  96. package/script/src/utils/jpeg_encoder.d.ts +15 -1
  97. package/script/src/utils/jpeg_encoder.js +263 -24
  98. package/script/src/utils/resize.js +51 -20
  99. package/script/src/utils/tiff_deflate.d.ts +18 -0
  100. package/script/src/utils/tiff_deflate.js +31 -0
  101. package/script/src/utils/tiff_packbits.d.ts +24 -0
  102. package/script/src/utils/tiff_packbits.js +94 -0
  103. package/script/src/utils/webp_decoder.d.ts +3 -1
  104. package/script/src/utils/webp_decoder.js +144 -63
  105. package/script/src/utils/webp_encoder.js +5 -11
@@ -2,14 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TIFFFormat = void 0;
4
4
  const tiff_lzw_js_1 = require("../utils/tiff_lzw.js");
5
+ const tiff_packbits_js_1 = require("../utils/tiff_packbits.js");
6
+ const tiff_deflate_js_1 = require("../utils/tiff_deflate.js");
5
7
  const security_js_1 = require("../utils/security.js");
6
8
  // Constants for unit conversions
7
9
  const DEFAULT_DPI = 72;
8
10
  /**
9
11
  * TIFF format handler
10
- * Implements pure-JS TIFF decoder for uncompressed and LZW-compressed RGB/RGBA images
11
- * and encoder for uncompressed and LZW-compressed RGBA TIFFs. Falls back to ImageDecoder
12
- * for other compressed TIFFs (JPEG, PackBits, etc.)
12
+ * Implements pure-JS TIFF decoder for uncompressed, LZW, PackBits, and Deflate-compressed RGB/RGBA images
13
+ * and encoder for uncompressed, LZW, PackBits, and Deflate-compressed RGBA TIFFs. Falls back to ImageDecoder
14
+ * for JPEG-compressed TIFFs.
13
15
  * Supports multi-page TIFF files.
14
16
  */
15
17
  class TIFFFormat {
@@ -55,7 +57,7 @@ class TIFFFormat {
55
57
  * @param data Raw TIFF image data
56
58
  * @returns Decoded image data with RGBA pixels of first page
57
59
  */
58
- async decode(data) {
60
+ async decode(data, _options) {
59
61
  if (!this.canDecode(data)) {
60
62
  throw new Error("Invalid TIFF signature");
61
63
  }
@@ -131,7 +133,7 @@ class TIFFFormat {
131
133
  metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
132
134
  };
133
135
  }
134
- encode(imageData, options) {
136
+ async encode(imageData, options) {
135
137
  const { width, height, data, metadata } = imageData;
136
138
  const opts = options;
137
139
  const compression = opts?.compression ?? "none";
@@ -175,6 +177,16 @@ class TIFFFormat {
175
177
  pixelData = encoder.compress(sourceData);
176
178
  compressionCode = 5;
177
179
  }
180
+ else if (compression === "packbits") {
181
+ // PackBits compress the pixel data
182
+ pixelData = (0, tiff_packbits_js_1.packBitsCompress)(sourceData);
183
+ compressionCode = 32773;
184
+ }
185
+ else if (compression === "deflate") {
186
+ // Deflate compress the pixel data
187
+ pixelData = await (0, tiff_deflate_js_1.deflateCompress)(sourceData);
188
+ compressionCode = 8;
189
+ }
178
190
  else {
179
191
  // Uncompressed
180
192
  pixelData = sourceData;
@@ -332,7 +344,7 @@ class TIFFFormat {
332
344
  /**
333
345
  * Decode all pages from a multi-page TIFF
334
346
  */
335
- async decodeFrames(data) {
347
+ async decodeFrames(data, _options) {
336
348
  if (!this.canDecode(data)) {
337
349
  throw new Error("Invalid TIFF signature");
338
350
  }
@@ -395,7 +407,7 @@ class TIFFFormat {
395
407
  /**
396
408
  * Encode multi-page TIFF
397
409
  */
398
- encodeFrames(imageData, options) {
410
+ async encodeFrames(imageData, options) {
399
411
  const opts = options;
400
412
  const compression = opts?.compression ?? "none";
401
413
  if (imageData.frames.length === 0) {
@@ -420,6 +432,12 @@ class TIFFFormat {
420
432
  const encoder = new tiff_lzw_js_1.TIFFLZWEncoder();
421
433
  pixelData = encoder.compress(frame.data);
422
434
  }
435
+ else if (compression === "packbits") {
436
+ pixelData = (0, tiff_packbits_js_1.packBitsCompress)(frame.data);
437
+ }
438
+ else if (compression === "deflate") {
439
+ pixelData = await (0, tiff_deflate_js_1.deflateCompress)(frame.data);
440
+ }
423
441
  else {
424
442
  pixelData = frame.data;
425
443
  }
@@ -456,7 +474,19 @@ class TIFFFormat {
456
474
  this.writeIFDEntry(result, 0x0102, 3, 4, dataOffset);
457
475
  dataOffset += 8;
458
476
  // Compression
459
- const compressionCode = compression === "lzw" ? 5 : 1;
477
+ let compressionCode;
478
+ if (compression === "lzw") {
479
+ compressionCode = 5;
480
+ }
481
+ else if (compression === "packbits") {
482
+ compressionCode = 32773;
483
+ }
484
+ else if (compression === "deflate") {
485
+ compressionCode = 8;
486
+ }
487
+ else {
488
+ compressionCode = 1;
489
+ }
460
490
  this.writeIFDEntry(result, 0x0103, 3, 1, compressionCode);
461
491
  // PhotometricInterpretation
462
492
  this.writeIFDEntry(result, 0x0106, 3, 1, 2);
@@ -467,9 +497,19 @@ class TIFFFormat {
467
497
  // RowsPerStrip
468
498
  this.writeIFDEntry(result, 0x0116, 4, 1, frame.height);
469
499
  // StripByteCounts
470
- const pixelDataSize = compression === "lzw"
471
- ? new tiff_lzw_js_1.TIFFLZWEncoder().compress(frame.data).length
472
- : frame.data.length;
500
+ let pixelDataSize;
501
+ if (compression === "lzw") {
502
+ pixelDataSize = new tiff_lzw_js_1.TIFFLZWEncoder().compress(frame.data).length;
503
+ }
504
+ else if (compression === "packbits") {
505
+ pixelDataSize = (0, tiff_packbits_js_1.packBitsCompress)(frame.data).length;
506
+ }
507
+ else if (compression === "deflate") {
508
+ pixelDataSize = (await (0, tiff_deflate_js_1.deflateCompress)(frame.data)).length;
509
+ }
510
+ else {
511
+ pixelDataSize = frame.data.length;
512
+ }
473
513
  this.writeIFDEntry(result, 0x0117, 4, 1, pixelDataSize);
474
514
  // XResolution
475
515
  const xResOffset = dataOffset;
@@ -572,7 +612,7 @@ class TIFFFormat {
572
612
  const isLittleEndian = data[0] === 0x49;
573
613
  // Try pure JavaScript decoder first
574
614
  try {
575
- const pureJSResult = this.decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian);
615
+ const pureJSResult = await this.decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian);
576
616
  if (pureJSResult) {
577
617
  return pureJSResult;
578
618
  }
@@ -696,7 +736,7 @@ class TIFFFormat {
696
736
  async decodeUsingRuntime(data, width, height) {
697
737
  // Try pure JavaScript decoder first for uncompressed TIFFs
698
738
  try {
699
- const pureJSResult = this.decodePureJS(data, width, height);
739
+ const pureJSResult = await this.decodePureJS(data, width, height);
700
740
  if (pureJSResult) {
701
741
  return pureJSResult;
702
742
  }
@@ -734,10 +774,10 @@ class TIFFFormat {
734
774
  return new TextDecoder().decode(data.slice(offset, endIndex));
735
775
  }
736
776
  /**
737
- * Pure JavaScript TIFF decoder for uncompressed and LZW-compressed RGB/RGBA images
777
+ * Pure JavaScript TIFF decoder for uncompressed, LZW, PackBits, and Deflate-compressed RGB/RGBA images
738
778
  * Returns null if the TIFF uses unsupported features
739
779
  */
740
- decodePureJS(data, width, height) {
780
+ async decodePureJS(data, width, height) {
741
781
  // Validate minimum TIFF header size
742
782
  if (data.length < 8) {
743
783
  return null;
@@ -748,8 +788,9 @@ class TIFFFormat {
748
788
  const ifdOffset = this.readUint32(data, 4, isLittleEndian);
749
789
  // Check compression
750
790
  const compression = this.getIFDValue(data, ifdOffset, 0x0103, isLittleEndian);
751
- if (compression !== 1 && compression !== 5) {
752
- // Only support uncompressed (1) and LZW (5)
791
+ if (compression !== 1 && compression !== 5 && compression !== 8 &&
792
+ compression !== 32773) {
793
+ // Support: 1 = uncompressed, 5 = LZW, 8 = Deflate, 32773 = PackBits
753
794
  return null;
754
795
  }
755
796
  // Check photometric interpretation
@@ -791,6 +832,16 @@ class TIFFFormat {
791
832
  const decoder = new tiff_lzw_js_1.TIFFLZWDecoder(compressedData);
792
833
  pixelData = decoder.decompress();
793
834
  }
835
+ else if (compression === 32773) {
836
+ // PackBits compressed
837
+ const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
838
+ pixelData = (0, tiff_packbits_js_1.packBitsDecompress)(compressedData);
839
+ }
840
+ else if (compression === 8) {
841
+ // Deflate compressed
842
+ const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
843
+ pixelData = await (0, tiff_deflate_js_1.deflateDecompress)(compressedData);
844
+ }
794
845
  else {
795
846
  // Uncompressed
796
847
  pixelData = data.slice(stripOffset, stripOffset + stripByteCount);
@@ -842,11 +893,12 @@ class TIFFFormat {
842
893
  * Pure JavaScript TIFF decoder for a specific IFD
843
894
  * Returns null if the TIFF uses unsupported features
844
895
  */
845
- decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian) {
896
+ async decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian) {
846
897
  // Check compression
847
898
  const compression = this.getIFDValue(data, ifdOffset, 0x0103, isLittleEndian);
848
- if (compression !== 1 && compression !== 5) {
849
- // Only support uncompressed (1) and LZW (5)
899
+ if (compression !== 1 && compression !== 5 && compression !== 8 &&
900
+ compression !== 32773) {
901
+ // Support: 1 = uncompressed, 5 = LZW, 8 = Deflate, 32773 = PackBits
850
902
  return null;
851
903
  }
852
904
  // Check photometric interpretation
@@ -888,6 +940,16 @@ class TIFFFormat {
888
940
  const decoder = new tiff_lzw_js_1.TIFFLZWDecoder(compressedData);
889
941
  pixelData = decoder.decompress();
890
942
  }
943
+ else if (compression === 32773) {
944
+ // PackBits compressed
945
+ const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
946
+ pixelData = (0, tiff_packbits_js_1.packBitsDecompress)(compressedData);
947
+ }
948
+ else if (compression === 8) {
949
+ // Deflate compressed
950
+ const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
951
+ pixelData = await (0, tiff_deflate_js_1.deflateDecompress)(compressedData);
952
+ }
891
953
  else {
892
954
  // Uncompressed
893
955
  pixelData = data.slice(stripOffset, stripOffset + stripByteCount);
@@ -1006,9 +1068,12 @@ class TIFFFormat {
1006
1068
  else if (compression === 5) {
1007
1069
  metadata.compression = "lzw";
1008
1070
  }
1009
- else if (compression === 7) {
1071
+ else if (compression === 7 || compression === 6) {
1010
1072
  metadata.compression = "jpeg";
1011
1073
  }
1074
+ else if (compression === 8) {
1075
+ metadata.compression = "deflate";
1076
+ }
1012
1077
  else if (compression === 32773) {
1013
1078
  metadata.compression = "packbits";
1014
1079
  }
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat, ImageMetadata, WebPEncodeOptions } from "../types.js";
1
+ import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, WebPEncoderOptions } from "../types.js";
2
2
  /**
3
3
  * WebP format handler
4
4
  * Implements a basic WebP decoder and encoder
@@ -19,14 +19,14 @@ export declare class WebPFormat implements ImageFormat {
19
19
  * @param data Raw WebP image data
20
20
  * @returns Decoded image data with RGBA pixels
21
21
  */
22
- decode(data: Uint8Array): Promise<ImageData>;
22
+ decode(data: Uint8Array, settings?: ImageDecoderOptions): Promise<ImageData>;
23
23
  /**
24
24
  * Encode RGBA image data to WebP format
25
25
  * @param imageData Image data to encode
26
26
  * @param options Optional WebP encoding options
27
27
  * @returns Encoded WebP image bytes
28
28
  */
29
- encode(imageData: ImageData, options?: WebPEncodeOptions): Promise<Uint8Array>;
29
+ encode(imageData: ImageData, options?: WebPEncoderOptions): Promise<Uint8Array>;
30
30
  private readUint24LE;
31
31
  private decodeUsingRuntime;
32
32
  private parseEXIF;
@@ -78,7 +78,7 @@ class WebPFormat {
78
78
  * @param data Raw WebP image data
79
79
  * @returns Decoded image data with RGBA pixels
80
80
  */
81
- async decode(data) {
81
+ async decode(data, settings) {
82
82
  if (!this.canDecode(data)) {
83
83
  throw new Error("Invalid WebP signature");
84
84
  }
@@ -144,7 +144,7 @@ class WebPFormat {
144
144
  (0, security_js_1.validateImageDimensions)(width, height);
145
145
  // For a pure JS implementation, we'd need to implement full WebP decoding
146
146
  // which is very complex. Instead, we'll use the browser/runtime's decoder.
147
- const rgba = await this.decodeUsingRuntime(data, width, height);
147
+ const rgba = await this.decodeUsingRuntime(data, width, height, settings);
148
148
  return {
149
149
  width,
150
150
  height,
@@ -207,9 +207,10 @@ class WebPFormat {
207
207
  readUint24LE(data, offset) {
208
208
  return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16);
209
209
  }
210
- async decodeUsingRuntime(data, _width, _height) {
210
+ async decodeUsingRuntime(data, _width, _height, settings) {
211
211
  // Try to use ImageDecoder API if available (Deno, modern browsers)
212
- if (typeof ImageDecoder !== "undefined") {
212
+ if (settings?.runtimeDecoding !== "never" &&
213
+ typeof ImageDecoder !== "undefined") {
213
214
  try {
214
215
  const decoder = new ImageDecoder({ data, type: "image/webp" });
215
216
  const result = await decoder.decode();
@@ -224,15 +225,17 @@ class WebPFormat {
224
225
  bitmap.close();
225
226
  return new Uint8Array(imageData.data.buffer);
226
227
  }
227
- catch (error) {
228
+ catch (_error) {
228
229
  // ImageDecoder API failed, fall through to pure JS decoder
229
- console.warn("WebP decoding with ImageDecoder failed, using pure JS decoder:", error);
230
230
  }
231
231
  }
232
- // Fallback to pure JavaScript decoder (VP8L lossless only)
232
+ // Fallback to pure JavaScript decoder (VP8L lossless only) with tolerant mode
233
233
  try {
234
234
  const { WebPDecoder } = await Promise.resolve().then(() => __importStar(require("../utils/webp_decoder.js")));
235
- const decoder = new WebPDecoder(data);
235
+ const decoder = new WebPDecoder(data, {
236
+ tolerantDecoding: settings?.tolerantDecoding ?? true,
237
+ onWarning: settings?.onWarning,
238
+ });
236
239
  const result = decoder.decode();
237
240
  return result.data;
238
241
  }
@@ -1,4 +1,4 @@
1
- import type { ImageFormat, ImageMetadata, MultiFrameImageData, ResizeOptions } from "./types.js";
1
+ import type { ImageDecoderOptions, ImageFormat, ImageMetadata, MultiFrameImageData, ResizeOptions } from "./types.js";
2
2
  /**
3
3
  * Main Image class for reading, manipulating, and saving images
4
4
  */
@@ -78,7 +78,11 @@ export declare class Image {
78
78
  * @param format Optional format hint (e.g., "png", "jpeg", "webp")
79
79
  * @returns Image instance
80
80
  */
81
- static decode(data: Uint8Array, format?: string): Promise<Image>;
81
+ static decode(data: Uint8Array): Promise<Image>;
82
+ static decode(data: Uint8Array, format: string): Promise<Image>;
83
+ static decode(data: Uint8Array, format: string, options?: ImageDecoderOptions): Promise<Image>;
84
+ static decode(data: Uint8Array, options?: ImageDecoderOptions): Promise<Image>;
85
+ private static decodeWithSettings;
82
86
  /**
83
87
  * Get supported metadata fields for a specific format
84
88
  * @param format Format name (e.g., "jpeg", "png", "webp")
@@ -108,7 +112,11 @@ export declare class Image {
108
112
  * @param format Optional format hint (e.g., "gif", "tiff")
109
113
  * @returns MultiFrameImageData with all frames
110
114
  */
111
- static decodeFrames(data: Uint8Array, format?: string): Promise<MultiFrameImageData>;
115
+ static decodeFrames(data: Uint8Array): Promise<MultiFrameImageData>;
116
+ static decodeFrames(data: Uint8Array, format: string): Promise<MultiFrameImageData>;
117
+ static decodeFrames(data: Uint8Array, format: string, options?: ImageDecoderOptions): Promise<MultiFrameImageData>;
118
+ static decodeFrames(data: Uint8Array, options?: ImageDecoderOptions): Promise<MultiFrameImageData>;
119
+ private static decodeFramesWithSettings;
112
120
  /**
113
121
  * Read all frames from a multi-frame image (GIF animation, multi-page TIFF)
114
122
  * @deprecated Use `decodeFrames()` instead. This method will be removed in a future version.
@@ -161,26 +161,37 @@ class Image {
161
161
  static getFormats() {
162
162
  return Image.formats;
163
163
  }
164
- /**
165
- * Decode an image from bytes
166
- * @param data Raw image data
167
- * @param format Optional format hint (e.g., "png", "jpeg", "webp")
168
- * @returns Image instance
169
- */
170
- static async decode(data, format) {
164
+ static async decode(data, formatOrOptions, options) {
165
+ // Backward-compatible overloads:
166
+ // - decode(data)
167
+ // - decode(data, "jpeg")
168
+ // New:
169
+ // - decode(data, { tolerantDecoding, onWarning, runtimeDecoding })
170
+ // - decode(data, "jpeg", { ...options })
171
+ if (typeof formatOrOptions === "string") {
172
+ return await Image.decodeWithSettings(data, formatOrOptions, options);
173
+ }
174
+ return await Image.decodeWithSettings(data, undefined, formatOrOptions);
175
+ }
176
+ static async decodeWithSettings(data, format, settings) {
171
177
  const image = new Image();
178
+ const normalizedSettings = {
179
+ tolerantDecoding: settings?.tolerantDecoding ?? true,
180
+ onWarning: settings?.onWarning,
181
+ runtimeDecoding: settings?.runtimeDecoding ?? "prefer",
182
+ };
172
183
  // Try specified format first
173
184
  if (format) {
174
185
  const handler = Image.formats.find((f) => f.name === format);
175
186
  if (handler && handler.canDecode(data)) {
176
- image.imageData = await handler.decode(data);
187
+ image.imageData = await handler.decode(data, normalizedSettings);
177
188
  return image;
178
189
  }
179
190
  }
180
191
  // Auto-detect format
181
192
  for (const handler of Image.formats) {
182
193
  if (handler.canDecode(data)) {
183
- image.imageData = await handler.decode(data);
194
+ image.imageData = await handler.decode(data, normalizedSettings);
184
195
  return image;
185
196
  }
186
197
  }
@@ -230,26 +241,31 @@ class Image {
230
241
  * @returns Image instance
231
242
  */
232
243
  static read(data, format) {
233
- return Image.decode(data, format);
244
+ return format ? Image.decode(data, format) : Image.decode(data);
234
245
  }
235
- /**
236
- * Decode all frames from a multi-frame image (GIF animation, multi-page TIFF)
237
- * @param data Raw image data
238
- * @param format Optional format hint (e.g., "gif", "tiff")
239
- * @returns MultiFrameImageData with all frames
240
- */
241
- static async decodeFrames(data, format) {
246
+ static async decodeFrames(data, formatOrOptions, options) {
247
+ if (typeof formatOrOptions === "string") {
248
+ return await Image.decodeFramesWithSettings(data, formatOrOptions, options);
249
+ }
250
+ return await Image.decodeFramesWithSettings(data, undefined, formatOrOptions);
251
+ }
252
+ static async decodeFramesWithSettings(data, format, settings) {
253
+ const normalizedSettings = {
254
+ tolerantDecoding: settings?.tolerantDecoding ?? true,
255
+ onWarning: settings?.onWarning,
256
+ runtimeDecoding: settings?.runtimeDecoding ?? "prefer",
257
+ };
242
258
  // Try specified format first
243
259
  if (format) {
244
260
  const handler = Image.formats.find((f) => f.name === format);
245
261
  if (handler && handler.canDecode(data) && handler.decodeFrames) {
246
- return await handler.decodeFrames(data);
262
+ return await handler.decodeFrames(data, normalizedSettings);
247
263
  }
248
264
  }
249
265
  // Auto-detect format
250
266
  for (const handler of Image.formats) {
251
267
  if (handler.canDecode(data) && handler.decodeFrames) {
252
- return await handler.decodeFrames(data);
268
+ return await handler.decodeFrames(data, normalizedSettings);
253
269
  }
254
270
  }
255
271
  throw new Error("Unsupported or unrecognized multi-frame image format");
@@ -262,7 +278,7 @@ class Image {
262
278
  * @returns MultiFrameImageData with all frames
263
279
  */
264
280
  static readFrames(data, format) {
265
- return Image.decodeFrames(data, format);
281
+ return format ? Image.decodeFrames(data, format) : Image.decodeFrames(data);
266
282
  }
267
283
  /**
268
284
  * Encode multi-frame image data to bytes in the specified format
@@ -138,7 +138,7 @@ export interface ResizeOptions {
138
138
  /**
139
139
  * Options for ASCII art encoding
140
140
  */
141
- export interface ASCIIOptions {
141
+ export interface ASCIIEncoderOptions {
142
142
  /** Target width in characters (default: 80) */
143
143
  width?: number;
144
144
  /** Character set to use (default: "simple") */
@@ -148,10 +148,21 @@ export interface ASCIIOptions {
148
148
  /** Whether to invert brightness (default: false) */
149
149
  invert?: boolean;
150
150
  }
151
+ /**
152
+ * Options for TIFF encoding.
153
+ */
154
+ export interface TIFFEncoderOptions {
155
+ /** Compression method: "none" (default), "lzw", "packbits", or "deflate" */
156
+ compression?: "none" | "lzw" | "packbits" | "deflate";
157
+ /** Encode as grayscale instead of RGB/RGBA (default: false) */
158
+ grayscale?: boolean;
159
+ /** Encode as RGB without alpha channel (default: false, ignored if grayscale is true) */
160
+ rgb?: boolean;
161
+ }
151
162
  /**
152
163
  * Options for WebP encoding
153
164
  */
154
- export interface WebPEncodeOptions {
165
+ export interface WebPEncoderOptions {
155
166
  /**
156
167
  * Encoding quality (1-100, default: 90)
157
168
  * - 100 = lossless (VP8L)
@@ -164,6 +175,47 @@ export interface WebPEncodeOptions {
164
175
  */
165
176
  lossless?: boolean;
166
177
  }
178
+ /**
179
+ * Options for JPEG encoding.
180
+ */
181
+ export interface JPEGEncoderOptions {
182
+ /**
183
+ * Encoding quality (1-100, default depends on encoder backend).
184
+ */
185
+ quality?: number;
186
+ /**
187
+ * Enable progressive JPEG output when using the pure-JS encoder.
188
+ * Runtime encoders do not currently expose a progressive toggle.
189
+ */
190
+ progressive?: boolean;
191
+ }
192
+ /**
193
+ * Common options for decode APIs.
194
+ *
195
+ * These options are runtime-agnostic and can be passed to `Image.decode()` and
196
+ * `Image.decodeFrames()`.
197
+ */
198
+ export interface ImageDecoderOptions {
199
+ /**
200
+ * Controls tolerant decoding in the pure-JS decoders.
201
+ *
202
+ * - true (default): try to recover from corruption and continue
203
+ * - false: strict mode (fail fast)
204
+ */
205
+ tolerantDecoding?: boolean;
206
+ /**
207
+ * Optional warning callback used by pure-JS decoders when non-fatal issues are
208
+ * encountered.
209
+ */
210
+ onWarning?: (message: string, details?: unknown) => void;
211
+ /**
212
+ * Runtime decoder strategy.
213
+ *
214
+ * - "prefer" (default): try runtime decoders first (ImageDecoder/Canvas), then fall back to pure JS
215
+ * - "never": skip runtime decoders and use pure JS when available
216
+ */
217
+ runtimeDecoding?: "prefer" | "never";
218
+ }
167
219
  /**
168
220
  * Image format handler interface
169
221
  */
@@ -177,7 +229,7 @@ export interface ImageFormat {
177
229
  * @param data Raw image data
178
230
  * @returns Decoded image data
179
231
  */
180
- decode(data: Uint8Array): Promise<ImageData>;
232
+ decode(data: Uint8Array, options?: ImageDecoderOptions): Promise<ImageData>;
181
233
  /**
182
234
  * Encode image data to bytes
183
235
  * @param imageData Image data to encode
@@ -196,7 +248,7 @@ export interface ImageFormat {
196
248
  * @param data Raw image data
197
249
  * @returns Decoded multi-frame image data
198
250
  */
199
- decodeFrames?(data: Uint8Array): Promise<MultiFrameImageData>;
251
+ decodeFrames?(data: Uint8Array, options?: ImageDecoderOptions): Promise<MultiFrameImageData>;
200
252
  /**
201
253
  * Encode multi-frame image data to bytes (optional)
202
254
  * @param imageData Multi-frame image data to encode
@@ -2,6 +2,7 @@
2
2
  * Pure JavaScript GIF decoder implementation
3
3
  * Supports GIF87a and GIF89a formats with LZW decompression
4
4
  */
5
+ import type { ImageDecoderOptions } from "../types.js";
5
6
  interface GIFImage {
6
7
  width: number;
7
8
  height: number;
@@ -19,7 +20,8 @@ interface GIFFrame {
19
20
  export declare class GIFDecoder {
20
21
  private data;
21
22
  private pos;
22
- constructor(data: Uint8Array);
23
+ private options;
24
+ constructor(data: Uint8Array, settings?: ImageDecoderOptions);
23
25
  private readByte;
24
26
  private readUint16LE;
25
27
  private readBytes;
@@ -36,6 +38,7 @@ export declare class GIFDecoder {
36
38
  frames: GIFFrame[];
37
39
  };
38
40
  private indexedToRGBA;
41
+ private decodeFrame;
39
42
  private deinterlace;
40
43
  }
41
44
  export {};