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