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
@@ -1060,4 +1060,80 @@ export class JPEGFormat {
1060
1060
  "dpiY",
1061
1061
  ];
1062
1062
  }
1063
+ /**
1064
+ * Extract metadata from JPEG data without fully decoding the pixel data
1065
+ * This quickly parses JFIF and EXIF markers to extract metadata
1066
+ * @param data Raw JPEG data
1067
+ * @returns Extracted metadata or undefined
1068
+ */
1069
+ extractMetadata(data) {
1070
+ if (!this.canDecode(data)) {
1071
+ return Promise.resolve(undefined);
1072
+ }
1073
+ // Parse JPEG structure to extract metadata
1074
+ let pos = 2; // Skip initial FF D8
1075
+ const metadata = {
1076
+ format: "jpeg",
1077
+ compression: "dct",
1078
+ frameCount: 1,
1079
+ bitDepth: 8,
1080
+ colorType: "rgb",
1081
+ };
1082
+ let width = 0;
1083
+ let height = 0;
1084
+ while (pos < data.length - 1) {
1085
+ if (data[pos] !== 0xff) {
1086
+ pos++;
1087
+ continue;
1088
+ }
1089
+ const marker = data[pos + 1];
1090
+ pos += 2;
1091
+ // SOF markers (Start of Frame) - get dimensions for DPI calculation
1092
+ if (marker >= 0xc0 && marker <= 0xcf &&
1093
+ marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {
1094
+ const length = (data[pos] << 8) | data[pos + 1];
1095
+ // precision at pos+2
1096
+ const precision = data[pos + 2];
1097
+ if (precision && precision !== 8) {
1098
+ metadata.bitDepth = precision;
1099
+ }
1100
+ height = (data[pos + 3] << 8) | data[pos + 4];
1101
+ width = (data[pos + 5] << 8) | data[pos + 6];
1102
+ // Check number of components
1103
+ const numComponents = data[pos + 7];
1104
+ if (numComponents === 1) {
1105
+ metadata.colorType = "grayscale";
1106
+ }
1107
+ // Don't break - continue parsing for metadata
1108
+ pos += length;
1109
+ continue;
1110
+ }
1111
+ // APP0 marker (JFIF)
1112
+ if (marker === 0xe0) {
1113
+ const length = (data[pos] << 8) | data[pos + 1];
1114
+ const appData = data.slice(pos + 2, pos + length);
1115
+ this.parseJFIF(appData, metadata, width, height);
1116
+ pos += length;
1117
+ continue;
1118
+ }
1119
+ // APP1 marker (EXIF)
1120
+ if (marker === 0xe1) {
1121
+ const length = (data[pos] << 8) | data[pos + 1];
1122
+ const appData = data.slice(pos + 2, pos + length);
1123
+ this.parseEXIF(appData, metadata);
1124
+ pos += length;
1125
+ continue;
1126
+ }
1127
+ // Skip other markers
1128
+ if (marker === 0xd9 || marker === 0xda)
1129
+ break; // EOI or SOS
1130
+ if (marker >= 0xd0 && marker <= 0xd8)
1131
+ continue; // RST markers have no length
1132
+ if (marker === 0x01)
1133
+ continue; // TEM has no length
1134
+ const length = (data[pos] << 8) | data[pos + 1];
1135
+ pos += length;
1136
+ }
1137
+ return Promise.resolve(Object.keys(metadata).length > 0 ? metadata : undefined);
1138
+ }
1063
1139
  }
@@ -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
@@ -174,4 +174,70 @@ export class PAMFormat {
174
174
  output.set(data, headerBytes.length);
175
175
  return Promise.resolve(output);
176
176
  }
177
+ /**
178
+ * Get the list of metadata fields supported by PAM format
179
+ */
180
+ getSupportedMetadata() {
181
+ return []; // PAM format doesn't support metadata preservation
182
+ }
183
+ /**
184
+ * Extract metadata from PAM data without fully decoding the pixel data
185
+ * @param data Raw PAM 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: "pam",
194
+ compression: "none",
195
+ frameCount: 1,
196
+ bitDepth: 8, // PAM typically uses 8 bits per channel
197
+ colorType: "rgba",
198
+ };
199
+ // Try to parse the header to get actual color type
200
+ const decoder = new TextDecoder();
201
+ const headerText = decoder.decode(data.slice(0, Math.min(200, data.length)));
202
+ // Look for TUPLTYPE to determine color type
203
+ const tupltypeMatch = headerText.match(/TUPLTYPE\s+(\S+)/);
204
+ if (tupltypeMatch) {
205
+ const tupltype = tupltypeMatch[1];
206
+ if (tupltype === "GRAYSCALE") {
207
+ metadata.colorType = "grayscale";
208
+ }
209
+ else if (tupltype === "RGB") {
210
+ metadata.colorType = "rgb";
211
+ }
212
+ else if (tupltype === "RGB_ALPHA") {
213
+ metadata.colorType = "rgba";
214
+ }
215
+ }
216
+ // Look for DEPTH to determine number of channels
217
+ const depthMatch = headerText.match(/DEPTH\s+(\d+)/);
218
+ if (depthMatch) {
219
+ const depth = parseInt(depthMatch[1]);
220
+ if (depth === 1) {
221
+ metadata.colorType = "grayscale";
222
+ }
223
+ else if (depth === 3) {
224
+ metadata.colorType = "rgb";
225
+ }
226
+ else if (depth === 4) {
227
+ metadata.colorType = "rgba";
228
+ }
229
+ }
230
+ // Look for MAXVAL to determine bit depth
231
+ const maxvalMatch = headerText.match(/MAXVAL\s+(\d+)/);
232
+ if (maxvalMatch) {
233
+ const maxval = parseInt(maxvalMatch[1]);
234
+ if (maxval === 255) {
235
+ metadata.bitDepth = 8;
236
+ }
237
+ else if (maxval === 65535) {
238
+ metadata.bitDepth = 16;
239
+ }
240
+ }
241
+ return Promise.resolve(metadata);
242
+ }
177
243
  }
@@ -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
@@ -201,4 +201,49 @@ export class PCXFormat {
201
201
  result.set(rleData, header.length);
202
202
  return Promise.resolve(result);
203
203
  }
204
+ /**
205
+ * Get the list of metadata fields supported by PCX format
206
+ */
207
+ getSupportedMetadata() {
208
+ return [
209
+ "dpiX", // DPI in header
210
+ "dpiY", // DPI in header
211
+ ];
212
+ }
213
+ /**
214
+ * Extract metadata from PCX data without fully decoding the pixel data
215
+ * @param data Raw PCX data
216
+ * @returns Extracted metadata or undefined
217
+ */
218
+ extractMetadata(data) {
219
+ if (!this.canDecode(data)) {
220
+ return Promise.resolve(undefined);
221
+ }
222
+ const metadata = {
223
+ format: "pcx",
224
+ compression: "rle",
225
+ frameCount: 1,
226
+ bitDepth: data[3], // Bits per pixel per plane
227
+ colorType: "rgb",
228
+ };
229
+ // Check number of planes to determine color type
230
+ const numPlanes = data[65];
231
+ if (numPlanes === 1) {
232
+ metadata.colorType = "indexed";
233
+ }
234
+ else if (numPlanes === 3) {
235
+ metadata.colorType = "rgb";
236
+ }
237
+ else if (numPlanes === 4) {
238
+ metadata.colorType = "rgba";
239
+ }
240
+ // DPI information
241
+ const dpiX = data[12] | (data[13] << 8);
242
+ const dpiY = data[14] | (data[15] << 8);
243
+ if (dpiX > 0)
244
+ metadata.dpiX = dpiX;
245
+ if (dpiY > 0)
246
+ metadata.dpiY = dpiY;
247
+ return Promise.resolve(metadata);
248
+ }
204
249
  }
@@ -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
@@ -137,4 +137,91 @@ export class PNGFormat extends PNGBase {
137
137
  // Concatenate all chunks
138
138
  return this.concatenateArrays(chunks);
139
139
  }
140
+ /**
141
+ * Get the list of metadata fields supported by PNG format
142
+ * Delegates to PNGBase implementation
143
+ */
144
+ getSupportedMetadata() {
145
+ return super.getSupportedMetadata();
146
+ }
147
+ /**
148
+ * Extract metadata from PNG data without fully decoding the pixel data
149
+ * This quickly parses PNG chunks to extract metadata
150
+ * @param data Raw PNG data
151
+ * @returns Extracted metadata or undefined
152
+ */
153
+ extractMetadata(data) {
154
+ if (!this.canDecode(data)) {
155
+ return Promise.resolve(undefined);
156
+ }
157
+ let pos = 8; // Skip PNG signature
158
+ let width = 0;
159
+ let height = 0;
160
+ const metadata = {
161
+ format: "png",
162
+ compression: "deflate",
163
+ frameCount: 1,
164
+ };
165
+ // Parse chunks for metadata only
166
+ while (pos < data.length) {
167
+ if (pos + 8 > data.length)
168
+ break;
169
+ const length = this.readUint32(data, pos);
170
+ pos += 4;
171
+ const type = String.fromCharCode(data[pos], data[pos + 1], data[pos + 2], data[pos + 3]);
172
+ pos += 4;
173
+ if (pos + length + 4 > data.length)
174
+ break;
175
+ const chunkData = data.slice(pos, pos + length);
176
+ pos += length;
177
+ pos += 4; // Skip CRC
178
+ if (type === "IHDR") {
179
+ width = this.readUint32(chunkData, 0);
180
+ height = this.readUint32(chunkData, 4);
181
+ // Parse bit depth and color type from IHDR
182
+ if (chunkData.length >= 9) {
183
+ metadata.bitDepth = chunkData[8];
184
+ const colorTypeCode = chunkData[9];
185
+ // PNG color types: 0=grayscale, 2=rgb, 3=indexed, 4=grayscale+alpha, 6=rgba
186
+ switch (colorTypeCode) {
187
+ case 0:
188
+ metadata.colorType = "grayscale";
189
+ break;
190
+ case 2:
191
+ metadata.colorType = "rgb";
192
+ break;
193
+ case 3:
194
+ metadata.colorType = "indexed";
195
+ break;
196
+ case 4:
197
+ metadata.colorType = "grayscale-alpha";
198
+ break;
199
+ case 6:
200
+ metadata.colorType = "rgba";
201
+ break;
202
+ }
203
+ }
204
+ }
205
+ else if (type === "pHYs") {
206
+ // Physical pixel dimensions
207
+ this.parsePhysChunk(chunkData, metadata, width, height);
208
+ }
209
+ else if (type === "tEXt") {
210
+ // Text chunk
211
+ this.parseTextChunk(chunkData, metadata);
212
+ }
213
+ else if (type === "iTXt") {
214
+ // International text chunk
215
+ this.parseITxtChunk(chunkData, metadata);
216
+ }
217
+ else if (type === "eXIf") {
218
+ // EXIF chunk
219
+ this.parseExifChunk(chunkData, metadata);
220
+ }
221
+ else if (type === "IEND") {
222
+ break;
223
+ }
224
+ }
225
+ return Promise.resolve(Object.keys(metadata).length > 0 ? metadata : undefined);
226
+ }
140
227
  }
@@ -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
@@ -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
  }
@@ -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
@@ -963,4 +963,138 @@ export class TIFFFormat {
963
963
  "physicalHeight",
964
964
  ];
965
965
  }
966
+ /**
967
+ * Extract metadata from TIFF data without fully decoding the pixel data
968
+ * This quickly parses IFD entries to extract metadata
969
+ * @param data Raw TIFF data
970
+ * @returns Extracted metadata or undefined
971
+ */
972
+ extractMetadata(data) {
973
+ if (!this.canDecode(data)) {
974
+ return Promise.resolve(undefined);
975
+ }
976
+ // Determine byte order
977
+ const isLittleEndian = data[0] === 0x49 && data[1] === 0x49;
978
+ if (!isLittleEndian && !(data[0] === 0x4d && data[1] === 0x4d)) {
979
+ return Promise.resolve(undefined);
980
+ }
981
+ // Read IFD offset
982
+ const ifdOffset = this.readUint32(data, 4, isLittleEndian);
983
+ if (ifdOffset >= data.length) {
984
+ return Promise.resolve(undefined);
985
+ }
986
+ // Get dimensions for DPI calculation
987
+ const width = this.getIFDValue(data, ifdOffset, 0x0100, isLittleEndian);
988
+ const height = this.getIFDValue(data, ifdOffset, 0x0101, isLittleEndian);
989
+ if (!width || !height) {
990
+ return Promise.resolve(undefined);
991
+ }
992
+ // Extract metadata from TIFF tags
993
+ const metadata = {
994
+ format: "tiff",
995
+ frameCount: 1,
996
+ bitDepth: 8,
997
+ };
998
+ // Get compression type
999
+ const compression = this.getIFDValue(data, ifdOffset, 0x0103, isLittleEndian);
1000
+ if (compression === 1) {
1001
+ metadata.compression = "none";
1002
+ }
1003
+ else if (compression === 5) {
1004
+ metadata.compression = "lzw";
1005
+ }
1006
+ else if (compression === 7) {
1007
+ metadata.compression = "jpeg";
1008
+ }
1009
+ else if (compression === 32773) {
1010
+ metadata.compression = "packbits";
1011
+ }
1012
+ else if (compression) {
1013
+ metadata.compression = `unknown-${compression}`;
1014
+ }
1015
+ // Get bits per sample
1016
+ const bitsPerSample = this.getIFDValue(data, ifdOffset, 0x0102, isLittleEndian);
1017
+ if (bitsPerSample) {
1018
+ metadata.bitDepth = bitsPerSample;
1019
+ }
1020
+ // Get photometric interpretation for color type
1021
+ const photometric = this.getIFDValue(data, ifdOffset, 0x0106, isLittleEndian);
1022
+ const samplesPerPixel = this.getIFDValue(data, ifdOffset, 0x0115, isLittleEndian);
1023
+ if (photometric === 0 || photometric === 1) {
1024
+ metadata.colorType = "grayscale";
1025
+ }
1026
+ else if (photometric === 2) {
1027
+ if (samplesPerPixel === 3) {
1028
+ metadata.colorType = "rgb";
1029
+ }
1030
+ else if (samplesPerPixel === 4) {
1031
+ metadata.colorType = "rgba";
1032
+ }
1033
+ }
1034
+ else if (photometric === 3) {
1035
+ metadata.colorType = "indexed";
1036
+ }
1037
+ // Count IFDs (pages/frames) by following the chain
1038
+ let currentIfdOffset = ifdOffset;
1039
+ let frameCount = 0;
1040
+ while (currentIfdOffset > 0 && currentIfdOffset < data.length &&
1041
+ frameCount < 1000) {
1042
+ frameCount++;
1043
+ // Read number of entries in this IFD
1044
+ const numEntries = this.readUint16(data, currentIfdOffset, isLittleEndian);
1045
+ // Next IFD offset is after all entries (2 + numEntries * 12 bytes)
1046
+ const nextIfdOffsetPos = currentIfdOffset + 2 + (numEntries * 12);
1047
+ if (nextIfdOffsetPos + 4 > data.length)
1048
+ break;
1049
+ currentIfdOffset = this.readUint32(data, nextIfdOffsetPos, isLittleEndian);
1050
+ }
1051
+ metadata.frameCount = frameCount;
1052
+ // XResolution (0x011a) and YResolution (0x011b) for DPI
1053
+ const xResOffset = this.getIFDValue(data, ifdOffset, 0x011a, isLittleEndian);
1054
+ const yResOffset = this.getIFDValue(data, ifdOffset, 0x011b, isLittleEndian);
1055
+ if (xResOffset && xResOffset < data.length - 8) {
1056
+ const numerator = this.readUint32(data, xResOffset, isLittleEndian);
1057
+ const denominator = this.readUint32(data, xResOffset + 4, isLittleEndian);
1058
+ if (denominator > 0) {
1059
+ metadata.dpiX = Math.round(numerator / denominator);
1060
+ }
1061
+ }
1062
+ if (yResOffset && yResOffset < data.length - 8) {
1063
+ const numerator = this.readUint32(data, yResOffset, isLittleEndian);
1064
+ const denominator = this.readUint32(data, yResOffset + 4, isLittleEndian);
1065
+ if (denominator > 0) {
1066
+ metadata.dpiY = Math.round(numerator / denominator);
1067
+ }
1068
+ }
1069
+ // Calculate physical dimensions if DPI is available
1070
+ if (metadata.dpiX && metadata.dpiY) {
1071
+ metadata.physicalWidth = width / metadata.dpiX;
1072
+ metadata.physicalHeight = height / metadata.dpiY;
1073
+ }
1074
+ // ImageDescription (0x010e)
1075
+ const descOffset = this.getIFDValue(data, ifdOffset, 0x010e, isLittleEndian);
1076
+ if (descOffset && descOffset < data.length) {
1077
+ metadata.description = this.readString(data, descOffset);
1078
+ }
1079
+ // Artist (0x013b)
1080
+ const artistOffset = this.getIFDValue(data, ifdOffset, 0x013b, isLittleEndian);
1081
+ if (artistOffset && artistOffset < data.length) {
1082
+ metadata.author = this.readString(data, artistOffset);
1083
+ }
1084
+ // Copyright (0x8298)
1085
+ const copyrightOffset = this.getIFDValue(data, ifdOffset, 0x8298, isLittleEndian);
1086
+ if (copyrightOffset && copyrightOffset < data.length) {
1087
+ metadata.copyright = this.readString(data, copyrightOffset);
1088
+ }
1089
+ // DateTime (0x0132)
1090
+ const dateTimeOffset = this.getIFDValue(data, ifdOffset, 0x0132, isLittleEndian);
1091
+ if (dateTimeOffset && dateTimeOffset < data.length) {
1092
+ const dateStr = this.readString(data, dateTimeOffset);
1093
+ const match = dateStr.match(/^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/);
1094
+ if (match) {
1095
+ metadata.creationDate = new Date(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]), parseInt(match[4]), parseInt(match[5]), parseInt(match[6]));
1096
+ }
1097
+ }
1098
+ return Promise.resolve(Object.keys(metadata).length > 0 ? metadata : undefined);
1099
+ }
966
1100
  }
@@ -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