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.
- package/README.md +333 -289
- package/esm/mod.d.ts +2 -0
- package/esm/mod.js +2 -0
- package/esm/src/formats/apng.d.ts +13 -1
- package/esm/src/formats/apng.js +97 -0
- package/esm/src/formats/ascii.d.ts +11 -1
- package/esm/src/formats/ascii.js +24 -0
- package/esm/src/formats/avif.d.ts +96 -0
- package/esm/src/formats/avif.js +607 -0
- package/esm/src/formats/bmp.d.ts +11 -1
- package/esm/src/formats/bmp.js +73 -0
- package/esm/src/formats/dng.d.ts +13 -1
- package/esm/src/formats/dng.js +26 -4
- package/esm/src/formats/gif.d.ts +15 -2
- package/esm/src/formats/gif.js +146 -4
- package/esm/src/formats/heic.d.ts +96 -0
- package/esm/src/formats/heic.js +608 -0
- package/esm/src/formats/ico.d.ts +11 -1
- package/esm/src/formats/ico.js +28 -0
- package/esm/src/formats/jpeg.d.ts +7 -0
- package/esm/src/formats/jpeg.js +76 -0
- package/esm/src/formats/pam.d.ts +11 -1
- package/esm/src/formats/pam.js +66 -0
- package/esm/src/formats/pcx.d.ts +11 -1
- package/esm/src/formats/pcx.js +45 -0
- package/esm/src/formats/png.d.ts +13 -1
- package/esm/src/formats/png.js +87 -0
- package/esm/src/formats/ppm.d.ts +11 -1
- package/esm/src/formats/ppm.js +34 -0
- package/esm/src/formats/tiff.d.ts +7 -0
- package/esm/src/formats/tiff.js +134 -0
- package/esm/src/formats/webp.d.ts +7 -0
- package/esm/src/formats/webp.js +92 -0
- package/esm/src/image.d.ts +9 -0
- package/esm/src/image.js +28 -0
- package/esm/src/types.d.ts +18 -0
- package/package.json +18 -1
- package/script/mod.d.ts +2 -0
- package/script/mod.js +5 -1
- package/script/src/formats/apng.d.ts +13 -1
- package/script/src/formats/apng.js +97 -0
- package/script/src/formats/ascii.d.ts +11 -1
- package/script/src/formats/ascii.js +24 -0
- package/script/src/formats/avif.d.ts +96 -0
- package/script/src/formats/avif.js +611 -0
- package/script/src/formats/bmp.d.ts +11 -1
- package/script/src/formats/bmp.js +73 -0
- package/script/src/formats/dng.d.ts +13 -1
- package/script/src/formats/dng.js +26 -4
- package/script/src/formats/gif.d.ts +15 -2
- package/script/src/formats/gif.js +146 -4
- package/script/src/formats/heic.d.ts +96 -0
- package/script/src/formats/heic.js +612 -0
- package/script/src/formats/ico.d.ts +11 -1
- package/script/src/formats/ico.js +28 -0
- package/script/src/formats/jpeg.d.ts +7 -0
- package/script/src/formats/jpeg.js +76 -0
- package/script/src/formats/pam.d.ts +11 -1
- package/script/src/formats/pam.js +66 -0
- package/script/src/formats/pcx.d.ts +11 -1
- package/script/src/formats/pcx.js +45 -0
- package/script/src/formats/png.d.ts +13 -1
- package/script/src/formats/png.js +87 -0
- package/script/src/formats/ppm.d.ts +11 -1
- package/script/src/formats/ppm.js +34 -0
- package/script/src/formats/tiff.d.ts +7 -0
- package/script/src/formats/tiff.js +134 -0
- package/script/src/formats/webp.d.ts +7 -0
- package/script/src/formats/webp.js +92 -0
- package/script/src/image.d.ts +9 -0
- package/script/src/image.js +28 -0
- 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
|