cross-image 0.2.3 → 0.2.4

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 (72) hide show
  1. package/README.md +333 -289
  2. package/esm/mod.d.ts +2 -0
  3. package/esm/mod.js +2 -0
  4. package/esm/src/formats/apng.d.ts +13 -1
  5. package/esm/src/formats/apng.js +97 -0
  6. package/esm/src/formats/ascii.d.ts +11 -1
  7. package/esm/src/formats/ascii.js +24 -0
  8. package/esm/src/formats/avif.d.ts +96 -0
  9. package/esm/src/formats/avif.js +607 -0
  10. package/esm/src/formats/bmp.d.ts +11 -1
  11. package/esm/src/formats/bmp.js +73 -0
  12. package/esm/src/formats/dng.d.ts +13 -1
  13. package/esm/src/formats/dng.js +26 -4
  14. package/esm/src/formats/gif.d.ts +15 -2
  15. package/esm/src/formats/gif.js +146 -4
  16. package/esm/src/formats/heic.d.ts +96 -0
  17. package/esm/src/formats/heic.js +608 -0
  18. package/esm/src/formats/ico.d.ts +11 -1
  19. package/esm/src/formats/ico.js +28 -0
  20. package/esm/src/formats/jpeg.d.ts +7 -0
  21. package/esm/src/formats/jpeg.js +76 -0
  22. package/esm/src/formats/pam.d.ts +11 -1
  23. package/esm/src/formats/pam.js +66 -0
  24. package/esm/src/formats/pcx.d.ts +11 -1
  25. package/esm/src/formats/pcx.js +45 -0
  26. package/esm/src/formats/png.d.ts +13 -1
  27. package/esm/src/formats/png.js +87 -0
  28. package/esm/src/formats/ppm.d.ts +11 -1
  29. package/esm/src/formats/ppm.js +34 -0
  30. package/esm/src/formats/tiff.d.ts +7 -0
  31. package/esm/src/formats/tiff.js +134 -0
  32. package/esm/src/formats/webp.d.ts +7 -0
  33. package/esm/src/formats/webp.js +92 -0
  34. package/esm/src/image.d.ts +9 -0
  35. package/esm/src/image.js +28 -0
  36. package/esm/src/types.d.ts +18 -0
  37. package/package.json +18 -1
  38. package/script/mod.d.ts +2 -0
  39. package/script/mod.js +5 -1
  40. package/script/src/formats/apng.d.ts +13 -1
  41. package/script/src/formats/apng.js +97 -0
  42. package/script/src/formats/ascii.d.ts +11 -1
  43. package/script/src/formats/ascii.js +24 -0
  44. package/script/src/formats/avif.d.ts +96 -0
  45. package/script/src/formats/avif.js +611 -0
  46. package/script/src/formats/bmp.d.ts +11 -1
  47. package/script/src/formats/bmp.js +73 -0
  48. package/script/src/formats/dng.d.ts +13 -1
  49. package/script/src/formats/dng.js +26 -4
  50. package/script/src/formats/gif.d.ts +15 -2
  51. package/script/src/formats/gif.js +146 -4
  52. package/script/src/formats/heic.d.ts +96 -0
  53. package/script/src/formats/heic.js +612 -0
  54. package/script/src/formats/ico.d.ts +11 -1
  55. package/script/src/formats/ico.js +28 -0
  56. package/script/src/formats/jpeg.d.ts +7 -0
  57. package/script/src/formats/jpeg.js +76 -0
  58. package/script/src/formats/pam.d.ts +11 -1
  59. package/script/src/formats/pam.js +66 -0
  60. package/script/src/formats/pcx.d.ts +11 -1
  61. package/script/src/formats/pcx.js +45 -0
  62. package/script/src/formats/png.d.ts +13 -1
  63. package/script/src/formats/png.js +87 -0
  64. package/script/src/formats/ppm.d.ts +11 -1
  65. package/script/src/formats/ppm.js +34 -0
  66. package/script/src/formats/tiff.d.ts +7 -0
  67. package/script/src/formats/tiff.js +134 -0
  68. package/script/src/formats/webp.d.ts +7 -0
  69. package/script/src/formats/webp.js +92 -0
  70. package/script/src/image.d.ts +9 -0
  71. package/script/src/image.js +28 -0
  72. package/script/src/types.d.ts +18 -0
@@ -1096,5 +1096,81 @@ class JPEGFormat {
1096
1096
  "dpiY",
1097
1097
  ];
1098
1098
  }
1099
+ /**
1100
+ * Extract metadata from JPEG data without fully decoding the pixel data
1101
+ * This quickly parses JFIF and EXIF markers to extract metadata
1102
+ * @param data Raw JPEG data
1103
+ * @returns Extracted metadata or undefined
1104
+ */
1105
+ extractMetadata(data) {
1106
+ if (!this.canDecode(data)) {
1107
+ return Promise.resolve(undefined);
1108
+ }
1109
+ // Parse JPEG structure to extract metadata
1110
+ let pos = 2; // Skip initial FF D8
1111
+ const metadata = {
1112
+ format: "jpeg",
1113
+ compression: "dct",
1114
+ frameCount: 1,
1115
+ bitDepth: 8,
1116
+ colorType: "rgb",
1117
+ };
1118
+ let width = 0;
1119
+ let height = 0;
1120
+ while (pos < data.length - 1) {
1121
+ if (data[pos] !== 0xff) {
1122
+ pos++;
1123
+ continue;
1124
+ }
1125
+ const marker = data[pos + 1];
1126
+ pos += 2;
1127
+ // SOF markers (Start of Frame) - get dimensions for DPI calculation
1128
+ if (marker >= 0xc0 && marker <= 0xcf &&
1129
+ marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {
1130
+ const length = (data[pos] << 8) | data[pos + 1];
1131
+ // precision at pos+2
1132
+ const precision = data[pos + 2];
1133
+ if (precision && precision !== 8) {
1134
+ metadata.bitDepth = precision;
1135
+ }
1136
+ height = (data[pos + 3] << 8) | data[pos + 4];
1137
+ width = (data[pos + 5] << 8) | data[pos + 6];
1138
+ // Check number of components
1139
+ const numComponents = data[pos + 7];
1140
+ if (numComponents === 1) {
1141
+ metadata.colorType = "grayscale";
1142
+ }
1143
+ // Don't break - continue parsing for metadata
1144
+ pos += length;
1145
+ continue;
1146
+ }
1147
+ // APP0 marker (JFIF)
1148
+ if (marker === 0xe0) {
1149
+ const length = (data[pos] << 8) | data[pos + 1];
1150
+ const appData = data.slice(pos + 2, pos + length);
1151
+ this.parseJFIF(appData, metadata, width, height);
1152
+ pos += length;
1153
+ continue;
1154
+ }
1155
+ // APP1 marker (EXIF)
1156
+ if (marker === 0xe1) {
1157
+ const length = (data[pos] << 8) | data[pos + 1];
1158
+ const appData = data.slice(pos + 2, pos + length);
1159
+ this.parseEXIF(appData, metadata);
1160
+ pos += length;
1161
+ continue;
1162
+ }
1163
+ // Skip other markers
1164
+ if (marker === 0xd9 || marker === 0xda)
1165
+ break; // EOI or SOS
1166
+ if (marker >= 0xd0 && marker <= 0xd8)
1167
+ continue; // RST markers have no length
1168
+ if (marker === 0x01)
1169
+ continue; // TEM has no length
1170
+ const length = (data[pos] << 8) | data[pos + 1];
1171
+ pos += length;
1172
+ }
1173
+ return Promise.resolve(Object.keys(metadata).length > 0 ? metadata : undefined);
1174
+ }
1099
1175
  }
1100
1176
  exports.JPEGFormat = JPEGFormat;
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat } from "../types.js";
1
+ import type { ImageData, ImageFormat, ImageMetadata } from "../types.js";
2
2
  /**
3
3
  * PAM format handler
4
4
  * Implements the Netpbm PAM (Portable Arbitrary Map) format.
@@ -39,5 +39,15 @@ export declare class PAMFormat implements ImageFormat {
39
39
  * @returns Encoded PAM image bytes
40
40
  */
41
41
  encode(imageData: ImageData): Promise<Uint8Array>;
42
+ /**
43
+ * Get the list of metadata fields supported by PAM format
44
+ */
45
+ getSupportedMetadata(): Array<keyof ImageMetadata>;
46
+ /**
47
+ * Extract metadata from PAM data without fully decoding the pixel data
48
+ * @param data Raw PAM data
49
+ * @returns Extracted metadata or undefined
50
+ */
51
+ extractMetadata(data: Uint8Array): Promise<ImageMetadata | undefined>;
42
52
  }
43
53
  //# sourceMappingURL=pam.d.ts.map
@@ -177,5 +177,71 @@ class PAMFormat {
177
177
  output.set(data, headerBytes.length);
178
178
  return Promise.resolve(output);
179
179
  }
180
+ /**
181
+ * Get the list of metadata fields supported by PAM format
182
+ */
183
+ getSupportedMetadata() {
184
+ return []; // PAM format doesn't support metadata preservation
185
+ }
186
+ /**
187
+ * Extract metadata from PAM data without fully decoding the pixel data
188
+ * @param data Raw PAM data
189
+ * @returns Extracted metadata or undefined
190
+ */
191
+ extractMetadata(data) {
192
+ if (!this.canDecode(data)) {
193
+ return Promise.resolve(undefined);
194
+ }
195
+ const metadata = {
196
+ format: "pam",
197
+ compression: "none",
198
+ frameCount: 1,
199
+ bitDepth: 8, // PAM typically uses 8 bits per channel
200
+ colorType: "rgba",
201
+ };
202
+ // Try to parse the header to get actual color type
203
+ const decoder = new TextDecoder();
204
+ const headerText = decoder.decode(data.slice(0, Math.min(200, data.length)));
205
+ // Look for TUPLTYPE to determine color type
206
+ const tupltypeMatch = headerText.match(/TUPLTYPE\s+(\S+)/);
207
+ if (tupltypeMatch) {
208
+ const tupltype = tupltypeMatch[1];
209
+ if (tupltype === "GRAYSCALE") {
210
+ metadata.colorType = "grayscale";
211
+ }
212
+ else if (tupltype === "RGB") {
213
+ metadata.colorType = "rgb";
214
+ }
215
+ else if (tupltype === "RGB_ALPHA") {
216
+ metadata.colorType = "rgba";
217
+ }
218
+ }
219
+ // Look for DEPTH to determine number of channels
220
+ const depthMatch = headerText.match(/DEPTH\s+(\d+)/);
221
+ if (depthMatch) {
222
+ const depth = parseInt(depthMatch[1]);
223
+ if (depth === 1) {
224
+ metadata.colorType = "grayscale";
225
+ }
226
+ else if (depth === 3) {
227
+ metadata.colorType = "rgb";
228
+ }
229
+ else if (depth === 4) {
230
+ metadata.colorType = "rgba";
231
+ }
232
+ }
233
+ // Look for MAXVAL to determine bit depth
234
+ const maxvalMatch = headerText.match(/MAXVAL\s+(\d+)/);
235
+ if (maxvalMatch) {
236
+ const maxval = parseInt(maxvalMatch[1]);
237
+ if (maxval === 255) {
238
+ metadata.bitDepth = 8;
239
+ }
240
+ else if (maxval === 65535) {
241
+ metadata.bitDepth = 16;
242
+ }
243
+ }
244
+ return Promise.resolve(metadata);
245
+ }
180
246
  }
181
247
  exports.PAMFormat = PAMFormat;
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat } from "../types.js";
1
+ import type { ImageData, ImageFormat, ImageMetadata } from "../types.js";
2
2
  /**
3
3
  * PCX format handler
4
4
  * Implements PCX decoder and encoder
@@ -9,5 +9,15 @@ export declare class PCXFormat implements ImageFormat {
9
9
  canDecode(data: Uint8Array): boolean;
10
10
  decode(data: Uint8Array): Promise<ImageData>;
11
11
  encode(image: ImageData): Promise<Uint8Array>;
12
+ /**
13
+ * Get the list of metadata fields supported by PCX format
14
+ */
15
+ getSupportedMetadata(): Array<keyof ImageMetadata>;
16
+ /**
17
+ * Extract metadata from PCX data without fully decoding the pixel data
18
+ * @param data Raw PCX data
19
+ * @returns Extracted metadata or undefined
20
+ */
21
+ extractMetadata(data: Uint8Array): Promise<ImageMetadata | undefined>;
12
22
  }
13
23
  //# sourceMappingURL=pcx.d.ts.map
@@ -204,5 +204,50 @@ class PCXFormat {
204
204
  result.set(rleData, header.length);
205
205
  return Promise.resolve(result);
206
206
  }
207
+ /**
208
+ * Get the list of metadata fields supported by PCX format
209
+ */
210
+ getSupportedMetadata() {
211
+ return [
212
+ "dpiX", // DPI in header
213
+ "dpiY", // DPI in header
214
+ ];
215
+ }
216
+ /**
217
+ * Extract metadata from PCX data without fully decoding the pixel data
218
+ * @param data Raw PCX data
219
+ * @returns Extracted metadata or undefined
220
+ */
221
+ extractMetadata(data) {
222
+ if (!this.canDecode(data)) {
223
+ return Promise.resolve(undefined);
224
+ }
225
+ const metadata = {
226
+ format: "pcx",
227
+ compression: "rle",
228
+ frameCount: 1,
229
+ bitDepth: data[3], // Bits per pixel per plane
230
+ colorType: "rgb",
231
+ };
232
+ // Check number of planes to determine color type
233
+ const numPlanes = data[65];
234
+ if (numPlanes === 1) {
235
+ metadata.colorType = "indexed";
236
+ }
237
+ else if (numPlanes === 3) {
238
+ metadata.colorType = "rgb";
239
+ }
240
+ else if (numPlanes === 4) {
241
+ metadata.colorType = "rgba";
242
+ }
243
+ // DPI information
244
+ const dpiX = data[12] | (data[13] << 8);
245
+ const dpiY = data[14] | (data[15] << 8);
246
+ if (dpiX > 0)
247
+ metadata.dpiX = dpiX;
248
+ if (dpiY > 0)
249
+ metadata.dpiY = dpiY;
250
+ return Promise.resolve(metadata);
251
+ }
207
252
  }
208
253
  exports.PCXFormat = PCXFormat;
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat } from "../types.js";
1
+ import type { ImageData, ImageFormat, ImageMetadata } from "../types.js";
2
2
  import { PNGBase } from "./png_base.js";
3
3
  /**
4
4
  * PNG format handler
@@ -27,5 +27,17 @@ export declare class PNGFormat extends PNGBase implements ImageFormat {
27
27
  * @returns Encoded PNG image bytes
28
28
  */
29
29
  encode(imageData: ImageData): Promise<Uint8Array>;
30
+ /**
31
+ * Get the list of metadata fields supported by PNG format
32
+ * Delegates to PNGBase implementation
33
+ */
34
+ getSupportedMetadata(): Array<keyof ImageMetadata>;
35
+ /**
36
+ * Extract metadata from PNG data without fully decoding the pixel data
37
+ * This quickly parses PNG chunks to extract metadata
38
+ * @param data Raw PNG data
39
+ * @returns Extracted metadata or undefined
40
+ */
41
+ extractMetadata(data: Uint8Array): Promise<ImageMetadata | undefined>;
30
42
  }
31
43
  //# sourceMappingURL=png.d.ts.map
@@ -140,5 +140,92 @@ class PNGFormat extends png_base_js_1.PNGBase {
140
140
  // Concatenate all chunks
141
141
  return this.concatenateArrays(chunks);
142
142
  }
143
+ /**
144
+ * Get the list of metadata fields supported by PNG format
145
+ * Delegates to PNGBase implementation
146
+ */
147
+ getSupportedMetadata() {
148
+ return super.getSupportedMetadata();
149
+ }
150
+ /**
151
+ * Extract metadata from PNG data without fully decoding the pixel data
152
+ * This quickly parses PNG chunks to extract metadata
153
+ * @param data Raw PNG data
154
+ * @returns Extracted metadata or undefined
155
+ */
156
+ extractMetadata(data) {
157
+ if (!this.canDecode(data)) {
158
+ return Promise.resolve(undefined);
159
+ }
160
+ let pos = 8; // Skip PNG signature
161
+ let width = 0;
162
+ let height = 0;
163
+ const metadata = {
164
+ format: "png",
165
+ compression: "deflate",
166
+ frameCount: 1,
167
+ };
168
+ // Parse chunks for metadata only
169
+ while (pos < data.length) {
170
+ if (pos + 8 > data.length)
171
+ break;
172
+ const length = this.readUint32(data, pos);
173
+ pos += 4;
174
+ const type = String.fromCharCode(data[pos], data[pos + 1], data[pos + 2], data[pos + 3]);
175
+ pos += 4;
176
+ if (pos + length + 4 > data.length)
177
+ break;
178
+ const chunkData = data.slice(pos, pos + length);
179
+ pos += length;
180
+ pos += 4; // Skip CRC
181
+ if (type === "IHDR") {
182
+ width = this.readUint32(chunkData, 0);
183
+ height = this.readUint32(chunkData, 4);
184
+ // Parse bit depth and color type from IHDR
185
+ if (chunkData.length >= 9) {
186
+ metadata.bitDepth = chunkData[8];
187
+ const colorTypeCode = chunkData[9];
188
+ // PNG color types: 0=grayscale, 2=rgb, 3=indexed, 4=grayscale+alpha, 6=rgba
189
+ switch (colorTypeCode) {
190
+ case 0:
191
+ metadata.colorType = "grayscale";
192
+ break;
193
+ case 2:
194
+ metadata.colorType = "rgb";
195
+ break;
196
+ case 3:
197
+ metadata.colorType = "indexed";
198
+ break;
199
+ case 4:
200
+ metadata.colorType = "grayscale-alpha";
201
+ break;
202
+ case 6:
203
+ metadata.colorType = "rgba";
204
+ break;
205
+ }
206
+ }
207
+ }
208
+ else if (type === "pHYs") {
209
+ // Physical pixel dimensions
210
+ this.parsePhysChunk(chunkData, metadata, width, height);
211
+ }
212
+ else if (type === "tEXt") {
213
+ // Text chunk
214
+ this.parseTextChunk(chunkData, metadata);
215
+ }
216
+ else if (type === "iTXt") {
217
+ // International text chunk
218
+ this.parseITxtChunk(chunkData, metadata);
219
+ }
220
+ else if (type === "eXIf") {
221
+ // EXIF chunk
222
+ this.parseExifChunk(chunkData, metadata);
223
+ }
224
+ else if (type === "IEND") {
225
+ break;
226
+ }
227
+ }
228
+ return Promise.resolve(Object.keys(metadata).length > 0 ? metadata : undefined);
229
+ }
143
230
  }
144
231
  exports.PNGFormat = PNGFormat;
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat } from "../types.js";
1
+ import type { ImageData, ImageFormat, ImageMetadata } from "../types.js";
2
2
  /**
3
3
  * PPM format handler
4
4
  * Implements the Netpbm PPM (Portable PixMap) format.
@@ -46,5 +46,15 @@ export declare class PPMFormat implements ImageFormat {
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
@@ -242,5 +242,39 @@ class PPMFormat {
242
242
  isWhitespace(byte) {
243
243
  return byte === 0x20 || byte === 0x09 || byte === 0x0a || byte === 0x0d;
244
244
  }
245
+ /**
246
+ * Get the list of metadata fields supported by PPM format
247
+ */
248
+ getSupportedMetadata() {
249
+ return []; // PPM format doesn't support metadata preservation
250
+ }
251
+ /**
252
+ * Extract metadata from PPM data without fully decoding the pixel data
253
+ * @param data Raw PPM data
254
+ * @returns Extracted metadata or undefined
255
+ */
256
+ extractMetadata(data) {
257
+ if (!this.canDecode(data)) {
258
+ return Promise.resolve(undefined);
259
+ }
260
+ const metadata = {
261
+ format: "ppm",
262
+ compression: "none",
263
+ frameCount: 1,
264
+ bitDepth: 8,
265
+ colorType: "rgb",
266
+ };
267
+ // PPM is always RGB, uncompressed, and typically 8-bit
268
+ // P3 is ASCII, P6 is binary
269
+ if (data[1] === 0x33) {
270
+ // '3'
271
+ metadata.compression = "none"; // ASCII encoding
272
+ }
273
+ else if (data[1] === 0x36) {
274
+ // '6'
275
+ metadata.compression = "none"; // Binary encoding
276
+ }
277
+ return Promise.resolve(metadata);
278
+ }
245
279
  }
246
280
  exports.PPMFormat = PPMFormat;
@@ -79,5 +79,12 @@ export declare class TIFFFormat implements ImageFormat {
79
79
  * TIFF supports extensive EXIF metadata including GPS and InteropIFD
80
80
  */
81
81
  getSupportedMetadata(): Array<keyof ImageMetadata>;
82
+ /**
83
+ * Extract metadata from TIFF data without fully decoding the pixel data
84
+ * This quickly parses IFD entries to extract metadata
85
+ * @param data Raw TIFF data
86
+ * @returns Extracted metadata or undefined
87
+ */
88
+ extractMetadata(data: Uint8Array): Promise<ImageMetadata | undefined>;
82
89
  }
83
90
  //# sourceMappingURL=tiff.d.ts.map
@@ -966,5 +966,139 @@ class TIFFFormat {
966
966
  "physicalHeight",
967
967
  ];
968
968
  }
969
+ /**
970
+ * Extract metadata from TIFF data without fully decoding the pixel data
971
+ * This quickly parses IFD entries to extract metadata
972
+ * @param data Raw TIFF data
973
+ * @returns Extracted metadata or undefined
974
+ */
975
+ extractMetadata(data) {
976
+ if (!this.canDecode(data)) {
977
+ return Promise.resolve(undefined);
978
+ }
979
+ // Determine byte order
980
+ const isLittleEndian = data[0] === 0x49 && data[1] === 0x49;
981
+ if (!isLittleEndian && !(data[0] === 0x4d && data[1] === 0x4d)) {
982
+ return Promise.resolve(undefined);
983
+ }
984
+ // Read IFD offset
985
+ const ifdOffset = this.readUint32(data, 4, isLittleEndian);
986
+ if (ifdOffset >= data.length) {
987
+ return Promise.resolve(undefined);
988
+ }
989
+ // Get dimensions for DPI calculation
990
+ const width = this.getIFDValue(data, ifdOffset, 0x0100, isLittleEndian);
991
+ const height = this.getIFDValue(data, ifdOffset, 0x0101, isLittleEndian);
992
+ if (!width || !height) {
993
+ return Promise.resolve(undefined);
994
+ }
995
+ // Extract metadata from TIFF tags
996
+ const metadata = {
997
+ format: "tiff",
998
+ frameCount: 1,
999
+ bitDepth: 8,
1000
+ };
1001
+ // Get compression type
1002
+ const compression = this.getIFDValue(data, ifdOffset, 0x0103, isLittleEndian);
1003
+ if (compression === 1) {
1004
+ metadata.compression = "none";
1005
+ }
1006
+ else if (compression === 5) {
1007
+ metadata.compression = "lzw";
1008
+ }
1009
+ else if (compression === 7) {
1010
+ metadata.compression = "jpeg";
1011
+ }
1012
+ else if (compression === 32773) {
1013
+ metadata.compression = "packbits";
1014
+ }
1015
+ else if (compression) {
1016
+ metadata.compression = `unknown-${compression}`;
1017
+ }
1018
+ // Get bits per sample
1019
+ const bitsPerSample = this.getIFDValue(data, ifdOffset, 0x0102, isLittleEndian);
1020
+ if (bitsPerSample) {
1021
+ metadata.bitDepth = bitsPerSample;
1022
+ }
1023
+ // Get photometric interpretation for color type
1024
+ const photometric = this.getIFDValue(data, ifdOffset, 0x0106, isLittleEndian);
1025
+ const samplesPerPixel = this.getIFDValue(data, ifdOffset, 0x0115, isLittleEndian);
1026
+ if (photometric === 0 || photometric === 1) {
1027
+ metadata.colorType = "grayscale";
1028
+ }
1029
+ else if (photometric === 2) {
1030
+ if (samplesPerPixel === 3) {
1031
+ metadata.colorType = "rgb";
1032
+ }
1033
+ else if (samplesPerPixel === 4) {
1034
+ metadata.colorType = "rgba";
1035
+ }
1036
+ }
1037
+ else if (photometric === 3) {
1038
+ metadata.colorType = "indexed";
1039
+ }
1040
+ // Count IFDs (pages/frames) by following the chain
1041
+ let currentIfdOffset = ifdOffset;
1042
+ let frameCount = 0;
1043
+ while (currentIfdOffset > 0 && currentIfdOffset < data.length &&
1044
+ frameCount < 1000) {
1045
+ frameCount++;
1046
+ // Read number of entries in this IFD
1047
+ const numEntries = this.readUint16(data, currentIfdOffset, isLittleEndian);
1048
+ // Next IFD offset is after all entries (2 + numEntries * 12 bytes)
1049
+ const nextIfdOffsetPos = currentIfdOffset + 2 + (numEntries * 12);
1050
+ if (nextIfdOffsetPos + 4 > data.length)
1051
+ break;
1052
+ currentIfdOffset = this.readUint32(data, nextIfdOffsetPos, isLittleEndian);
1053
+ }
1054
+ metadata.frameCount = frameCount;
1055
+ // XResolution (0x011a) and YResolution (0x011b) for DPI
1056
+ const xResOffset = this.getIFDValue(data, ifdOffset, 0x011a, isLittleEndian);
1057
+ const yResOffset = this.getIFDValue(data, ifdOffset, 0x011b, isLittleEndian);
1058
+ if (xResOffset && xResOffset < data.length - 8) {
1059
+ const numerator = this.readUint32(data, xResOffset, isLittleEndian);
1060
+ const denominator = this.readUint32(data, xResOffset + 4, isLittleEndian);
1061
+ if (denominator > 0) {
1062
+ metadata.dpiX = Math.round(numerator / denominator);
1063
+ }
1064
+ }
1065
+ if (yResOffset && yResOffset < data.length - 8) {
1066
+ const numerator = this.readUint32(data, yResOffset, isLittleEndian);
1067
+ const denominator = this.readUint32(data, yResOffset + 4, isLittleEndian);
1068
+ if (denominator > 0) {
1069
+ metadata.dpiY = Math.round(numerator / denominator);
1070
+ }
1071
+ }
1072
+ // Calculate physical dimensions if DPI is available
1073
+ if (metadata.dpiX && metadata.dpiY) {
1074
+ metadata.physicalWidth = width / metadata.dpiX;
1075
+ metadata.physicalHeight = height / metadata.dpiY;
1076
+ }
1077
+ // ImageDescription (0x010e)
1078
+ const descOffset = this.getIFDValue(data, ifdOffset, 0x010e, isLittleEndian);
1079
+ if (descOffset && descOffset < data.length) {
1080
+ metadata.description = this.readString(data, descOffset);
1081
+ }
1082
+ // Artist (0x013b)
1083
+ const artistOffset = this.getIFDValue(data, ifdOffset, 0x013b, isLittleEndian);
1084
+ if (artistOffset && artistOffset < data.length) {
1085
+ metadata.author = this.readString(data, artistOffset);
1086
+ }
1087
+ // Copyright (0x8298)
1088
+ const copyrightOffset = this.getIFDValue(data, ifdOffset, 0x8298, isLittleEndian);
1089
+ if (copyrightOffset && copyrightOffset < data.length) {
1090
+ metadata.copyright = this.readString(data, copyrightOffset);
1091
+ }
1092
+ // DateTime (0x0132)
1093
+ const dateTimeOffset = this.getIFDValue(data, ifdOffset, 0x0132, isLittleEndian);
1094
+ if (dateTimeOffset && dateTimeOffset < data.length) {
1095
+ const dateStr = this.readString(data, dateTimeOffset);
1096
+ const match = dateStr.match(/^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/);
1097
+ if (match) {
1098
+ metadata.creationDate = new Date(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]), parseInt(match[4]), parseInt(match[5]), parseInt(match[6]));
1099
+ }
1100
+ }
1101
+ return Promise.resolve(Object.keys(metadata).length > 0 ? metadata : undefined);
1102
+ }
969
1103
  }
970
1104
  exports.TIFFFormat = TIFFFormat;
@@ -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