cross-image 0.2.4 → 0.4.1

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 (117) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +615 -333
  3. package/esm/mod.d.ts +6 -4
  4. package/esm/mod.js +4 -2
  5. package/esm/src/formats/apng.d.ts +7 -5
  6. package/esm/src/formats/apng.js +15 -10
  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 +17 -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 +5 -5
  16. package/esm/src/formats/gif.js +17 -13
  17. package/esm/src/formats/heic.d.ts +3 -3
  18. package/esm/src/formats/heic.js +17 -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 +4 -3
  28. package/esm/src/formats/png.js +9 -3
  29. package/esm/src/formats/png_base.d.ts +42 -1
  30. package/esm/src/formats/png_base.js +200 -10
  31. package/esm/src/formats/ppm.d.ts +3 -3
  32. package/esm/src/formats/ppm.js +2 -2
  33. package/esm/src/formats/tiff.d.ts +7 -18
  34. package/esm/src/formats/tiff.js +162 -27
  35. package/esm/src/formats/webp.d.ts +3 -3
  36. package/esm/src/formats/webp.js +11 -8
  37. package/esm/src/image.d.ts +26 -3
  38. package/esm/src/image.js +66 -22
  39. package/esm/src/types.d.ts +122 -4
  40. package/esm/src/utils/base64.d.ts +32 -0
  41. package/esm/src/utils/base64.js +173 -0
  42. package/esm/src/utils/gif_decoder.d.ts +4 -1
  43. package/esm/src/utils/gif_decoder.js +91 -65
  44. package/esm/src/utils/gif_encoder.d.ts +3 -1
  45. package/esm/src/utils/gif_encoder.js +4 -2
  46. package/esm/src/utils/image_processing.d.ts +31 -0
  47. package/esm/src/utils/image_processing.js +232 -70
  48. package/esm/src/utils/jpeg_decoder.d.ts +17 -4
  49. package/esm/src/utils/jpeg_decoder.js +448 -83
  50. package/esm/src/utils/jpeg_encoder.d.ts +15 -1
  51. package/esm/src/utils/jpeg_encoder.js +263 -24
  52. package/esm/src/utils/resize.js +51 -20
  53. package/esm/src/utils/tiff_deflate.d.ts +18 -0
  54. package/esm/src/utils/tiff_deflate.js +27 -0
  55. package/esm/src/utils/tiff_packbits.d.ts +24 -0
  56. package/esm/src/utils/tiff_packbits.js +90 -0
  57. package/esm/src/utils/webp_decoder.d.ts +3 -1
  58. package/esm/src/utils/webp_decoder.js +144 -63
  59. package/esm/src/utils/webp_encoder.js +5 -11
  60. package/package.json +1 -1
  61. package/script/mod.d.ts +6 -4
  62. package/script/mod.js +13 -3
  63. package/script/src/formats/apng.d.ts +7 -5
  64. package/script/src/formats/apng.js +15 -10
  65. package/script/src/formats/ascii.d.ts +3 -3
  66. package/script/src/formats/ascii.js +1 -1
  67. package/script/src/formats/avif.d.ts +3 -3
  68. package/script/src/formats/avif.js +17 -7
  69. package/script/src/formats/bmp.d.ts +3 -3
  70. package/script/src/formats/bmp.js +2 -2
  71. package/script/src/formats/dng.d.ts +1 -1
  72. package/script/src/formats/dng.js +1 -1
  73. package/script/src/formats/gif.d.ts +5 -5
  74. package/script/src/formats/gif.js +17 -13
  75. package/script/src/formats/heic.d.ts +3 -3
  76. package/script/src/formats/heic.js +17 -7
  77. package/script/src/formats/ico.d.ts +3 -3
  78. package/script/src/formats/ico.js +4 -4
  79. package/script/src/formats/jpeg.d.ts +3 -3
  80. package/script/src/formats/jpeg.js +23 -11
  81. package/script/src/formats/pam.d.ts +3 -3
  82. package/script/src/formats/pam.js +2 -2
  83. package/script/src/formats/pcx.d.ts +3 -3
  84. package/script/src/formats/pcx.js +2 -2
  85. package/script/src/formats/png.d.ts +4 -3
  86. package/script/src/formats/png.js +9 -3
  87. package/script/src/formats/png_base.d.ts +42 -1
  88. package/script/src/formats/png_base.js +200 -10
  89. package/script/src/formats/ppm.d.ts +3 -3
  90. package/script/src/formats/ppm.js +2 -2
  91. package/script/src/formats/tiff.d.ts +7 -18
  92. package/script/src/formats/tiff.js +162 -27
  93. package/script/src/formats/webp.d.ts +3 -3
  94. package/script/src/formats/webp.js +11 -8
  95. package/script/src/image.d.ts +26 -3
  96. package/script/src/image.js +64 -20
  97. package/script/src/types.d.ts +122 -4
  98. package/script/src/utils/base64.d.ts +32 -0
  99. package/script/src/utils/base64.js +179 -0
  100. package/script/src/utils/gif_decoder.d.ts +4 -1
  101. package/script/src/utils/gif_decoder.js +91 -65
  102. package/script/src/utils/gif_encoder.d.ts +3 -1
  103. package/script/src/utils/gif_encoder.js +4 -2
  104. package/script/src/utils/image_processing.d.ts +31 -0
  105. package/script/src/utils/image_processing.js +236 -70
  106. package/script/src/utils/jpeg_decoder.d.ts +17 -4
  107. package/script/src/utils/jpeg_decoder.js +448 -83
  108. package/script/src/utils/jpeg_encoder.d.ts +15 -1
  109. package/script/src/utils/jpeg_encoder.js +263 -24
  110. package/script/src/utils/resize.js +51 -20
  111. package/script/src/utils/tiff_deflate.d.ts +18 -0
  112. package/script/src/utils/tiff_deflate.js +31 -0
  113. package/script/src/utils/tiff_packbits.d.ts +24 -0
  114. package/script/src/utils/tiff_packbits.js +94 -0
  115. package/script/src/utils/webp_decoder.d.ts +3 -1
  116. package/script/src/utils/webp_decoder.js +144 -63
  117. package/script/src/utils/webp_encoder.js +5 -11
@@ -2,14 +2,17 @@
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");
8
+ const image_processing_js_1 = require("../utils/image_processing.js");
6
9
  // Constants for unit conversions
7
10
  const DEFAULT_DPI = 72;
8
11
  /**
9
12
  * 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.)
13
+ * Implements pure-JS TIFF decoder for uncompressed, LZW, PackBits, and Deflate-compressed RGB/RGBA images
14
+ * and encoder for uncompressed, LZW, PackBits, and Deflate-compressed RGBA TIFFs. Falls back to ImageDecoder
15
+ * for JPEG-compressed TIFFs.
13
16
  * Supports multi-page TIFF files.
14
17
  */
15
18
  class TIFFFormat {
@@ -55,7 +58,7 @@ class TIFFFormat {
55
58
  * @param data Raw TIFF image data
56
59
  * @returns Decoded image data with RGBA pixels of first page
57
60
  */
58
- async decode(data) {
61
+ async decode(data, _options) {
59
62
  if (!this.canDecode(data)) {
60
63
  throw new Error("Invalid TIFF signature");
61
64
  }
@@ -131,12 +134,13 @@ class TIFFFormat {
131
134
  metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
132
135
  };
133
136
  }
134
- encode(imageData, options) {
137
+ async encode(imageData, options) {
135
138
  const { width, height, data, metadata } = imageData;
136
139
  const opts = options;
137
140
  const compression = opts?.compression ?? "none";
138
141
  const grayscale = opts?.grayscale ?? false;
139
142
  const rgb = opts?.rgb ?? false;
143
+ const cmyk = opts?.cmyk ?? false;
140
144
  // Convert RGBA to grayscale if requested
141
145
  let sourceData;
142
146
  let samplesPerPixel;
@@ -151,6 +155,16 @@ class TIFFFormat {
151
155
  sourceData[i] = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
152
156
  }
153
157
  }
158
+ else if (cmyk) {
159
+ // Convert RGBA to CMYK
160
+ const cmykData = (0, image_processing_js_1.rgbaToCmyk)(data);
161
+ sourceData = new Uint8Array(width * height * 4);
162
+ samplesPerPixel = 4;
163
+ // Convert Float32Array CMYK (0-1) to Uint8Array (0-255)
164
+ for (let i = 0; i < cmykData.length; i++) {
165
+ sourceData[i] = Math.round(cmykData[i] * 255);
166
+ }
167
+ }
154
168
  else if (rgb) {
155
169
  // Convert RGBA to RGB (strip alpha channel)
156
170
  sourceData = new Uint8Array(width * height * 3);
@@ -175,6 +189,16 @@ class TIFFFormat {
175
189
  pixelData = encoder.compress(sourceData);
176
190
  compressionCode = 5;
177
191
  }
192
+ else if (compression === "packbits") {
193
+ // PackBits compress the pixel data
194
+ pixelData = (0, tiff_packbits_js_1.packBitsCompress)(sourceData);
195
+ compressionCode = 32773;
196
+ }
197
+ else if (compression === "deflate") {
198
+ // Deflate compress the pixel data
199
+ pixelData = await (0, tiff_deflate_js_1.deflateCompress)(sourceData);
200
+ compressionCode = 8;
201
+ }
178
202
  else {
179
203
  // Uncompressed
180
204
  pixelData = sourceData;
@@ -232,8 +256,8 @@ class TIFFFormat {
232
256
  }
233
257
  // Compression (0x0103) - 1 = uncompressed, 5 = LZW
234
258
  this.writeIFDEntry(result, 0x0103, 3, 1, compressionCode);
235
- // PhotometricInterpretation (0x0106) - 1 = BlackIsZero (grayscale), 2 = RGB
236
- this.writeIFDEntry(result, 0x0106, 3, 1, grayscale ? 1 : 2);
259
+ // PhotometricInterpretation (0x0106) - 1 = BlackIsZero (grayscale), 2 = RGB, 5 = CMYK
260
+ this.writeIFDEntry(result, 0x0106, 3, 1, grayscale ? 1 : (cmyk ? 5 : 2));
237
261
  // StripOffsets (0x0111)
238
262
  this.writeIFDEntry(result, 0x0111, 4, 1, 8);
239
263
  // SamplesPerPixel (0x0115) - 1 for grayscale, 3 for RGB, 4 for RGBA
@@ -332,7 +356,7 @@ class TIFFFormat {
332
356
  /**
333
357
  * Decode all pages from a multi-page TIFF
334
358
  */
335
- async decodeFrames(data) {
359
+ async decodeFrames(data, _options) {
336
360
  if (!this.canDecode(data)) {
337
361
  throw new Error("Invalid TIFF signature");
338
362
  }
@@ -395,7 +419,7 @@ class TIFFFormat {
395
419
  /**
396
420
  * Encode multi-page TIFF
397
421
  */
398
- encodeFrames(imageData, options) {
422
+ async encodeFrames(imageData, options) {
399
423
  const opts = options;
400
424
  const compression = opts?.compression ?? "none";
401
425
  if (imageData.frames.length === 0) {
@@ -420,6 +444,12 @@ class TIFFFormat {
420
444
  const encoder = new tiff_lzw_js_1.TIFFLZWEncoder();
421
445
  pixelData = encoder.compress(frame.data);
422
446
  }
447
+ else if (compression === "packbits") {
448
+ pixelData = (0, tiff_packbits_js_1.packBitsCompress)(frame.data);
449
+ }
450
+ else if (compression === "deflate") {
451
+ pixelData = await (0, tiff_deflate_js_1.deflateCompress)(frame.data);
452
+ }
423
453
  else {
424
454
  pixelData = frame.data;
425
455
  }
@@ -456,7 +486,19 @@ class TIFFFormat {
456
486
  this.writeIFDEntry(result, 0x0102, 3, 4, dataOffset);
457
487
  dataOffset += 8;
458
488
  // Compression
459
- const compressionCode = compression === "lzw" ? 5 : 1;
489
+ let compressionCode;
490
+ if (compression === "lzw") {
491
+ compressionCode = 5;
492
+ }
493
+ else if (compression === "packbits") {
494
+ compressionCode = 32773;
495
+ }
496
+ else if (compression === "deflate") {
497
+ compressionCode = 8;
498
+ }
499
+ else {
500
+ compressionCode = 1;
501
+ }
460
502
  this.writeIFDEntry(result, 0x0103, 3, 1, compressionCode);
461
503
  // PhotometricInterpretation
462
504
  this.writeIFDEntry(result, 0x0106, 3, 1, 2);
@@ -467,9 +509,19 @@ class TIFFFormat {
467
509
  // RowsPerStrip
468
510
  this.writeIFDEntry(result, 0x0116, 4, 1, frame.height);
469
511
  // StripByteCounts
470
- const pixelDataSize = compression === "lzw"
471
- ? new tiff_lzw_js_1.TIFFLZWEncoder().compress(frame.data).length
472
- : frame.data.length;
512
+ let pixelDataSize;
513
+ if (compression === "lzw") {
514
+ pixelDataSize = new tiff_lzw_js_1.TIFFLZWEncoder().compress(frame.data).length;
515
+ }
516
+ else if (compression === "packbits") {
517
+ pixelDataSize = (0, tiff_packbits_js_1.packBitsCompress)(frame.data).length;
518
+ }
519
+ else if (compression === "deflate") {
520
+ pixelDataSize = (await (0, tiff_deflate_js_1.deflateCompress)(frame.data)).length;
521
+ }
522
+ else {
523
+ pixelDataSize = frame.data.length;
524
+ }
473
525
  this.writeIFDEntry(result, 0x0117, 4, 1, pixelDataSize);
474
526
  // XResolution
475
527
  const xResOffset = dataOffset;
@@ -572,7 +624,7 @@ class TIFFFormat {
572
624
  const isLittleEndian = data[0] === 0x49;
573
625
  // Try pure JavaScript decoder first
574
626
  try {
575
- const pureJSResult = this.decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian);
627
+ const pureJSResult = await this.decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian);
576
628
  if (pureJSResult) {
577
629
  return pureJSResult;
578
630
  }
@@ -696,7 +748,7 @@ class TIFFFormat {
696
748
  async decodeUsingRuntime(data, width, height) {
697
749
  // Try pure JavaScript decoder first for uncompressed TIFFs
698
750
  try {
699
- const pureJSResult = this.decodePureJS(data, width, height);
751
+ const pureJSResult = await this.decodePureJS(data, width, height);
700
752
  if (pureJSResult) {
701
753
  return pureJSResult;
702
754
  }
@@ -734,10 +786,10 @@ class TIFFFormat {
734
786
  return new TextDecoder().decode(data.slice(offset, endIndex));
735
787
  }
736
788
  /**
737
- * Pure JavaScript TIFF decoder for uncompressed and LZW-compressed RGB/RGBA images
789
+ * Pure JavaScript TIFF decoder for uncompressed, LZW, PackBits, and Deflate-compressed RGB/RGBA images
738
790
  * Returns null if the TIFF uses unsupported features
739
791
  */
740
- decodePureJS(data, width, height) {
792
+ async decodePureJS(data, width, height) {
741
793
  // Validate minimum TIFF header size
742
794
  if (data.length < 8) {
743
795
  return null;
@@ -748,20 +800,23 @@ class TIFFFormat {
748
800
  const ifdOffset = this.readUint32(data, 4, isLittleEndian);
749
801
  // Check compression
750
802
  const compression = this.getIFDValue(data, ifdOffset, 0x0103, isLittleEndian);
751
- if (compression !== 1 && compression !== 5) {
752
- // Only support uncompressed (1) and LZW (5)
803
+ if (compression !== 1 && compression !== 5 && compression !== 8 &&
804
+ compression !== 32773) {
805
+ // Support: 1 = uncompressed, 5 = LZW, 8 = Deflate, 32773 = PackBits
753
806
  return null;
754
807
  }
755
808
  // Check photometric interpretation
756
809
  const photometric = this.getIFDValue(data, ifdOffset, 0x0106, isLittleEndian);
757
- if (photometric !== 0 && photometric !== 1 && photometric !== 2) {
758
- // Support: 0 = WhiteIsZero, 1 = BlackIsZero, 2 = RGB
810
+ if (photometric !== 0 && photometric !== 1 && photometric !== 2 &&
811
+ photometric !== 5) {
812
+ // Support: 0 = WhiteIsZero, 1 = BlackIsZero, 2 = RGB, 5 = CMYK
759
813
  return null;
760
814
  }
761
815
  // Get samples per pixel
762
816
  const samplesPerPixel = this.getIFDValue(data, ifdOffset, 0x0115, isLittleEndian);
763
817
  // For grayscale (photometric 0 or 1), expect 1 sample per pixel
764
818
  // For RGB, expect 3 or 4 samples per pixel
819
+ // For CMYK, expect 4 samples per pixel
765
820
  if (!samplesPerPixel) {
766
821
  return null;
767
822
  }
@@ -773,6 +828,10 @@ class TIFFFormat {
773
828
  // RGB requires 3 or 4 samples per pixel
774
829
  return null;
775
830
  }
831
+ if (photometric === 5 && samplesPerPixel !== 4) {
832
+ // CMYK requires 4 samples per pixel
833
+ return null;
834
+ }
776
835
  // Get strip offset
777
836
  const stripOffset = this.getIFDValue(data, ifdOffset, 0x0111, isLittleEndian);
778
837
  if (!stripOffset || stripOffset >= data.length) {
@@ -791,6 +850,16 @@ class TIFFFormat {
791
850
  const decoder = new tiff_lzw_js_1.TIFFLZWDecoder(compressedData);
792
851
  pixelData = decoder.decompress();
793
852
  }
853
+ else if (compression === 32773) {
854
+ // PackBits compressed
855
+ const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
856
+ pixelData = (0, tiff_packbits_js_1.packBitsDecompress)(compressedData);
857
+ }
858
+ else if (compression === 8) {
859
+ // Deflate compressed
860
+ const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
861
+ pixelData = await (0, tiff_deflate_js_1.deflateDecompress)(compressedData);
862
+ }
794
863
  else {
795
864
  // Uncompressed
796
865
  pixelData = data.slice(stripOffset, stripOffset + stripByteCount);
@@ -820,6 +889,29 @@ class TIFFFormat {
820
889
  }
821
890
  }
822
891
  }
892
+ else if (photometric === 5) {
893
+ // CMYK image - convert to RGB
894
+ for (let y = 0; y < height; y++) {
895
+ for (let x = 0; x < width; x++) {
896
+ const dstIdx = (y * width + x) * 4;
897
+ if (srcPos + 4 > pixelData.length) {
898
+ return null; // Not enough data
899
+ }
900
+ // TIFF stores CMYK in order, values are 0-255
901
+ // Convert to 0-1 range for conversion
902
+ const c = pixelData[srcPos++] / 255;
903
+ const m = pixelData[srcPos++] / 255;
904
+ const yVal = pixelData[srcPos++] / 255;
905
+ const k = pixelData[srcPos++] / 255;
906
+ // Convert CMYK to RGB
907
+ const [r, g, b] = (0, image_processing_js_1.cmykToRgb)(c, m, yVal, k);
908
+ rgba[dstIdx] = r; // R
909
+ rgba[dstIdx + 1] = g; // G
910
+ rgba[dstIdx + 2] = b; // B
911
+ rgba[dstIdx + 3] = 255; // A (opaque)
912
+ }
913
+ }
914
+ }
823
915
  else {
824
916
  // RGB/RGBA image
825
917
  for (let y = 0; y < height; y++) {
@@ -842,23 +934,26 @@ class TIFFFormat {
842
934
  * Pure JavaScript TIFF decoder for a specific IFD
843
935
  * Returns null if the TIFF uses unsupported features
844
936
  */
845
- decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian) {
937
+ async decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian) {
846
938
  // Check compression
847
939
  const compression = this.getIFDValue(data, ifdOffset, 0x0103, isLittleEndian);
848
- if (compression !== 1 && compression !== 5) {
849
- // Only support uncompressed (1) and LZW (5)
940
+ if (compression !== 1 && compression !== 5 && compression !== 8 &&
941
+ compression !== 32773) {
942
+ // Support: 1 = uncompressed, 5 = LZW, 8 = Deflate, 32773 = PackBits
850
943
  return null;
851
944
  }
852
945
  // Check photometric interpretation
853
946
  const photometric = this.getIFDValue(data, ifdOffset, 0x0106, isLittleEndian);
854
- if (photometric !== 0 && photometric !== 1 && photometric !== 2) {
855
- // Support: 0 = WhiteIsZero, 1 = BlackIsZero, 2 = RGB
947
+ if (photometric !== 0 && photometric !== 1 && photometric !== 2 &&
948
+ photometric !== 5) {
949
+ // Support: 0 = WhiteIsZero, 1 = BlackIsZero, 2 = RGB, 5 = CMYK
856
950
  return null;
857
951
  }
858
952
  // Get samples per pixel
859
953
  const samplesPerPixel = this.getIFDValue(data, ifdOffset, 0x0115, isLittleEndian);
860
954
  // For grayscale (photometric 0 or 1), expect 1 sample per pixel
861
955
  // For RGB, expect 3 or 4 samples per pixel
956
+ // For CMYK, expect 4 samples per pixel
862
957
  if (!samplesPerPixel) {
863
958
  return null;
864
959
  }
@@ -870,6 +965,10 @@ class TIFFFormat {
870
965
  // RGB requires 3 or 4 samples per pixel
871
966
  return null;
872
967
  }
968
+ if (photometric === 5 && samplesPerPixel !== 4) {
969
+ // CMYK requires 4 samples per pixel
970
+ return null;
971
+ }
873
972
  // Get strip offset
874
973
  const stripOffset = this.getIFDValue(data, ifdOffset, 0x0111, isLittleEndian);
875
974
  if (!stripOffset || stripOffset >= data.length) {
@@ -888,6 +987,16 @@ class TIFFFormat {
888
987
  const decoder = new tiff_lzw_js_1.TIFFLZWDecoder(compressedData);
889
988
  pixelData = decoder.decompress();
890
989
  }
990
+ else if (compression === 32773) {
991
+ // PackBits compressed
992
+ const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
993
+ pixelData = (0, tiff_packbits_js_1.packBitsDecompress)(compressedData);
994
+ }
995
+ else if (compression === 8) {
996
+ // Deflate compressed
997
+ const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
998
+ pixelData = await (0, tiff_deflate_js_1.deflateDecompress)(compressedData);
999
+ }
891
1000
  else {
892
1001
  // Uncompressed
893
1002
  pixelData = data.slice(stripOffset, stripOffset + stripByteCount);
@@ -917,6 +1026,29 @@ class TIFFFormat {
917
1026
  }
918
1027
  }
919
1028
  }
1029
+ else if (photometric === 5) {
1030
+ // CMYK image - convert to RGB
1031
+ for (let y = 0; y < height; y++) {
1032
+ for (let x = 0; x < width; x++) {
1033
+ const dstIdx = (y * width + x) * 4;
1034
+ if (srcPos + 4 > pixelData.length) {
1035
+ return null; // Not enough data
1036
+ }
1037
+ // TIFF stores CMYK in order, values are 0-255
1038
+ // Convert to 0-1 range for conversion
1039
+ const c = pixelData[srcPos++] / 255;
1040
+ const m = pixelData[srcPos++] / 255;
1041
+ const yVal = pixelData[srcPos++] / 255;
1042
+ const k = pixelData[srcPos++] / 255;
1043
+ // Convert CMYK to RGB
1044
+ const [r, g, b] = (0, image_processing_js_1.cmykToRgb)(c, m, yVal, k);
1045
+ rgba[dstIdx] = r; // R
1046
+ rgba[dstIdx + 1] = g; // G
1047
+ rgba[dstIdx + 2] = b; // B
1048
+ rgba[dstIdx + 3] = 255; // A (opaque)
1049
+ }
1050
+ }
1051
+ }
920
1052
  else {
921
1053
  // RGB/RGBA image
922
1054
  for (let y = 0; y < height; y++) {
@@ -1006,9 +1138,12 @@ class TIFFFormat {
1006
1138
  else if (compression === 5) {
1007
1139
  metadata.compression = "lzw";
1008
1140
  }
1009
- else if (compression === 7) {
1141
+ else if (compression === 7 || compression === 6) {
1010
1142
  metadata.compression = "jpeg";
1011
1143
  }
1144
+ else if (compression === 8) {
1145
+ metadata.compression = "deflate";
1146
+ }
1012
1147
  else if (compression === 32773) {
1013
1148
  metadata.compression = "packbits";
1014
1149
  }
@@ -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.
@@ -342,5 +350,20 @@ export declare class Image {
342
350
  * @returns This image instance for chaining
343
351
  */
344
352
  flipVertical(): this;
353
+ /**
354
+ * Convert the image to CMYK color space
355
+ * Returns a Float32Array with 4 values per pixel (C, M, Y, K) in 0-1 range
356
+ * @returns CMYK image data as Float32Array
357
+ */
358
+ toCMYK(): Float32Array;
359
+ /**
360
+ * Create an Image from CMYK data
361
+ * @param cmykData CMYK image data (4 values per pixel in 0-1 range)
362
+ * @param width Image width
363
+ * @param height Image height
364
+ * @param alpha Optional alpha value for all pixels (0-255, default: 255)
365
+ * @returns New Image instance
366
+ */
367
+ static fromCMYK(cmykData: Float32Array, width: number, height: number, alpha?: number): Image;
345
368
  }
346
369
  //# sourceMappingURL=image.d.ts.map
@@ -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
@@ -851,6 +867,34 @@ class Image {
851
867
  this.imageData.data = (0, image_processing_js_1.flipVertical)(this.imageData.data, this.imageData.width, this.imageData.height);
852
868
  return this;
853
869
  }
870
+ /**
871
+ * Convert the image to CMYK color space
872
+ * Returns a Float32Array with 4 values per pixel (C, M, Y, K) in 0-1 range
873
+ * @returns CMYK image data as Float32Array
874
+ */
875
+ toCMYK() {
876
+ if (!this.imageData)
877
+ throw new Error("No image loaded");
878
+ return (0, image_processing_js_1.rgbaToCmyk)(this.imageData.data);
879
+ }
880
+ /**
881
+ * Create an Image from CMYK data
882
+ * @param cmykData CMYK image data (4 values per pixel in 0-1 range)
883
+ * @param width Image width
884
+ * @param height Image height
885
+ * @param alpha Optional alpha value for all pixels (0-255, default: 255)
886
+ * @returns New Image instance
887
+ */
888
+ static fromCMYK(cmykData, width, height, alpha = 255) {
889
+ const rgbaData = (0, image_processing_js_1.cmykToRgba)(cmykData, alpha);
890
+ const image = new Image();
891
+ image.imageData = {
892
+ width,
893
+ height,
894
+ data: rgbaData,
895
+ };
896
+ return image;
897
+ }
854
898
  }
855
899
  exports.Image = Image;
856
900
  Object.defineProperty(Image, "formats", {