cross-image 0.2.3 → 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 +292 -74
  3. package/esm/mod.d.ts +6 -4
  4. package/esm/mod.js +4 -2
  5. package/esm/src/formats/apng.d.ts +17 -5
  6. package/esm/src/formats/apng.js +104 -9
  7. package/esm/src/formats/ascii.d.ts +13 -3
  8. package/esm/src/formats/ascii.js +25 -1
  9. package/esm/src/formats/avif.d.ts +96 -0
  10. package/esm/src/formats/avif.js +607 -0
  11. package/esm/src/formats/bmp.d.ts +13 -3
  12. package/esm/src/formats/bmp.js +75 -2
  13. package/esm/src/formats/dng.d.ts +14 -2
  14. package/esm/src/formats/dng.js +27 -5
  15. package/esm/src/formats/gif.d.ts +18 -5
  16. package/esm/src/formats/gif.js +160 -14
  17. package/esm/src/formats/heic.d.ts +96 -0
  18. package/esm/src/formats/heic.js +608 -0
  19. package/esm/src/formats/ico.d.ts +13 -3
  20. package/esm/src/formats/ico.js +32 -4
  21. package/esm/src/formats/jpeg.d.ts +10 -3
  22. package/esm/src/formats/jpeg.js +99 -11
  23. package/esm/src/formats/pam.d.ts +13 -3
  24. package/esm/src/formats/pam.js +68 -2
  25. package/esm/src/formats/pcx.d.ts +13 -3
  26. package/esm/src/formats/pcx.js +47 -2
  27. package/esm/src/formats/png.d.ts +15 -3
  28. package/esm/src/formats/png.js +89 -2
  29. package/esm/src/formats/png_base.js +2 -5
  30. package/esm/src/formats/ppm.d.ts +13 -3
  31. package/esm/src/formats/ppm.js +36 -2
  32. package/esm/src/formats/tiff.d.ts +14 -18
  33. package/esm/src/formats/tiff.js +219 -20
  34. package/esm/src/formats/webp.d.ts +10 -3
  35. package/esm/src/formats/webp.js +103 -8
  36. package/esm/src/image.d.ts +20 -3
  37. package/esm/src/image.js +65 -21
  38. package/esm/src/types.d.ts +74 -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 +18 -1
  55. package/script/mod.d.ts +6 -4
  56. package/script/mod.js +7 -3
  57. package/script/src/formats/apng.d.ts +17 -5
  58. package/script/src/formats/apng.js +104 -9
  59. package/script/src/formats/ascii.d.ts +13 -3
  60. package/script/src/formats/ascii.js +25 -1
  61. package/script/src/formats/avif.d.ts +96 -0
  62. package/script/src/formats/avif.js +611 -0
  63. package/script/src/formats/bmp.d.ts +13 -3
  64. package/script/src/formats/bmp.js +75 -2
  65. package/script/src/formats/dng.d.ts +14 -2
  66. package/script/src/formats/dng.js +27 -5
  67. package/script/src/formats/gif.d.ts +18 -5
  68. package/script/src/formats/gif.js +160 -14
  69. package/script/src/formats/heic.d.ts +96 -0
  70. package/script/src/formats/heic.js +612 -0
  71. package/script/src/formats/ico.d.ts +13 -3
  72. package/script/src/formats/ico.js +32 -4
  73. package/script/src/formats/jpeg.d.ts +10 -3
  74. package/script/src/formats/jpeg.js +99 -11
  75. package/script/src/formats/pam.d.ts +13 -3
  76. package/script/src/formats/pam.js +68 -2
  77. package/script/src/formats/pcx.d.ts +13 -3
  78. package/script/src/formats/pcx.js +47 -2
  79. package/script/src/formats/png.d.ts +15 -3
  80. package/script/src/formats/png.js +89 -2
  81. package/script/src/formats/png_base.js +2 -5
  82. package/script/src/formats/ppm.d.ts +13 -3
  83. package/script/src/formats/ppm.js +36 -2
  84. package/script/src/formats/tiff.d.ts +14 -18
  85. package/script/src/formats/tiff.js +219 -20
  86. package/script/src/formats/webp.d.ts +10 -3
  87. package/script/src/formats/webp.js +103 -8
  88. package/script/src/image.d.ts +20 -3
  89. package/script/src/image.js +64 -20
  90. package/script/src/types.d.ts +74 -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
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat } 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,17 +34,27 @@ 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
  */
48
48
  private isWhitespace;
49
+ /**
50
+ * Get the list of metadata fields supported by PPM format
51
+ */
52
+ getSupportedMetadata(): Array<keyof ImageMetadata>;
53
+ /**
54
+ * Extract metadata from PPM data without fully decoding the pixel data
55
+ * @param data Raw PPM data
56
+ * @returns Extracted metadata or undefined
57
+ */
58
+ extractMetadata(data: Uint8Array): Promise<ImageMetadata | undefined>;
49
59
  }
50
60
  //# sourceMappingURL=ppm.d.ts.map
@@ -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) {
@@ -239,4 +239,38 @@ export class PPMFormat {
239
239
  isWhitespace(byte) {
240
240
  return byte === 0x20 || byte === 0x09 || byte === 0x0a || byte === 0x0d;
241
241
  }
242
+ /**
243
+ * Get the list of metadata fields supported by PPM format
244
+ */
245
+ getSupportedMetadata() {
246
+ return []; // PPM format doesn't support metadata preservation
247
+ }
248
+ /**
249
+ * Extract metadata from PPM data without fully decoding the pixel data
250
+ * @param data Raw PPM data
251
+ * @returns Extracted metadata or undefined
252
+ */
253
+ extractMetadata(data) {
254
+ if (!this.canDecode(data)) {
255
+ return Promise.resolve(undefined);
256
+ }
257
+ const metadata = {
258
+ format: "ppm",
259
+ compression: "none",
260
+ frameCount: 1,
261
+ bitDepth: 8,
262
+ colorType: "rgb",
263
+ };
264
+ // PPM is always RGB, uncompressed, and typically 8-bit
265
+ // P3 is ASCII, P6 is binary
266
+ if (data[1] === 0x33) {
267
+ // '3'
268
+ metadata.compression = "none"; // ASCII encoding
269
+ }
270
+ else if (data[1] === 0x36) {
271
+ // '6'
272
+ metadata.compression = "none"; // Binary encoding
273
+ }
274
+ return Promise.resolve(metadata);
275
+ }
242
276
  }
@@ -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;
@@ -79,5 +68,12 @@ export declare class TIFFFormat implements ImageFormat {
79
68
  * TIFF supports extensive EXIF metadata including GPS and InteropIFD
80
69
  */
81
70
  getSupportedMetadata(): Array<keyof ImageMetadata>;
71
+ /**
72
+ * Extract metadata from TIFF data without fully decoding the pixel data
73
+ * This quickly parses IFD entries to extract metadata
74
+ * @param data Raw TIFF data
75
+ * @returns Extracted metadata or undefined
76
+ */
77
+ extractMetadata(data: Uint8Array): Promise<ImageMetadata | undefined>;
82
78
  }
83
79
  //# sourceMappingURL=tiff.d.ts.map
@@ -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);
@@ -963,4 +1025,141 @@ export class TIFFFormat {
963
1025
  "physicalHeight",
964
1026
  ];
965
1027
  }
1028
+ /**
1029
+ * Extract metadata from TIFF data without fully decoding the pixel data
1030
+ * This quickly parses IFD entries to extract metadata
1031
+ * @param data Raw TIFF data
1032
+ * @returns Extracted metadata or undefined
1033
+ */
1034
+ extractMetadata(data) {
1035
+ if (!this.canDecode(data)) {
1036
+ return Promise.resolve(undefined);
1037
+ }
1038
+ // Determine byte order
1039
+ const isLittleEndian = data[0] === 0x49 && data[1] === 0x49;
1040
+ if (!isLittleEndian && !(data[0] === 0x4d && data[1] === 0x4d)) {
1041
+ return Promise.resolve(undefined);
1042
+ }
1043
+ // Read IFD offset
1044
+ const ifdOffset = this.readUint32(data, 4, isLittleEndian);
1045
+ if (ifdOffset >= data.length) {
1046
+ return Promise.resolve(undefined);
1047
+ }
1048
+ // Get dimensions for DPI calculation
1049
+ const width = this.getIFDValue(data, ifdOffset, 0x0100, isLittleEndian);
1050
+ const height = this.getIFDValue(data, ifdOffset, 0x0101, isLittleEndian);
1051
+ if (!width || !height) {
1052
+ return Promise.resolve(undefined);
1053
+ }
1054
+ // Extract metadata from TIFF tags
1055
+ const metadata = {
1056
+ format: "tiff",
1057
+ frameCount: 1,
1058
+ bitDepth: 8,
1059
+ };
1060
+ // Get compression type
1061
+ const compression = this.getIFDValue(data, ifdOffset, 0x0103, isLittleEndian);
1062
+ if (compression === 1) {
1063
+ metadata.compression = "none";
1064
+ }
1065
+ else if (compression === 5) {
1066
+ metadata.compression = "lzw";
1067
+ }
1068
+ else if (compression === 7 || compression === 6) {
1069
+ metadata.compression = "jpeg";
1070
+ }
1071
+ else if (compression === 8) {
1072
+ metadata.compression = "deflate";
1073
+ }
1074
+ else if (compression === 32773) {
1075
+ metadata.compression = "packbits";
1076
+ }
1077
+ else if (compression) {
1078
+ metadata.compression = `unknown-${compression}`;
1079
+ }
1080
+ // Get bits per sample
1081
+ const bitsPerSample = this.getIFDValue(data, ifdOffset, 0x0102, isLittleEndian);
1082
+ if (bitsPerSample) {
1083
+ metadata.bitDepth = bitsPerSample;
1084
+ }
1085
+ // Get photometric interpretation for color type
1086
+ const photometric = this.getIFDValue(data, ifdOffset, 0x0106, isLittleEndian);
1087
+ const samplesPerPixel = this.getIFDValue(data, ifdOffset, 0x0115, isLittleEndian);
1088
+ if (photometric === 0 || photometric === 1) {
1089
+ metadata.colorType = "grayscale";
1090
+ }
1091
+ else if (photometric === 2) {
1092
+ if (samplesPerPixel === 3) {
1093
+ metadata.colorType = "rgb";
1094
+ }
1095
+ else if (samplesPerPixel === 4) {
1096
+ metadata.colorType = "rgba";
1097
+ }
1098
+ }
1099
+ else if (photometric === 3) {
1100
+ metadata.colorType = "indexed";
1101
+ }
1102
+ // Count IFDs (pages/frames) by following the chain
1103
+ let currentIfdOffset = ifdOffset;
1104
+ let frameCount = 0;
1105
+ while (currentIfdOffset > 0 && currentIfdOffset < data.length &&
1106
+ frameCount < 1000) {
1107
+ frameCount++;
1108
+ // Read number of entries in this IFD
1109
+ const numEntries = this.readUint16(data, currentIfdOffset, isLittleEndian);
1110
+ // Next IFD offset is after all entries (2 + numEntries * 12 bytes)
1111
+ const nextIfdOffsetPos = currentIfdOffset + 2 + (numEntries * 12);
1112
+ if (nextIfdOffsetPos + 4 > data.length)
1113
+ break;
1114
+ currentIfdOffset = this.readUint32(data, nextIfdOffsetPos, isLittleEndian);
1115
+ }
1116
+ metadata.frameCount = frameCount;
1117
+ // XResolution (0x011a) and YResolution (0x011b) for DPI
1118
+ const xResOffset = this.getIFDValue(data, ifdOffset, 0x011a, isLittleEndian);
1119
+ const yResOffset = this.getIFDValue(data, ifdOffset, 0x011b, isLittleEndian);
1120
+ if (xResOffset && xResOffset < data.length - 8) {
1121
+ const numerator = this.readUint32(data, xResOffset, isLittleEndian);
1122
+ const denominator = this.readUint32(data, xResOffset + 4, isLittleEndian);
1123
+ if (denominator > 0) {
1124
+ metadata.dpiX = Math.round(numerator / denominator);
1125
+ }
1126
+ }
1127
+ if (yResOffset && yResOffset < data.length - 8) {
1128
+ const numerator = this.readUint32(data, yResOffset, isLittleEndian);
1129
+ const denominator = this.readUint32(data, yResOffset + 4, isLittleEndian);
1130
+ if (denominator > 0) {
1131
+ metadata.dpiY = Math.round(numerator / denominator);
1132
+ }
1133
+ }
1134
+ // Calculate physical dimensions if DPI is available
1135
+ if (metadata.dpiX && metadata.dpiY) {
1136
+ metadata.physicalWidth = width / metadata.dpiX;
1137
+ metadata.physicalHeight = height / metadata.dpiY;
1138
+ }
1139
+ // ImageDescription (0x010e)
1140
+ const descOffset = this.getIFDValue(data, ifdOffset, 0x010e, isLittleEndian);
1141
+ if (descOffset && descOffset < data.length) {
1142
+ metadata.description = this.readString(data, descOffset);
1143
+ }
1144
+ // Artist (0x013b)
1145
+ const artistOffset = this.getIFDValue(data, ifdOffset, 0x013b, isLittleEndian);
1146
+ if (artistOffset && artistOffset < data.length) {
1147
+ metadata.author = this.readString(data, artistOffset);
1148
+ }
1149
+ // Copyright (0x8298)
1150
+ const copyrightOffset = this.getIFDValue(data, ifdOffset, 0x8298, isLittleEndian);
1151
+ if (copyrightOffset && copyrightOffset < data.length) {
1152
+ metadata.copyright = this.readString(data, copyrightOffset);
1153
+ }
1154
+ // DateTime (0x0132)
1155
+ const dateTimeOffset = this.getIFDValue(data, ifdOffset, 0x0132, isLittleEndian);
1156
+ if (dateTimeOffset && dateTimeOffset < data.length) {
1157
+ const dateStr = this.readString(data, dateTimeOffset);
1158
+ const match = dateStr.match(/^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/);
1159
+ if (match) {
1160
+ metadata.creationDate = new Date(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]), parseInt(match[4]), parseInt(match[5]), parseInt(match[6]));
1161
+ }
1162
+ }
1163
+ return Promise.resolve(Object.keys(metadata).length > 0 ? metadata : undefined);
1164
+ }
966
1165
  }
@@ -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,5 +42,12 @@ export declare class WebPFormat implements ImageFormat {
42
42
  * Get the list of metadata fields supported by WebP format
43
43
  */
44
44
  getSupportedMetadata(): Array<keyof ImageMetadata>;
45
+ /**
46
+ * Extract metadata from WebP data without fully decoding the pixel data
47
+ * This quickly parses RIFF chunks to extract EXIF and XMP metadata
48
+ * @param data Raw WebP data
49
+ * @returns Extracted metadata or undefined
50
+ */
51
+ extractMetadata(data: Uint8Array): Promise<ImageMetadata | undefined>;
45
52
  }
46
53
  //# sourceMappingURL=webp.d.ts.map