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
@@ -41,7 +41,7 @@ class BMPFormat {
41
41
  * @param data Raw BMP image data
42
42
  * @returns Decoded image data with RGBA pixels
43
43
  */
44
- decode(data) {
44
+ decode(data, _options) {
45
45
  if (!this.canDecode(data)) {
46
46
  throw new Error("Invalid BMP signature");
47
47
  }
@@ -118,7 +118,7 @@ class BMPFormat {
118
118
  * @param imageData Image data to encode
119
119
  * @returns Encoded BMP image bytes
120
120
  */
121
- encode(imageData) {
121
+ encode(imageData, _options) {
122
122
  const { width, height, data, metadata } = imageData;
123
123
  // Calculate sizes
124
124
  const bytesPerPixel = 4; // We'll encode as 32-bit RGBA
@@ -171,5 +171,78 @@ class BMPFormat {
171
171
  }
172
172
  return Promise.resolve(result);
173
173
  }
174
+ /**
175
+ * Get the list of metadata fields supported by BMP format
176
+ */
177
+ getSupportedMetadata() {
178
+ return [
179
+ "dpiX", // Pixels per meter in header
180
+ "dpiY", // Pixels per meter in header
181
+ ];
182
+ }
183
+ /**
184
+ * Extract metadata from BMP data without fully decoding the pixel data
185
+ * @param data Raw BMP data
186
+ * @returns Extracted metadata or undefined
187
+ */
188
+ extractMetadata(data) {
189
+ if (!this.canDecode(data)) {
190
+ return Promise.resolve(undefined);
191
+ }
192
+ const metadata = {
193
+ format: "bmp",
194
+ frameCount: 1,
195
+ bitDepth: 24,
196
+ colorType: "rgb",
197
+ };
198
+ // Read BMP header to determine version
199
+ const headerSize = (0, byte_utils_js_1.readUint32LE)(data, 14);
200
+ if (headerSize >= 40) {
201
+ // BITMAPINFOHEADER or later
202
+ const bitsPerPixel = (0, byte_utils_js_1.readUint16LE)(data, 28);
203
+ metadata.bitDepth = bitsPerPixel;
204
+ if (bitsPerPixel === 1 || bitsPerPixel === 8) {
205
+ metadata.colorType = "indexed";
206
+ }
207
+ else if (bitsPerPixel === 24) {
208
+ metadata.colorType = "rgb";
209
+ }
210
+ else if (bitsPerPixel === 32) {
211
+ metadata.colorType = "rgba";
212
+ }
213
+ const compression = (0, byte_utils_js_1.readUint32LE)(data, 30);
214
+ if (compression === 0) {
215
+ metadata.compression = "none";
216
+ }
217
+ else if (compression === 1) {
218
+ metadata.compression = "rle8";
219
+ }
220
+ else if (compression === 2) {
221
+ metadata.compression = "rle4";
222
+ }
223
+ else if (compression === 3) {
224
+ metadata.compression = "bitfields";
225
+ }
226
+ else {
227
+ metadata.compression = `unknown-${compression}`;
228
+ }
229
+ // DPI information (pixels per meter)
230
+ if (headerSize >= 40) {
231
+ const xPelsPerMeter = (0, byte_utils_js_1.readUint32LE)(data, 38);
232
+ const yPelsPerMeter = (0, byte_utils_js_1.readUint32LE)(data, 42);
233
+ if (xPelsPerMeter > 0) {
234
+ metadata.dpiX = Math.round(xPelsPerMeter * 0.0254);
235
+ }
236
+ if (yPelsPerMeter > 0) {
237
+ metadata.dpiY = Math.round(yPelsPerMeter * 0.0254);
238
+ }
239
+ }
240
+ }
241
+ else {
242
+ // BITMAPCOREHEADER
243
+ metadata.compression = "none";
244
+ }
245
+ return Promise.resolve(Object.keys(metadata).length > 0 ? metadata : undefined);
246
+ }
174
247
  }
175
248
  exports.BMPFormat = BMPFormat;
@@ -1,4 +1,4 @@
1
- import type { ImageData } from "../types.js";
1
+ import type { ImageData, ImageMetadata } from "../types.js";
2
2
  import { TIFFFormat } from "./tiff.js";
3
3
  /**
4
4
  * DNG format handler
@@ -22,6 +22,18 @@ export declare class DNGFormat extends TIFFFormat {
22
22
  * @param imageData Image data to encode
23
23
  * @returns Encoded DNG image bytes
24
24
  */
25
- encode(imageData: ImageData): Promise<Uint8Array>;
25
+ encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
26
+ /**
27
+ * Get the list of metadata fields supported by DNG format
28
+ * DNG is based on TIFF, so it supports all TIFF metadata fields
29
+ */
30
+ getSupportedMetadata(): Array<keyof ImageMetadata>;
31
+ /**
32
+ * Extract metadata from DNG data without fully decoding the pixel data
33
+ * DNG is TIFF-based, so we can use the parent extractMetadata and override format
34
+ * @param data Raw DNG data
35
+ * @returns Extracted metadata or undefined
36
+ */
37
+ extractMetadata(data: Uint8Array): Promise<ImageMetadata | undefined>;
26
38
  }
27
39
  //# sourceMappingURL=dng.d.ts.map
@@ -25,10 +25,6 @@ class DNGFormat extends tiff_js_1.TIFFFormat {
25
25
  writable: true,
26
26
  value: "image/x-adobe-dng"
27
27
  });
28
- // Helper methods duplicated from TIFFFormat because they are protected/private there
29
- // and we can't easily access them if they are private.
30
- // Let's check TIFFFormat visibility.
31
- // The read/write helpers were not exported in the previous read_file output.
32
28
  }
33
29
  /**
34
30
  * Check if the given data is a DNG image
@@ -81,7 +77,7 @@ class DNGFormat extends tiff_js_1.TIFFFormat {
81
77
  * @param imageData Image data to encode
82
78
  * @returns Encoded DNG image bytes
83
79
  */
84
- encode(imageData) {
80
+ encode(imageData, _options) {
85
81
  const { width, height, data } = imageData;
86
82
  // We'll create a Linear DNG (demosaiced RGB)
87
83
  // This is very similar to a standard TIFF but with specific tags.
@@ -191,5 +187,31 @@ class DNGFormat extends tiff_js_1.TIFFFormat {
191
187
  }
192
188
  return Promise.resolve(new Uint8Array(result));
193
189
  }
190
+ // Helper methods duplicated from TIFFFormat because they are protected/private there
191
+ // and we can't easily access them if they are private.
192
+ // Let's check TIFFFormat visibility.
193
+ // The read/write helpers were not exported in the previous read_file output.
194
+ /**
195
+ * Get the list of metadata fields supported by DNG format
196
+ * DNG is based on TIFF, so it supports all TIFF metadata fields
197
+ */
198
+ getSupportedMetadata() {
199
+ return super.getSupportedMetadata();
200
+ }
201
+ /**
202
+ * Extract metadata from DNG data without fully decoding the pixel data
203
+ * DNG is TIFF-based, so we can use the parent extractMetadata and override format
204
+ * @param data Raw DNG data
205
+ * @returns Extracted metadata or undefined
206
+ */
207
+ async extractMetadata(data) {
208
+ // Use parent TIFF extractMetadata
209
+ const metadata = await super.extractMetadata(data);
210
+ if (metadata) {
211
+ // Override format to indicate this is a DNG
212
+ metadata.format = "dng";
213
+ }
214
+ return metadata;
215
+ }
194
216
  }
195
217
  exports.DNGFormat = DNGFormat;
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat, MultiFrameImageData } from "../types.js";
1
+ import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, MultiFrameImageData } from "../types.js";
2
2
  /**
3
3
  * GIF format handler
4
4
  * Now includes pure-JS implementation with custom LZW compression/decompression
@@ -32,18 +32,18 @@ export declare class GIFFormat implements ImageFormat {
32
32
  * @param data Raw GIF image data
33
33
  * @returns Decoded image data with RGBA pixels of first frame
34
34
  */
35
- decode(data: Uint8Array): Promise<ImageData>;
36
- private extractMetadata;
35
+ decode(data: Uint8Array, settings?: ImageDecoderOptions): Promise<ImageData>;
36
+ private extractGIFMetadata;
37
37
  /**
38
38
  * Encode RGBA image data to GIF format (single frame)
39
39
  * @param imageData Image data to encode
40
40
  * @returns Encoded GIF image bytes
41
41
  */
42
- encode(imageData: ImageData): Promise<Uint8Array>;
42
+ encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
43
43
  /**
44
44
  * Decode all frames from an animated GIF
45
45
  */
46
- decodeFrames(data: Uint8Array): Promise<MultiFrameImageData>;
46
+ decodeFrames(data: Uint8Array, settings?: ImageDecoderOptions): Promise<MultiFrameImageData>;
47
47
  /**
48
48
  * Encode multi-frame image data to animated GIF
49
49
  */
@@ -55,5 +55,18 @@ export declare class GIFFormat implements ImageFormat {
55
55
  private parseXMP;
56
56
  private injectMetadata;
57
57
  private createCommentText;
58
+ /**
59
+ * Get the list of metadata fields supported by GIF format
60
+ */
61
+ getSupportedMetadata(): Array<keyof ImageMetadata>;
62
+ /**
63
+ * Extract metadata from GIF data without fully decoding the pixel data
64
+ * This quickly parses GIF structure to extract metadata including frame count
65
+ * @param data Raw GIF data
66
+ * @returns Extracted metadata or undefined
67
+ */
68
+ extractMetadata(data: Uint8Array): Promise<ImageMetadata | undefined>;
69
+ private getSubBlocksSize;
70
+ private parseGIFCommentMetadata;
58
71
  }
59
72
  //# sourceMappingURL=gif.d.ts.map
@@ -59,18 +59,21 @@ class GIFFormat {
59
59
  * @param data Raw GIF image data
60
60
  * @returns Decoded image data with RGBA pixels of first frame
61
61
  */
62
- async decode(data) {
62
+ async decode(data, settings) {
63
63
  if (!this.canDecode(data)) {
64
64
  throw new Error("Invalid GIF signature");
65
65
  }
66
- // Try pure-JS decoder first
66
+ // Try pure-JS decoder first with tolerant decoding enabled by default
67
67
  try {
68
- const decoder = new gif_decoder_js_1.GIFDecoder(data);
68
+ const decoder = new gif_decoder_js_1.GIFDecoder(data, {
69
+ tolerantDecoding: settings?.tolerantDecoding ?? true,
70
+ onWarning: settings?.onWarning,
71
+ });
69
72
  const result = decoder.decode();
70
73
  // Validate dimensions for security (prevent integer overflow and heap exhaustion)
71
74
  (0, security_js_1.validateImageDimensions)(result.width, result.height);
72
75
  // Extract metadata from comment extensions
73
- const metadata = this.extractMetadata(data);
76
+ const metadata = this.extractGIFMetadata(data);
74
77
  return {
75
78
  width: result.width,
76
79
  height: result.height,
@@ -78,9 +81,8 @@ class GIFFormat {
78
81
  metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
79
82
  };
80
83
  }
81
- catch (error) {
84
+ catch (_error) {
82
85
  // Fall back to runtime decoder if pure-JS fails
83
- console.warn("Pure-JS GIF decoder failed, falling back to runtime:", error);
84
86
  let pos = 6; // Skip "GIF89a" or "GIF87a"
85
87
  const width = (0, byte_utils_js_1.readUint16LE)(data, pos);
86
88
  pos += 2;
@@ -88,7 +90,7 @@ class GIFFormat {
88
90
  // Validate dimensions for security (prevent integer overflow and heap exhaustion)
89
91
  (0, security_js_1.validateImageDimensions)(width, height);
90
92
  const rgba = await this.decodeUsingRuntime(data, width, height);
91
- const metadata = this.extractMetadata(data);
93
+ const metadata = this.extractGIFMetadata(data);
92
94
  return {
93
95
  width,
94
96
  height,
@@ -97,7 +99,7 @@ class GIFFormat {
97
99
  };
98
100
  }
99
101
  }
100
- extractMetadata(data) {
102
+ extractGIFMetadata(data) {
101
103
  const metadata = {};
102
104
  let pos = 6; // Skip "GIF89a" or "GIF87a"
103
105
  // Read logical screen descriptor
@@ -154,7 +156,7 @@ class GIFFormat {
154
156
  * @param imageData Image data to encode
155
157
  * @returns Encoded GIF image bytes
156
158
  */
157
- async encode(imageData) {
159
+ async encode(imageData, _options) {
158
160
  const { width, height, data, metadata } = imageData;
159
161
  // Try pure-JS encoder first
160
162
  try {
@@ -167,9 +169,8 @@ class GIFFormat {
167
169
  }
168
170
  return encoded;
169
171
  }
170
- catch (error) {
172
+ catch (_error) {
171
173
  // Fall back to runtime encoding if pure-JS fails
172
- console.warn("Pure-JS GIF encoder failed, falling back to runtime:", error);
173
174
  if (typeof OffscreenCanvas !== "undefined") {
174
175
  try {
175
176
  const canvas = new OffscreenCanvas(width, height);
@@ -202,15 +203,18 @@ class GIFFormat {
202
203
  /**
203
204
  * Decode all frames from an animated GIF
204
205
  */
205
- decodeFrames(data) {
206
+ decodeFrames(data, settings) {
206
207
  if (!this.canDecode(data)) {
207
208
  throw new Error("Invalid GIF signature");
208
209
  }
209
210
  try {
210
- const decoder = new gif_decoder_js_1.GIFDecoder(data);
211
+ const decoder = new gif_decoder_js_1.GIFDecoder(data, {
212
+ tolerantDecoding: settings?.tolerantDecoding ?? true,
213
+ onWarning: settings?.onWarning,
214
+ });
211
215
  const result = decoder.decodeAllFrames();
212
216
  // Extract metadata from comment extensions
213
- const metadata = this.extractMetadata(data);
217
+ const metadata = this.extractGIFMetadata(data);
214
218
  return Promise.resolve({
215
219
  width: result.width,
216
220
  height: result.height,
@@ -405,5 +409,147 @@ class GIFFormat {
405
409
  parts.push(`Copyright: ${metadata.copyright}`);
406
410
  return parts.length > 0 ? parts.join("\n") : null;
407
411
  }
412
+ /**
413
+ * Get the list of metadata fields supported by GIF format
414
+ */
415
+ getSupportedMetadata() {
416
+ return [
417
+ "title", // Comment extension
418
+ "description", // Comment extension
419
+ "author", // Comment extension
420
+ "copyright", // Comment extension
421
+ "frameCount", // Animation frames
422
+ ];
423
+ }
424
+ /**
425
+ * Extract metadata from GIF data without fully decoding the pixel data
426
+ * This quickly parses GIF structure to extract metadata including frame count
427
+ * @param data Raw GIF data
428
+ * @returns Extracted metadata or undefined
429
+ */
430
+ extractMetadata(data) {
431
+ if (!this.canDecode(data)) {
432
+ return Promise.resolve(undefined);
433
+ }
434
+ const metadata = {
435
+ format: "gif",
436
+ compression: "lzw",
437
+ frameCount: 0,
438
+ bitDepth: 8,
439
+ colorType: "indexed",
440
+ };
441
+ let pos = 6; // Skip "GIF89a" or "GIF87a"
442
+ // Read Logical Screen Descriptor
443
+ const _width = (0, byte_utils_js_1.readUint16LE)(data, pos);
444
+ pos += 2;
445
+ const _height = (0, byte_utils_js_1.readUint16LE)(data, pos);
446
+ pos += 2;
447
+ const packed = data[pos++];
448
+ const _backgroundColorIndex = data[pos++];
449
+ const _aspectRatio = data[pos++];
450
+ // Parse packed fields
451
+ const hasGlobalColorTable = (packed & 0x80) !== 0;
452
+ const globalColorTableSize = 2 << (packed & 0x07);
453
+ // Skip global color table if present
454
+ if (hasGlobalColorTable) {
455
+ pos += globalColorTableSize * 3;
456
+ }
457
+ // Parse data stream to count frames and extract metadata
458
+ while (pos < data.length) {
459
+ const separator = data[pos++];
460
+ if (separator === 0x21) {
461
+ // Extension
462
+ const label = data[pos++];
463
+ if (label === 0xf9) {
464
+ // Graphics Control Extension - indicates a frame
465
+ metadata.frameCount++;
466
+ // Skip block
467
+ const blockSize = data[pos++];
468
+ pos += blockSize;
469
+ pos++; // Block terminator
470
+ }
471
+ else if (label === 0xfe) {
472
+ // Comment Extension
473
+ const commentResult = this.readDataSubBlocks(data, pos);
474
+ pos = commentResult.endPos;
475
+ this.parseGIFCommentMetadata(commentResult.text, metadata);
476
+ }
477
+ else if (label === 0xff) {
478
+ // Application Extension
479
+ const blockSize = data[pos++];
480
+ pos += blockSize;
481
+ // Skip sub-blocks
482
+ pos += this.getSubBlocksSize(data, pos);
483
+ }
484
+ else {
485
+ // Other extension - skip sub-blocks
486
+ pos += this.getSubBlocksSize(data, pos);
487
+ }
488
+ }
489
+ else if (separator === 0x2c) {
490
+ // Image Descriptor - frame data
491
+ if (metadata.frameCount === 0) {
492
+ metadata.frameCount = 1;
493
+ }
494
+ pos += 8; // Skip left, top, width, height
495
+ const localPacked = data[pos++];
496
+ const hasLocalColorTable = (localPacked & 0x80) !== 0;
497
+ if (hasLocalColorTable) {
498
+ const localColorTableSize = 2 << (localPacked & 0x07);
499
+ pos += localColorTableSize * 3;
500
+ }
501
+ // Skip LZW minimum code size
502
+ pos++;
503
+ // Skip image data sub-blocks
504
+ pos += this.getSubBlocksSize(data, pos);
505
+ }
506
+ else if (separator === 0x3b) {
507
+ // Trailer - end of GIF
508
+ break;
509
+ }
510
+ else if (separator === 0x00) {
511
+ // Padding - skip
512
+ continue;
513
+ }
514
+ else {
515
+ // Unknown - try to continue
516
+ break;
517
+ }
518
+ }
519
+ // If no frames were counted, assume at least 1
520
+ if (metadata.frameCount === 0) {
521
+ metadata.frameCount = 1;
522
+ }
523
+ return Promise.resolve(Object.keys(metadata).length > 0 ? metadata : undefined);
524
+ }
525
+ getSubBlocksSize(data, pos) {
526
+ let size = 0;
527
+ while (pos + size < data.length) {
528
+ const blockSize = data[pos + size];
529
+ size++; // For block size byte
530
+ if (blockSize === 0)
531
+ break;
532
+ size += blockSize;
533
+ }
534
+ return size;
535
+ }
536
+ parseGIFCommentMetadata(commentText, metadata) {
537
+ const lines = commentText.split("\n");
538
+ for (const line of lines) {
539
+ const colonIndex = line.indexOf(":");
540
+ if (colonIndex > 0) {
541
+ const key = line.substring(0, colonIndex).trim();
542
+ const value = line.substring(colonIndex + 1).trim();
543
+ if (key === "Title")
544
+ metadata.title = value;
545
+ else if (key === "Description")
546
+ metadata.description = value;
547
+ else if (key === "Author")
548
+ metadata.author = value;
549
+ else if (key === "Copyright")
550
+ metadata.copyright = value;
551
+ }
552
+ }
553
+ }
408
554
  }
409
555
  exports.GIFFormat = GIFFormat;
@@ -0,0 +1,96 @@
1
+ import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata } from "../types.js";
2
+ /**
3
+ * HEIC format handler
4
+ * Supports HEIC/HEIF images using runtime APIs (ImageDecoder/OffscreenCanvas)
5
+ * Note: Pure JavaScript encode/decode is not supported due to complexity
6
+ */
7
+ export declare class HEICFormat implements ImageFormat {
8
+ /** Format name identifier */
9
+ readonly name = "heic";
10
+ /** MIME type for HEIC images */
11
+ readonly mimeType = "image/heic";
12
+ /**
13
+ * Check if the given data is a HEIC/HEIF image
14
+ * @param data Raw image data to check
15
+ * @returns true if data has HEIC/HEIF signature
16
+ */
17
+ canDecode(data: Uint8Array): boolean;
18
+ /**
19
+ * Decode HEIC image data to RGBA
20
+ * Uses runtime APIs (ImageDecoder) for decoding
21
+ * @param data Raw HEIC image data
22
+ * @returns Decoded image data with RGBA pixels
23
+ */
24
+ decode(data: Uint8Array, settings?: ImageDecoderOptions): Promise<ImageData>;
25
+ /**
26
+ * Encode RGBA image data to HEIC format
27
+ * Uses runtime APIs (OffscreenCanvas) for encoding
28
+ *
29
+ * Note: Metadata injection is not currently implemented. Metadata may be lost during encoding
30
+ * as it would require parsing and modifying the ISOBMFF container structure.
31
+ *
32
+ * @param imageData Image data to encode
33
+ * @returns Encoded HEIC image bytes
34
+ */
35
+ encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
36
+ /**
37
+ * Decode using runtime APIs
38
+ * @param data Raw HEIC data
39
+ * @returns Decoded image dimensions and pixel data
40
+ */
41
+ private decodeUsingRuntime;
42
+ /**
43
+ * Parse EXIF metadata from HEIC data
44
+ *
45
+ * Note: This is a simplified implementation that searches for EXIF headers linearly.
46
+ * A full implementation would require navigating the ISOBMFF box structure to find
47
+ * the 'meta' box and then the 'Exif' item. This simplified approach may not work
48
+ * in all cases but is suitable for basic metadata extraction when runtime APIs are
49
+ * not available or as a fallback.
50
+ *
51
+ * @param data Raw HEIC data
52
+ * @param metadata Metadata object to populate
53
+ */
54
+ private parseEXIF;
55
+ /**
56
+ * Parse TIFF-formatted EXIF data
57
+ * @param data EXIF data in TIFF format
58
+ * @param metadata Metadata object to populate
59
+ */
60
+ private parseTIFFExif;
61
+ /**
62
+ * Parse Exif Sub-IFD for camera settings
63
+ * @param data EXIF data
64
+ * @param exifIfdOffset Offset to Exif Sub-IFD
65
+ * @param littleEndian Byte order
66
+ * @param metadata Metadata object to populate
67
+ */
68
+ private parseExifSubIFD;
69
+ /**
70
+ * Parse GPS IFD for location data
71
+ * @param data EXIF data
72
+ * @param gpsIfdOffset Offset to GPS IFD
73
+ * @param littleEndian Byte order
74
+ * @param metadata Metadata object to populate
75
+ */
76
+ private parseGPSIFD;
77
+ /**
78
+ * Read a rational value (numerator/denominator)
79
+ * @param data Data buffer
80
+ * @param offset Offset to rational
81
+ * @param littleEndian Byte order
82
+ * @returns Decimal value
83
+ */
84
+ private readRational;
85
+ /**
86
+ * Get the list of metadata fields supported by HEIC format
87
+ */
88
+ getSupportedMetadata(): Array<keyof ImageMetadata>;
89
+ /**
90
+ * Extract metadata from HEIC data without fully decoding the pixel data
91
+ * @param data Raw HEIC data
92
+ * @returns Extracted metadata or undefined
93
+ */
94
+ extractMetadata(data: Uint8Array): Promise<ImageMetadata | undefined>;
95
+ }
96
+ //# sourceMappingURL=heic.d.ts.map