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
@@ -53,7 +53,7 @@ export class PAMFormat {
53
53
  * @param data Raw PAM image data
54
54
  * @returns Decoded image data with RGBA pixels
55
55
  */
56
- decode(data) {
56
+ decode(data, _options) {
57
57
  if (!this.canDecode(data)) {
58
58
  throw new Error("Invalid PAM signature");
59
59
  }
@@ -150,7 +150,7 @@ export class PAMFormat {
150
150
  * @param imageData Image data to encode
151
151
  * @returns Encoded PAM image bytes
152
152
  */
153
- encode(imageData) {
153
+ encode(imageData, _options) {
154
154
  const { width, height, data } = imageData;
155
155
  // Validate input
156
156
  if (data.length !== width * height * 4) {
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat, ImageMetadata } from "../types.js";
1
+ import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata } from "../types.js";
2
2
  /**
3
3
  * PCX format handler
4
4
  * Implements PCX decoder and encoder
@@ -7,8 +7,8 @@ export declare class PCXFormat implements ImageFormat {
7
7
  readonly name = "pcx";
8
8
  readonly mimeType = "image/x-pcx";
9
9
  canDecode(data: Uint8Array): boolean;
10
- decode(data: Uint8Array): Promise<ImageData>;
11
- encode(image: ImageData): Promise<Uint8Array>;
10
+ decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
11
+ encode(image: ImageData, _options?: unknown): Promise<Uint8Array>;
12
12
  /**
13
13
  * Get the list of metadata fields supported by PCX format
14
14
  */
@@ -28,7 +28,7 @@ export class PCXFormat {
28
28
  data[1] === 5) &&
29
29
  data[2] === 1;
30
30
  }
31
- decode(data) {
31
+ decode(data, _options) {
32
32
  if (!this.canDecode(data)) {
33
33
  return Promise.reject(new Error("Invalid PCX data"));
34
34
  }
@@ -134,7 +134,7 @@ export class PCXFormat {
134
134
  data: rgba,
135
135
  });
136
136
  }
137
- encode(image) {
137
+ encode(image, _options) {
138
138
  const width = image.width;
139
139
  const height = image.height;
140
140
  const data = image.data;
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat, ImageMetadata } from "../types.js";
1
+ import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata } from "../types.js";
2
2
  import { PNGBase } from "./png_base.js";
3
3
  /**
4
4
  * PNG format handler
@@ -20,13 +20,13 @@ export declare class PNGFormat extends PNGBase implements ImageFormat {
20
20
  * @param data Raw PNG image data
21
21
  * @returns Decoded image data with RGBA pixels
22
22
  */
23
- decode(data: Uint8Array): Promise<ImageData>;
23
+ decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
24
24
  /**
25
25
  * Encode RGBA image data to PNG format
26
26
  * @param imageData Image data to encode
27
27
  * @returns Encoded PNG image bytes
28
28
  */
29
- encode(imageData: ImageData): Promise<Uint8Array>;
29
+ encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
30
30
  /**
31
31
  * Get the list of metadata fields supported by PNG format
32
32
  * Delegates to PNGBase implementation
@@ -40,7 +40,7 @@ export class PNGFormat extends PNGBase {
40
40
  * @param data Raw PNG image data
41
41
  * @returns Decoded image data with RGBA pixels
42
42
  */
43
- async decode(data) {
43
+ async decode(data, _options) {
44
44
  if (!this.canDecode(data)) {
45
45
  throw new Error("Invalid PNG signature");
46
46
  }
@@ -112,7 +112,7 @@ export class PNGFormat extends PNGBase {
112
112
  * @param imageData Image data to encode
113
113
  * @returns Encoded PNG image bytes
114
114
  */
115
- async encode(imageData) {
115
+ async encode(imageData, _options) {
116
116
  const { width, height, data, metadata } = imageData;
117
117
  // Prepare IHDR chunk
118
118
  const ihdr = new Uint8Array(13);
@@ -105,9 +105,7 @@ export class PNGBase {
105
105
  for (let x = 0; x < scanline.length; x++) {
106
106
  const left = x >= bytesPerPixel ? scanline[x - bytesPerPixel] : 0;
107
107
  const above = prevLine ? prevLine[x] : 0;
108
- const upperLeft = (x >= bytesPerPixel && prevLine)
109
- ? prevLine[x - bytesPerPixel]
110
- : 0;
108
+ const upperLeft = (x >= bytesPerPixel && prevLine) ? prevLine[x - bytesPerPixel] : 0;
111
109
  switch (filterType) {
112
110
  case 0: // None
113
111
  break;
@@ -121,8 +119,7 @@ export class PNGBase {
121
119
  scanline[x] = (scanline[x] + Math.floor((left + above) / 2)) & 0xff;
122
120
  break;
123
121
  case 4: // Paeth
124
- scanline[x] =
125
- (scanline[x] + this.paethPredictor(left, above, upperLeft)) & 0xff;
122
+ scanline[x] = (scanline[x] + this.paethPredictor(left, above, upperLeft)) & 0xff;
126
123
  break;
127
124
  }
128
125
  }
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat, ImageMetadata } from "../types.js";
1
+ import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata } from "../types.js";
2
2
  /**
3
3
  * PPM format handler
4
4
  * Implements the Netpbm PPM (Portable PixMap) format.
@@ -34,14 +34,14 @@ export declare class PPMFormat implements ImageFormat {
34
34
  * @param data Raw PPM image data
35
35
  * @returns Decoded image data with RGBA pixels
36
36
  */
37
- decode(data: Uint8Array): Promise<ImageData>;
37
+ decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
38
38
  /**
39
39
  * Encode RGBA image data to PPM format (P6 binary)
40
40
  * Note: Alpha channel is ignored as PPM doesn't support transparency
41
41
  * @param imageData Image data to encode
42
42
  * @returns Encoded PPM image bytes
43
43
  */
44
- encode(imageData: ImageData): Promise<Uint8Array>;
44
+ encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
45
45
  /**
46
46
  * Check if a byte is whitespace (space, tab, CR, LF)
47
47
  */
@@ -56,7 +56,7 @@ export class PPMFormat {
56
56
  * @param data Raw PPM image data
57
57
  * @returns Decoded image data with RGBA pixels
58
58
  */
59
- decode(data) {
59
+ decode(data, _options) {
60
60
  if (!this.canDecode(data)) {
61
61
  throw new Error("Invalid PPM signature");
62
62
  }
@@ -208,7 +208,7 @@ export class PPMFormat {
208
208
  * @param imageData Image data to encode
209
209
  * @returns Encoded PPM image bytes
210
210
  */
211
- encode(imageData) {
211
+ encode(imageData, _options) {
212
212
  const { width, height, data } = imageData;
213
213
  // Validate input
214
214
  if (data.length !== width * height * 4) {
@@ -1,20 +1,9 @@
1
- import type { ImageData, ImageFormat, ImageMetadata, MultiFrameImageData } from "../types.js";
2
- /**
3
- * Options for TIFF encoding
4
- */
5
- export interface TIFFEncodeOptions {
6
- /** Compression method: "none" for uncompressed (default), "lzw" for LZW compression */
7
- compression?: "none" | "lzw";
8
- /** Encode as grayscale instead of RGB/RGBA */
9
- grayscale?: boolean;
10
- /** Encode as RGB without alpha channel (ignored if grayscale is true) */
11
- rgb?: boolean;
12
- }
1
+ import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, MultiFrameImageData } from "../types.js";
13
2
  /**
14
3
  * TIFF format handler
15
- * Implements pure-JS TIFF decoder for uncompressed and LZW-compressed RGB/RGBA images
16
- * and encoder for uncompressed and LZW-compressed RGBA TIFFs. Falls back to ImageDecoder
17
- * for other compressed TIFFs (JPEG, PackBits, etc.)
4
+ * Implements pure-JS TIFF decoder for uncompressed, LZW, PackBits, and Deflate-compressed RGB/RGBA images
5
+ * and encoder for uncompressed, LZW, PackBits, and Deflate-compressed RGBA TIFFs. Falls back to ImageDecoder
6
+ * for JPEG-compressed TIFFs.
18
7
  * Supports multi-page TIFF files.
19
8
  */
20
9
  export declare class TIFFFormat implements ImageFormat {
@@ -38,12 +27,12 @@ export declare class TIFFFormat implements ImageFormat {
38
27
  * @param data Raw TIFF image data
39
28
  * @returns Decoded image data with RGBA pixels of first page
40
29
  */
41
- decode(data: Uint8Array): Promise<ImageData>;
30
+ decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
42
31
  encode(imageData: ImageData, options?: unknown): Promise<Uint8Array>;
43
32
  /**
44
33
  * Decode all pages from a multi-page TIFF
45
34
  */
46
- decodeFrames(data: Uint8Array): Promise<MultiFrameImageData>;
35
+ decodeFrames(data: Uint8Array, _options?: ImageDecoderOptions): Promise<MultiFrameImageData>;
47
36
  /**
48
37
  * Encode multi-page TIFF
49
38
  */
@@ -65,7 +54,7 @@ export declare class TIFFFormat implements ImageFormat {
65
54
  private decodeUsingRuntime;
66
55
  private readString;
67
56
  /**
68
- * Pure JavaScript TIFF decoder for uncompressed and LZW-compressed RGB/RGBA images
57
+ * Pure JavaScript TIFF decoder for uncompressed, LZW, PackBits, and Deflate-compressed RGB/RGBA images
69
58
  * Returns null if the TIFF uses unsupported features
70
59
  */
71
60
  private decodePureJS;
@@ -1,12 +1,14 @@
1
1
  import { TIFFLZWDecoder, TIFFLZWEncoder } from "../utils/tiff_lzw.js";
2
+ import { packBitsCompress, packBitsDecompress } from "../utils/tiff_packbits.js";
3
+ import { deflateCompress, deflateDecompress } from "../utils/tiff_deflate.js";
2
4
  import { validateImageDimensions } from "../utils/security.js";
3
5
  // Constants for unit conversions
4
6
  const DEFAULT_DPI = 72;
5
7
  /**
6
8
  * TIFF format handler
7
- * Implements pure-JS TIFF decoder for uncompressed and LZW-compressed RGB/RGBA images
8
- * and encoder for uncompressed and LZW-compressed RGBA TIFFs. Falls back to ImageDecoder
9
- * for other compressed TIFFs (JPEG, PackBits, etc.)
9
+ * Implements pure-JS TIFF decoder for uncompressed, LZW, PackBits, and Deflate-compressed RGB/RGBA images
10
+ * and encoder for uncompressed, LZW, PackBits, and Deflate-compressed RGBA TIFFs. Falls back to ImageDecoder
11
+ * for JPEG-compressed TIFFs.
10
12
  * Supports multi-page TIFF files.
11
13
  */
12
14
  export class TIFFFormat {
@@ -52,7 +54,7 @@ export class TIFFFormat {
52
54
  * @param data Raw TIFF image data
53
55
  * @returns Decoded image data with RGBA pixels of first page
54
56
  */
55
- async decode(data) {
57
+ async decode(data, _options) {
56
58
  if (!this.canDecode(data)) {
57
59
  throw new Error("Invalid TIFF signature");
58
60
  }
@@ -128,7 +130,7 @@ export class TIFFFormat {
128
130
  metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
129
131
  };
130
132
  }
131
- encode(imageData, options) {
133
+ async encode(imageData, options) {
132
134
  const { width, height, data, metadata } = imageData;
133
135
  const opts = options;
134
136
  const compression = opts?.compression ?? "none";
@@ -172,6 +174,16 @@ export class TIFFFormat {
172
174
  pixelData = encoder.compress(sourceData);
173
175
  compressionCode = 5;
174
176
  }
177
+ else if (compression === "packbits") {
178
+ // PackBits compress the pixel data
179
+ pixelData = packBitsCompress(sourceData);
180
+ compressionCode = 32773;
181
+ }
182
+ else if (compression === "deflate") {
183
+ // Deflate compress the pixel data
184
+ pixelData = await deflateCompress(sourceData);
185
+ compressionCode = 8;
186
+ }
175
187
  else {
176
188
  // Uncompressed
177
189
  pixelData = sourceData;
@@ -329,7 +341,7 @@ export class TIFFFormat {
329
341
  /**
330
342
  * Decode all pages from a multi-page TIFF
331
343
  */
332
- async decodeFrames(data) {
344
+ async decodeFrames(data, _options) {
333
345
  if (!this.canDecode(data)) {
334
346
  throw new Error("Invalid TIFF signature");
335
347
  }
@@ -392,7 +404,7 @@ export class TIFFFormat {
392
404
  /**
393
405
  * Encode multi-page TIFF
394
406
  */
395
- encodeFrames(imageData, options) {
407
+ async encodeFrames(imageData, options) {
396
408
  const opts = options;
397
409
  const compression = opts?.compression ?? "none";
398
410
  if (imageData.frames.length === 0) {
@@ -417,6 +429,12 @@ export class TIFFFormat {
417
429
  const encoder = new TIFFLZWEncoder();
418
430
  pixelData = encoder.compress(frame.data);
419
431
  }
432
+ else if (compression === "packbits") {
433
+ pixelData = packBitsCompress(frame.data);
434
+ }
435
+ else if (compression === "deflate") {
436
+ pixelData = await deflateCompress(frame.data);
437
+ }
420
438
  else {
421
439
  pixelData = frame.data;
422
440
  }
@@ -453,7 +471,19 @@ export class TIFFFormat {
453
471
  this.writeIFDEntry(result, 0x0102, 3, 4, dataOffset);
454
472
  dataOffset += 8;
455
473
  // Compression
456
- const compressionCode = compression === "lzw" ? 5 : 1;
474
+ let compressionCode;
475
+ if (compression === "lzw") {
476
+ compressionCode = 5;
477
+ }
478
+ else if (compression === "packbits") {
479
+ compressionCode = 32773;
480
+ }
481
+ else if (compression === "deflate") {
482
+ compressionCode = 8;
483
+ }
484
+ else {
485
+ compressionCode = 1;
486
+ }
457
487
  this.writeIFDEntry(result, 0x0103, 3, 1, compressionCode);
458
488
  // PhotometricInterpretation
459
489
  this.writeIFDEntry(result, 0x0106, 3, 1, 2);
@@ -464,9 +494,19 @@ export class TIFFFormat {
464
494
  // RowsPerStrip
465
495
  this.writeIFDEntry(result, 0x0116, 4, 1, frame.height);
466
496
  // StripByteCounts
467
- const pixelDataSize = compression === "lzw"
468
- ? new TIFFLZWEncoder().compress(frame.data).length
469
- : frame.data.length;
497
+ let pixelDataSize;
498
+ if (compression === "lzw") {
499
+ pixelDataSize = new TIFFLZWEncoder().compress(frame.data).length;
500
+ }
501
+ else if (compression === "packbits") {
502
+ pixelDataSize = packBitsCompress(frame.data).length;
503
+ }
504
+ else if (compression === "deflate") {
505
+ pixelDataSize = (await deflateCompress(frame.data)).length;
506
+ }
507
+ else {
508
+ pixelDataSize = frame.data.length;
509
+ }
470
510
  this.writeIFDEntry(result, 0x0117, 4, 1, pixelDataSize);
471
511
  // XResolution
472
512
  const xResOffset = dataOffset;
@@ -569,7 +609,7 @@ export class TIFFFormat {
569
609
  const isLittleEndian = data[0] === 0x49;
570
610
  // Try pure JavaScript decoder first
571
611
  try {
572
- const pureJSResult = this.decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian);
612
+ const pureJSResult = await this.decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian);
573
613
  if (pureJSResult) {
574
614
  return pureJSResult;
575
615
  }
@@ -693,7 +733,7 @@ export class TIFFFormat {
693
733
  async decodeUsingRuntime(data, width, height) {
694
734
  // Try pure JavaScript decoder first for uncompressed TIFFs
695
735
  try {
696
- const pureJSResult = this.decodePureJS(data, width, height);
736
+ const pureJSResult = await this.decodePureJS(data, width, height);
697
737
  if (pureJSResult) {
698
738
  return pureJSResult;
699
739
  }
@@ -731,10 +771,10 @@ export class TIFFFormat {
731
771
  return new TextDecoder().decode(data.slice(offset, endIndex));
732
772
  }
733
773
  /**
734
- * Pure JavaScript TIFF decoder for uncompressed and LZW-compressed RGB/RGBA images
774
+ * Pure JavaScript TIFF decoder for uncompressed, LZW, PackBits, and Deflate-compressed RGB/RGBA images
735
775
  * Returns null if the TIFF uses unsupported features
736
776
  */
737
- decodePureJS(data, width, height) {
777
+ async decodePureJS(data, width, height) {
738
778
  // Validate minimum TIFF header size
739
779
  if (data.length < 8) {
740
780
  return null;
@@ -745,8 +785,9 @@ export class TIFFFormat {
745
785
  const ifdOffset = this.readUint32(data, 4, isLittleEndian);
746
786
  // Check compression
747
787
  const compression = this.getIFDValue(data, ifdOffset, 0x0103, isLittleEndian);
748
- if (compression !== 1 && compression !== 5) {
749
- // Only support uncompressed (1) and LZW (5)
788
+ if (compression !== 1 && compression !== 5 && compression !== 8 &&
789
+ compression !== 32773) {
790
+ // Support: 1 = uncompressed, 5 = LZW, 8 = Deflate, 32773 = PackBits
750
791
  return null;
751
792
  }
752
793
  // Check photometric interpretation
@@ -788,6 +829,16 @@ export class TIFFFormat {
788
829
  const decoder = new TIFFLZWDecoder(compressedData);
789
830
  pixelData = decoder.decompress();
790
831
  }
832
+ else if (compression === 32773) {
833
+ // PackBits compressed
834
+ const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
835
+ pixelData = packBitsDecompress(compressedData);
836
+ }
837
+ else if (compression === 8) {
838
+ // Deflate compressed
839
+ const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
840
+ pixelData = await deflateDecompress(compressedData);
841
+ }
791
842
  else {
792
843
  // Uncompressed
793
844
  pixelData = data.slice(stripOffset, stripOffset + stripByteCount);
@@ -839,11 +890,12 @@ export class TIFFFormat {
839
890
  * Pure JavaScript TIFF decoder for a specific IFD
840
891
  * Returns null if the TIFF uses unsupported features
841
892
  */
842
- decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian) {
893
+ async decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian) {
843
894
  // Check compression
844
895
  const compression = this.getIFDValue(data, ifdOffset, 0x0103, isLittleEndian);
845
- if (compression !== 1 && compression !== 5) {
846
- // Only support uncompressed (1) and LZW (5)
896
+ if (compression !== 1 && compression !== 5 && compression !== 8 &&
897
+ compression !== 32773) {
898
+ // Support: 1 = uncompressed, 5 = LZW, 8 = Deflate, 32773 = PackBits
847
899
  return null;
848
900
  }
849
901
  // Check photometric interpretation
@@ -885,6 +937,16 @@ export class TIFFFormat {
885
937
  const decoder = new TIFFLZWDecoder(compressedData);
886
938
  pixelData = decoder.decompress();
887
939
  }
940
+ else if (compression === 32773) {
941
+ // PackBits compressed
942
+ const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
943
+ pixelData = packBitsDecompress(compressedData);
944
+ }
945
+ else if (compression === 8) {
946
+ // Deflate compressed
947
+ const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
948
+ pixelData = await deflateDecompress(compressedData);
949
+ }
888
950
  else {
889
951
  // Uncompressed
890
952
  pixelData = data.slice(stripOffset, stripOffset + stripByteCount);
@@ -1003,9 +1065,12 @@ export class TIFFFormat {
1003
1065
  else if (compression === 5) {
1004
1066
  metadata.compression = "lzw";
1005
1067
  }
1006
- else if (compression === 7) {
1068
+ else if (compression === 7 || compression === 6) {
1007
1069
  metadata.compression = "jpeg";
1008
1070
  }
1071
+ else if (compression === 8) {
1072
+ metadata.compression = "deflate";
1073
+ }
1009
1074
  else if (compression === 32773) {
1010
1075
  metadata.compression = "packbits";
1011
1076
  }
@@ -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;
@@ -42,7 +42,7 @@ export class WebPFormat {
42
42
  * @param data Raw WebP image data
43
43
  * @returns Decoded image data with RGBA pixels
44
44
  */
45
- async decode(data) {
45
+ async decode(data, settings) {
46
46
  if (!this.canDecode(data)) {
47
47
  throw new Error("Invalid WebP signature");
48
48
  }
@@ -108,7 +108,7 @@ export class WebPFormat {
108
108
  validateImageDimensions(width, height);
109
109
  // For a pure JS implementation, we'd need to implement full WebP decoding
110
110
  // which is very complex. Instead, we'll use the browser/runtime's decoder.
111
- const rgba = await this.decodeUsingRuntime(data, width, height);
111
+ const rgba = await this.decodeUsingRuntime(data, width, height, settings);
112
112
  return {
113
113
  width,
114
114
  height,
@@ -171,9 +171,10 @@ export class WebPFormat {
171
171
  readUint24LE(data, offset) {
172
172
  return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16);
173
173
  }
174
- async decodeUsingRuntime(data, _width, _height) {
174
+ async decodeUsingRuntime(data, _width, _height, settings) {
175
175
  // Try to use ImageDecoder API if available (Deno, modern browsers)
176
- if (typeof ImageDecoder !== "undefined") {
176
+ if (settings?.runtimeDecoding !== "never" &&
177
+ typeof ImageDecoder !== "undefined") {
177
178
  try {
178
179
  const decoder = new ImageDecoder({ data, type: "image/webp" });
179
180
  const result = await decoder.decode();
@@ -188,15 +189,17 @@ export class WebPFormat {
188
189
  bitmap.close();
189
190
  return new Uint8Array(imageData.data.buffer);
190
191
  }
191
- catch (error) {
192
+ catch (_error) {
192
193
  // ImageDecoder API failed, fall through to pure JS decoder
193
- console.warn("WebP decoding with ImageDecoder failed, using pure JS decoder:", error);
194
194
  }
195
195
  }
196
- // Fallback to pure JavaScript decoder (VP8L lossless only)
196
+ // Fallback to pure JavaScript decoder (VP8L lossless only) with tolerant mode
197
197
  try {
198
198
  const { WebPDecoder } = await import("../utils/webp_decoder.js");
199
- const decoder = new WebPDecoder(data);
199
+ const decoder = new WebPDecoder(data, {
200
+ tolerantDecoding: settings?.tolerantDecoding ?? true,
201
+ onWarning: settings?.onWarning,
202
+ });
200
203
  const result = decoder.decode();
201
204
  return result.data;
202
205
  }
@@ -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.
package/esm/src/image.js CHANGED
@@ -1,4 +1,4 @@
1
- import { resizeBicubic, resizeBilinear, resizeNearest, } from "./utils/resize.js";
1
+ import { resizeBicubic, resizeBilinear, resizeNearest } from "./utils/resize.js";
2
2
  import { adjustBrightness, adjustContrast, adjustExposure, adjustHue, adjustSaturation, boxBlur, composite, crop, fillRect, flipHorizontal, flipVertical, gaussianBlur, grayscale, invert, medianFilter, rotate180, rotate270, rotate90, sepia, sharpen, } from "./utils/image_processing.js";
3
3
  import { PNGFormat } from "./formats/png.js";
4
4
  import { APNGFormat } from "./formats/apng.js";
@@ -158,26 +158,37 @@ export class Image {
158
158
  static getFormats() {
159
159
  return Image.formats;
160
160
  }
161
- /**
162
- * Decode an image from bytes
163
- * @param data Raw image data
164
- * @param format Optional format hint (e.g., "png", "jpeg", "webp")
165
- * @returns Image instance
166
- */
167
- static async decode(data, format) {
161
+ static async decode(data, formatOrOptions, options) {
162
+ // Backward-compatible overloads:
163
+ // - decode(data)
164
+ // - decode(data, "jpeg")
165
+ // New:
166
+ // - decode(data, { tolerantDecoding, onWarning, runtimeDecoding })
167
+ // - decode(data, "jpeg", { ...options })
168
+ if (typeof formatOrOptions === "string") {
169
+ return await Image.decodeWithSettings(data, formatOrOptions, options);
170
+ }
171
+ return await Image.decodeWithSettings(data, undefined, formatOrOptions);
172
+ }
173
+ static async decodeWithSettings(data, format, settings) {
168
174
  const image = new Image();
175
+ const normalizedSettings = {
176
+ tolerantDecoding: settings?.tolerantDecoding ?? true,
177
+ onWarning: settings?.onWarning,
178
+ runtimeDecoding: settings?.runtimeDecoding ?? "prefer",
179
+ };
169
180
  // Try specified format first
170
181
  if (format) {
171
182
  const handler = Image.formats.find((f) => f.name === format);
172
183
  if (handler && handler.canDecode(data)) {
173
- image.imageData = await handler.decode(data);
184
+ image.imageData = await handler.decode(data, normalizedSettings);
174
185
  return image;
175
186
  }
176
187
  }
177
188
  // Auto-detect format
178
189
  for (const handler of Image.formats) {
179
190
  if (handler.canDecode(data)) {
180
- image.imageData = await handler.decode(data);
191
+ image.imageData = await handler.decode(data, normalizedSettings);
181
192
  return image;
182
193
  }
183
194
  }
@@ -227,26 +238,31 @@ export class Image {
227
238
  * @returns Image instance
228
239
  */
229
240
  static read(data, format) {
230
- return Image.decode(data, format);
241
+ return format ? Image.decode(data, format) : Image.decode(data);
231
242
  }
232
- /**
233
- * Decode all frames from a multi-frame image (GIF animation, multi-page TIFF)
234
- * @param data Raw image data
235
- * @param format Optional format hint (e.g., "gif", "tiff")
236
- * @returns MultiFrameImageData with all frames
237
- */
238
- static async decodeFrames(data, format) {
243
+ static async decodeFrames(data, formatOrOptions, options) {
244
+ if (typeof formatOrOptions === "string") {
245
+ return await Image.decodeFramesWithSettings(data, formatOrOptions, options);
246
+ }
247
+ return await Image.decodeFramesWithSettings(data, undefined, formatOrOptions);
248
+ }
249
+ static async decodeFramesWithSettings(data, format, settings) {
250
+ const normalizedSettings = {
251
+ tolerantDecoding: settings?.tolerantDecoding ?? true,
252
+ onWarning: settings?.onWarning,
253
+ runtimeDecoding: settings?.runtimeDecoding ?? "prefer",
254
+ };
239
255
  // Try specified format first
240
256
  if (format) {
241
257
  const handler = Image.formats.find((f) => f.name === format);
242
258
  if (handler && handler.canDecode(data) && handler.decodeFrames) {
243
- return await handler.decodeFrames(data);
259
+ return await handler.decodeFrames(data, normalizedSettings);
244
260
  }
245
261
  }
246
262
  // Auto-detect format
247
263
  for (const handler of Image.formats) {
248
264
  if (handler.canDecode(data) && handler.decodeFrames) {
249
- return await handler.decodeFrames(data);
265
+ return await handler.decodeFrames(data, normalizedSettings);
250
266
  }
251
267
  }
252
268
  throw new Error("Unsupported or unrecognized multi-frame image format");
@@ -259,7 +275,7 @@ export class Image {
259
275
  * @returns MultiFrameImageData with all frames
260
276
  */
261
277
  static readFrames(data, format) {
262
- return Image.decodeFrames(data, format);
278
+ return format ? Image.decodeFrames(data, format) : Image.decodeFrames(data);
263
279
  }
264
280
  /**
265
281
  * Encode multi-frame image data to bytes in the specified format