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
package/esm/src/formats/jpeg.js
CHANGED
|
@@ -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
|
}
|
package/esm/src/formats/pam.d.ts
CHANGED
|
@@ -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
|
package/esm/src/formats/pam.js
CHANGED
|
@@ -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
|
}
|
package/esm/src/formats/pcx.d.ts
CHANGED
|
@@ -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
|
package/esm/src/formats/pcx.js
CHANGED
|
@@ -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
|
}
|
package/esm/src/formats/png.d.ts
CHANGED
|
@@ -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
|
package/esm/src/formats/png.js
CHANGED
|
@@ -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
|
}
|
package/esm/src/formats/ppm.d.ts
CHANGED
|
@@ -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
|
package/esm/src/formats/ppm.js
CHANGED
|
@@ -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
|
package/esm/src/formats/tiff.js
CHANGED
|
@@ -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
|