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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageData, ImageFormat } from "../types.js";
|
|
1
|
+
import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata } from "../types.js";
|
|
2
2
|
/**
|
|
3
3
|
* PPM format handler
|
|
4
4
|
* Implements the Netpbm PPM (Portable PixMap) format.
|
|
@@ -34,17 +34,27 @@ export declare class PPMFormat implements ImageFormat {
|
|
|
34
34
|
* @param data Raw PPM image data
|
|
35
35
|
* @returns Decoded image data with RGBA pixels
|
|
36
36
|
*/
|
|
37
|
-
decode(data: Uint8Array): Promise<ImageData>;
|
|
37
|
+
decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
|
|
38
38
|
/**
|
|
39
39
|
* Encode RGBA image data to PPM format (P6 binary)
|
|
40
40
|
* Note: Alpha channel is ignored as PPM doesn't support transparency
|
|
41
41
|
* @param imageData Image data to encode
|
|
42
42
|
* @returns Encoded PPM image bytes
|
|
43
43
|
*/
|
|
44
|
-
encode(imageData: ImageData): Promise<Uint8Array>;
|
|
44
|
+
encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
|
|
45
45
|
/**
|
|
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
|
|
@@ -59,7 +59,7 @@ class PPMFormat {
|
|
|
59
59
|
* @param data Raw PPM image data
|
|
60
60
|
* @returns Decoded image data with RGBA pixels
|
|
61
61
|
*/
|
|
62
|
-
decode(data) {
|
|
62
|
+
decode(data, _options) {
|
|
63
63
|
if (!this.canDecode(data)) {
|
|
64
64
|
throw new Error("Invalid PPM signature");
|
|
65
65
|
}
|
|
@@ -211,7 +211,7 @@ class PPMFormat {
|
|
|
211
211
|
* @param imageData Image data to encode
|
|
212
212
|
* @returns Encoded PPM image bytes
|
|
213
213
|
*/
|
|
214
|
-
encode(imageData) {
|
|
214
|
+
encode(imageData, _options) {
|
|
215
215
|
const { width, height, data } = imageData;
|
|
216
216
|
// Validate input
|
|
217
217
|
if (data.length !== width * height * 4) {
|
|
@@ -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;
|
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
import type { ImageData, ImageFormat, ImageMetadata, MultiFrameImageData } from "../types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Options for TIFF encoding
|
|
4
|
-
*/
|
|
5
|
-
export interface TIFFEncodeOptions {
|
|
6
|
-
/** Compression method: "none" for uncompressed (default), "lzw" for LZW compression */
|
|
7
|
-
compression?: "none" | "lzw";
|
|
8
|
-
/** Encode as grayscale instead of RGB/RGBA */
|
|
9
|
-
grayscale?: boolean;
|
|
10
|
-
/** Encode as RGB without alpha channel (ignored if grayscale is true) */
|
|
11
|
-
rgb?: boolean;
|
|
12
|
-
}
|
|
1
|
+
import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, MultiFrameImageData } from "../types.js";
|
|
13
2
|
/**
|
|
14
3
|
* TIFF format handler
|
|
15
|
-
* Implements pure-JS TIFF decoder for uncompressed and
|
|
16
|
-
* and encoder for uncompressed and
|
|
17
|
-
* for
|
|
4
|
+
* Implements pure-JS TIFF decoder for uncompressed, LZW, PackBits, and Deflate-compressed RGB/RGBA images
|
|
5
|
+
* and encoder for uncompressed, LZW, PackBits, and Deflate-compressed RGBA TIFFs. Falls back to ImageDecoder
|
|
6
|
+
* for JPEG-compressed TIFFs.
|
|
18
7
|
* Supports multi-page TIFF files.
|
|
19
8
|
*/
|
|
20
9
|
export declare class TIFFFormat implements ImageFormat {
|
|
@@ -38,12 +27,12 @@ export declare class TIFFFormat implements ImageFormat {
|
|
|
38
27
|
* @param data Raw TIFF image data
|
|
39
28
|
* @returns Decoded image data with RGBA pixels of first page
|
|
40
29
|
*/
|
|
41
|
-
decode(data: Uint8Array): Promise<ImageData>;
|
|
30
|
+
decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
|
|
42
31
|
encode(imageData: ImageData, options?: unknown): Promise<Uint8Array>;
|
|
43
32
|
/**
|
|
44
33
|
* Decode all pages from a multi-page TIFF
|
|
45
34
|
*/
|
|
46
|
-
decodeFrames(data: Uint8Array): Promise<MultiFrameImageData>;
|
|
35
|
+
decodeFrames(data: Uint8Array, _options?: ImageDecoderOptions): Promise<MultiFrameImageData>;
|
|
47
36
|
/**
|
|
48
37
|
* Encode multi-page TIFF
|
|
49
38
|
*/
|
|
@@ -65,7 +54,7 @@ export declare class TIFFFormat implements ImageFormat {
|
|
|
65
54
|
private decodeUsingRuntime;
|
|
66
55
|
private readString;
|
|
67
56
|
/**
|
|
68
|
-
* Pure JavaScript TIFF decoder for uncompressed and
|
|
57
|
+
* Pure JavaScript TIFF decoder for uncompressed, LZW, PackBits, and Deflate-compressed RGB/RGBA images
|
|
69
58
|
* Returns null if the TIFF uses unsupported features
|
|
70
59
|
*/
|
|
71
60
|
private decodePureJS;
|
|
@@ -79,5 +68,12 @@ export declare class TIFFFormat implements ImageFormat {
|
|
|
79
68
|
* TIFF supports extensive EXIF metadata including GPS and InteropIFD
|
|
80
69
|
*/
|
|
81
70
|
getSupportedMetadata(): Array<keyof ImageMetadata>;
|
|
71
|
+
/**
|
|
72
|
+
* Extract metadata from TIFF data without fully decoding the pixel data
|
|
73
|
+
* This quickly parses IFD entries to extract metadata
|
|
74
|
+
* @param data Raw TIFF data
|
|
75
|
+
* @returns Extracted metadata or undefined
|
|
76
|
+
*/
|
|
77
|
+
extractMetadata(data: Uint8Array): Promise<ImageMetadata | undefined>;
|
|
82
78
|
}
|
|
83
79
|
//# sourceMappingURL=tiff.d.ts.map
|
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TIFFFormat = void 0;
|
|
4
4
|
const tiff_lzw_js_1 = require("../utils/tiff_lzw.js");
|
|
5
|
+
const tiff_packbits_js_1 = require("../utils/tiff_packbits.js");
|
|
6
|
+
const tiff_deflate_js_1 = require("../utils/tiff_deflate.js");
|
|
5
7
|
const security_js_1 = require("../utils/security.js");
|
|
6
8
|
// Constants for unit conversions
|
|
7
9
|
const DEFAULT_DPI = 72;
|
|
8
10
|
/**
|
|
9
11
|
* TIFF format handler
|
|
10
|
-
* Implements pure-JS TIFF decoder for uncompressed and
|
|
11
|
-
* and encoder for uncompressed and
|
|
12
|
-
* for
|
|
12
|
+
* Implements pure-JS TIFF decoder for uncompressed, LZW, PackBits, and Deflate-compressed RGB/RGBA images
|
|
13
|
+
* and encoder for uncompressed, LZW, PackBits, and Deflate-compressed RGBA TIFFs. Falls back to ImageDecoder
|
|
14
|
+
* for JPEG-compressed TIFFs.
|
|
13
15
|
* Supports multi-page TIFF files.
|
|
14
16
|
*/
|
|
15
17
|
class TIFFFormat {
|
|
@@ -55,7 +57,7 @@ class TIFFFormat {
|
|
|
55
57
|
* @param data Raw TIFF image data
|
|
56
58
|
* @returns Decoded image data with RGBA pixels of first page
|
|
57
59
|
*/
|
|
58
|
-
async decode(data) {
|
|
60
|
+
async decode(data, _options) {
|
|
59
61
|
if (!this.canDecode(data)) {
|
|
60
62
|
throw new Error("Invalid TIFF signature");
|
|
61
63
|
}
|
|
@@ -131,7 +133,7 @@ class TIFFFormat {
|
|
|
131
133
|
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
132
134
|
};
|
|
133
135
|
}
|
|
134
|
-
encode(imageData, options) {
|
|
136
|
+
async encode(imageData, options) {
|
|
135
137
|
const { width, height, data, metadata } = imageData;
|
|
136
138
|
const opts = options;
|
|
137
139
|
const compression = opts?.compression ?? "none";
|
|
@@ -175,6 +177,16 @@ class TIFFFormat {
|
|
|
175
177
|
pixelData = encoder.compress(sourceData);
|
|
176
178
|
compressionCode = 5;
|
|
177
179
|
}
|
|
180
|
+
else if (compression === "packbits") {
|
|
181
|
+
// PackBits compress the pixel data
|
|
182
|
+
pixelData = (0, tiff_packbits_js_1.packBitsCompress)(sourceData);
|
|
183
|
+
compressionCode = 32773;
|
|
184
|
+
}
|
|
185
|
+
else if (compression === "deflate") {
|
|
186
|
+
// Deflate compress the pixel data
|
|
187
|
+
pixelData = await (0, tiff_deflate_js_1.deflateCompress)(sourceData);
|
|
188
|
+
compressionCode = 8;
|
|
189
|
+
}
|
|
178
190
|
else {
|
|
179
191
|
// Uncompressed
|
|
180
192
|
pixelData = sourceData;
|
|
@@ -332,7 +344,7 @@ class TIFFFormat {
|
|
|
332
344
|
/**
|
|
333
345
|
* Decode all pages from a multi-page TIFF
|
|
334
346
|
*/
|
|
335
|
-
async decodeFrames(data) {
|
|
347
|
+
async decodeFrames(data, _options) {
|
|
336
348
|
if (!this.canDecode(data)) {
|
|
337
349
|
throw new Error("Invalid TIFF signature");
|
|
338
350
|
}
|
|
@@ -395,7 +407,7 @@ class TIFFFormat {
|
|
|
395
407
|
/**
|
|
396
408
|
* Encode multi-page TIFF
|
|
397
409
|
*/
|
|
398
|
-
encodeFrames(imageData, options) {
|
|
410
|
+
async encodeFrames(imageData, options) {
|
|
399
411
|
const opts = options;
|
|
400
412
|
const compression = opts?.compression ?? "none";
|
|
401
413
|
if (imageData.frames.length === 0) {
|
|
@@ -420,6 +432,12 @@ class TIFFFormat {
|
|
|
420
432
|
const encoder = new tiff_lzw_js_1.TIFFLZWEncoder();
|
|
421
433
|
pixelData = encoder.compress(frame.data);
|
|
422
434
|
}
|
|
435
|
+
else if (compression === "packbits") {
|
|
436
|
+
pixelData = (0, tiff_packbits_js_1.packBitsCompress)(frame.data);
|
|
437
|
+
}
|
|
438
|
+
else if (compression === "deflate") {
|
|
439
|
+
pixelData = await (0, tiff_deflate_js_1.deflateCompress)(frame.data);
|
|
440
|
+
}
|
|
423
441
|
else {
|
|
424
442
|
pixelData = frame.data;
|
|
425
443
|
}
|
|
@@ -456,7 +474,19 @@ class TIFFFormat {
|
|
|
456
474
|
this.writeIFDEntry(result, 0x0102, 3, 4, dataOffset);
|
|
457
475
|
dataOffset += 8;
|
|
458
476
|
// Compression
|
|
459
|
-
|
|
477
|
+
let compressionCode;
|
|
478
|
+
if (compression === "lzw") {
|
|
479
|
+
compressionCode = 5;
|
|
480
|
+
}
|
|
481
|
+
else if (compression === "packbits") {
|
|
482
|
+
compressionCode = 32773;
|
|
483
|
+
}
|
|
484
|
+
else if (compression === "deflate") {
|
|
485
|
+
compressionCode = 8;
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
compressionCode = 1;
|
|
489
|
+
}
|
|
460
490
|
this.writeIFDEntry(result, 0x0103, 3, 1, compressionCode);
|
|
461
491
|
// PhotometricInterpretation
|
|
462
492
|
this.writeIFDEntry(result, 0x0106, 3, 1, 2);
|
|
@@ -467,9 +497,19 @@ class TIFFFormat {
|
|
|
467
497
|
// RowsPerStrip
|
|
468
498
|
this.writeIFDEntry(result, 0x0116, 4, 1, frame.height);
|
|
469
499
|
// StripByteCounts
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
500
|
+
let pixelDataSize;
|
|
501
|
+
if (compression === "lzw") {
|
|
502
|
+
pixelDataSize = new tiff_lzw_js_1.TIFFLZWEncoder().compress(frame.data).length;
|
|
503
|
+
}
|
|
504
|
+
else if (compression === "packbits") {
|
|
505
|
+
pixelDataSize = (0, tiff_packbits_js_1.packBitsCompress)(frame.data).length;
|
|
506
|
+
}
|
|
507
|
+
else if (compression === "deflate") {
|
|
508
|
+
pixelDataSize = (await (0, tiff_deflate_js_1.deflateCompress)(frame.data)).length;
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
pixelDataSize = frame.data.length;
|
|
512
|
+
}
|
|
473
513
|
this.writeIFDEntry(result, 0x0117, 4, 1, pixelDataSize);
|
|
474
514
|
// XResolution
|
|
475
515
|
const xResOffset = dataOffset;
|
|
@@ -572,7 +612,7 @@ class TIFFFormat {
|
|
|
572
612
|
const isLittleEndian = data[0] === 0x49;
|
|
573
613
|
// Try pure JavaScript decoder first
|
|
574
614
|
try {
|
|
575
|
-
const pureJSResult = this.decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian);
|
|
615
|
+
const pureJSResult = await this.decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian);
|
|
576
616
|
if (pureJSResult) {
|
|
577
617
|
return pureJSResult;
|
|
578
618
|
}
|
|
@@ -696,7 +736,7 @@ class TIFFFormat {
|
|
|
696
736
|
async decodeUsingRuntime(data, width, height) {
|
|
697
737
|
// Try pure JavaScript decoder first for uncompressed TIFFs
|
|
698
738
|
try {
|
|
699
|
-
const pureJSResult = this.decodePureJS(data, width, height);
|
|
739
|
+
const pureJSResult = await this.decodePureJS(data, width, height);
|
|
700
740
|
if (pureJSResult) {
|
|
701
741
|
return pureJSResult;
|
|
702
742
|
}
|
|
@@ -734,10 +774,10 @@ class TIFFFormat {
|
|
|
734
774
|
return new TextDecoder().decode(data.slice(offset, endIndex));
|
|
735
775
|
}
|
|
736
776
|
/**
|
|
737
|
-
* Pure JavaScript TIFF decoder for uncompressed and
|
|
777
|
+
* Pure JavaScript TIFF decoder for uncompressed, LZW, PackBits, and Deflate-compressed RGB/RGBA images
|
|
738
778
|
* Returns null if the TIFF uses unsupported features
|
|
739
779
|
*/
|
|
740
|
-
decodePureJS(data, width, height) {
|
|
780
|
+
async decodePureJS(data, width, height) {
|
|
741
781
|
// Validate minimum TIFF header size
|
|
742
782
|
if (data.length < 8) {
|
|
743
783
|
return null;
|
|
@@ -748,8 +788,9 @@ class TIFFFormat {
|
|
|
748
788
|
const ifdOffset = this.readUint32(data, 4, isLittleEndian);
|
|
749
789
|
// Check compression
|
|
750
790
|
const compression = this.getIFDValue(data, ifdOffset, 0x0103, isLittleEndian);
|
|
751
|
-
if (compression !== 1 && compression !== 5
|
|
752
|
-
|
|
791
|
+
if (compression !== 1 && compression !== 5 && compression !== 8 &&
|
|
792
|
+
compression !== 32773) {
|
|
793
|
+
// Support: 1 = uncompressed, 5 = LZW, 8 = Deflate, 32773 = PackBits
|
|
753
794
|
return null;
|
|
754
795
|
}
|
|
755
796
|
// Check photometric interpretation
|
|
@@ -791,6 +832,16 @@ class TIFFFormat {
|
|
|
791
832
|
const decoder = new tiff_lzw_js_1.TIFFLZWDecoder(compressedData);
|
|
792
833
|
pixelData = decoder.decompress();
|
|
793
834
|
}
|
|
835
|
+
else if (compression === 32773) {
|
|
836
|
+
// PackBits compressed
|
|
837
|
+
const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
|
|
838
|
+
pixelData = (0, tiff_packbits_js_1.packBitsDecompress)(compressedData);
|
|
839
|
+
}
|
|
840
|
+
else if (compression === 8) {
|
|
841
|
+
// Deflate compressed
|
|
842
|
+
const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
|
|
843
|
+
pixelData = await (0, tiff_deflate_js_1.deflateDecompress)(compressedData);
|
|
844
|
+
}
|
|
794
845
|
else {
|
|
795
846
|
// Uncompressed
|
|
796
847
|
pixelData = data.slice(stripOffset, stripOffset + stripByteCount);
|
|
@@ -842,11 +893,12 @@ class TIFFFormat {
|
|
|
842
893
|
* Pure JavaScript TIFF decoder for a specific IFD
|
|
843
894
|
* Returns null if the TIFF uses unsupported features
|
|
844
895
|
*/
|
|
845
|
-
decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian) {
|
|
896
|
+
async decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian) {
|
|
846
897
|
// Check compression
|
|
847
898
|
const compression = this.getIFDValue(data, ifdOffset, 0x0103, isLittleEndian);
|
|
848
|
-
if (compression !== 1 && compression !== 5
|
|
849
|
-
|
|
899
|
+
if (compression !== 1 && compression !== 5 && compression !== 8 &&
|
|
900
|
+
compression !== 32773) {
|
|
901
|
+
// Support: 1 = uncompressed, 5 = LZW, 8 = Deflate, 32773 = PackBits
|
|
850
902
|
return null;
|
|
851
903
|
}
|
|
852
904
|
// Check photometric interpretation
|
|
@@ -888,6 +940,16 @@ class TIFFFormat {
|
|
|
888
940
|
const decoder = new tiff_lzw_js_1.TIFFLZWDecoder(compressedData);
|
|
889
941
|
pixelData = decoder.decompress();
|
|
890
942
|
}
|
|
943
|
+
else if (compression === 32773) {
|
|
944
|
+
// PackBits compressed
|
|
945
|
+
const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
|
|
946
|
+
pixelData = (0, tiff_packbits_js_1.packBitsDecompress)(compressedData);
|
|
947
|
+
}
|
|
948
|
+
else if (compression === 8) {
|
|
949
|
+
// Deflate compressed
|
|
950
|
+
const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
|
|
951
|
+
pixelData = await (0, tiff_deflate_js_1.deflateDecompress)(compressedData);
|
|
952
|
+
}
|
|
891
953
|
else {
|
|
892
954
|
// Uncompressed
|
|
893
955
|
pixelData = data.slice(stripOffset, stripOffset + stripByteCount);
|
|
@@ -966,5 +1028,142 @@ class TIFFFormat {
|
|
|
966
1028
|
"physicalHeight",
|
|
967
1029
|
];
|
|
968
1030
|
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Extract metadata from TIFF data without fully decoding the pixel data
|
|
1033
|
+
* This quickly parses IFD entries to extract metadata
|
|
1034
|
+
* @param data Raw TIFF data
|
|
1035
|
+
* @returns Extracted metadata or undefined
|
|
1036
|
+
*/
|
|
1037
|
+
extractMetadata(data) {
|
|
1038
|
+
if (!this.canDecode(data)) {
|
|
1039
|
+
return Promise.resolve(undefined);
|
|
1040
|
+
}
|
|
1041
|
+
// Determine byte order
|
|
1042
|
+
const isLittleEndian = data[0] === 0x49 && data[1] === 0x49;
|
|
1043
|
+
if (!isLittleEndian && !(data[0] === 0x4d && data[1] === 0x4d)) {
|
|
1044
|
+
return Promise.resolve(undefined);
|
|
1045
|
+
}
|
|
1046
|
+
// Read IFD offset
|
|
1047
|
+
const ifdOffset = this.readUint32(data, 4, isLittleEndian);
|
|
1048
|
+
if (ifdOffset >= data.length) {
|
|
1049
|
+
return Promise.resolve(undefined);
|
|
1050
|
+
}
|
|
1051
|
+
// Get dimensions for DPI calculation
|
|
1052
|
+
const width = this.getIFDValue(data, ifdOffset, 0x0100, isLittleEndian);
|
|
1053
|
+
const height = this.getIFDValue(data, ifdOffset, 0x0101, isLittleEndian);
|
|
1054
|
+
if (!width || !height) {
|
|
1055
|
+
return Promise.resolve(undefined);
|
|
1056
|
+
}
|
|
1057
|
+
// Extract metadata from TIFF tags
|
|
1058
|
+
const metadata = {
|
|
1059
|
+
format: "tiff",
|
|
1060
|
+
frameCount: 1,
|
|
1061
|
+
bitDepth: 8,
|
|
1062
|
+
};
|
|
1063
|
+
// Get compression type
|
|
1064
|
+
const compression = this.getIFDValue(data, ifdOffset, 0x0103, isLittleEndian);
|
|
1065
|
+
if (compression === 1) {
|
|
1066
|
+
metadata.compression = "none";
|
|
1067
|
+
}
|
|
1068
|
+
else if (compression === 5) {
|
|
1069
|
+
metadata.compression = "lzw";
|
|
1070
|
+
}
|
|
1071
|
+
else if (compression === 7 || compression === 6) {
|
|
1072
|
+
metadata.compression = "jpeg";
|
|
1073
|
+
}
|
|
1074
|
+
else if (compression === 8) {
|
|
1075
|
+
metadata.compression = "deflate";
|
|
1076
|
+
}
|
|
1077
|
+
else if (compression === 32773) {
|
|
1078
|
+
metadata.compression = "packbits";
|
|
1079
|
+
}
|
|
1080
|
+
else if (compression) {
|
|
1081
|
+
metadata.compression = `unknown-${compression}`;
|
|
1082
|
+
}
|
|
1083
|
+
// Get bits per sample
|
|
1084
|
+
const bitsPerSample = this.getIFDValue(data, ifdOffset, 0x0102, isLittleEndian);
|
|
1085
|
+
if (bitsPerSample) {
|
|
1086
|
+
metadata.bitDepth = bitsPerSample;
|
|
1087
|
+
}
|
|
1088
|
+
// Get photometric interpretation for color type
|
|
1089
|
+
const photometric = this.getIFDValue(data, ifdOffset, 0x0106, isLittleEndian);
|
|
1090
|
+
const samplesPerPixel = this.getIFDValue(data, ifdOffset, 0x0115, isLittleEndian);
|
|
1091
|
+
if (photometric === 0 || photometric === 1) {
|
|
1092
|
+
metadata.colorType = "grayscale";
|
|
1093
|
+
}
|
|
1094
|
+
else if (photometric === 2) {
|
|
1095
|
+
if (samplesPerPixel === 3) {
|
|
1096
|
+
metadata.colorType = "rgb";
|
|
1097
|
+
}
|
|
1098
|
+
else if (samplesPerPixel === 4) {
|
|
1099
|
+
metadata.colorType = "rgba";
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
else if (photometric === 3) {
|
|
1103
|
+
metadata.colorType = "indexed";
|
|
1104
|
+
}
|
|
1105
|
+
// Count IFDs (pages/frames) by following the chain
|
|
1106
|
+
let currentIfdOffset = ifdOffset;
|
|
1107
|
+
let frameCount = 0;
|
|
1108
|
+
while (currentIfdOffset > 0 && currentIfdOffset < data.length &&
|
|
1109
|
+
frameCount < 1000) {
|
|
1110
|
+
frameCount++;
|
|
1111
|
+
// Read number of entries in this IFD
|
|
1112
|
+
const numEntries = this.readUint16(data, currentIfdOffset, isLittleEndian);
|
|
1113
|
+
// Next IFD offset is after all entries (2 + numEntries * 12 bytes)
|
|
1114
|
+
const nextIfdOffsetPos = currentIfdOffset + 2 + (numEntries * 12);
|
|
1115
|
+
if (nextIfdOffsetPos + 4 > data.length)
|
|
1116
|
+
break;
|
|
1117
|
+
currentIfdOffset = this.readUint32(data, nextIfdOffsetPos, isLittleEndian);
|
|
1118
|
+
}
|
|
1119
|
+
metadata.frameCount = frameCount;
|
|
1120
|
+
// XResolution (0x011a) and YResolution (0x011b) for DPI
|
|
1121
|
+
const xResOffset = this.getIFDValue(data, ifdOffset, 0x011a, isLittleEndian);
|
|
1122
|
+
const yResOffset = this.getIFDValue(data, ifdOffset, 0x011b, isLittleEndian);
|
|
1123
|
+
if (xResOffset && xResOffset < data.length - 8) {
|
|
1124
|
+
const numerator = this.readUint32(data, xResOffset, isLittleEndian);
|
|
1125
|
+
const denominator = this.readUint32(data, xResOffset + 4, isLittleEndian);
|
|
1126
|
+
if (denominator > 0) {
|
|
1127
|
+
metadata.dpiX = Math.round(numerator / denominator);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
if (yResOffset && yResOffset < data.length - 8) {
|
|
1131
|
+
const numerator = this.readUint32(data, yResOffset, isLittleEndian);
|
|
1132
|
+
const denominator = this.readUint32(data, yResOffset + 4, isLittleEndian);
|
|
1133
|
+
if (denominator > 0) {
|
|
1134
|
+
metadata.dpiY = Math.round(numerator / denominator);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
// Calculate physical dimensions if DPI is available
|
|
1138
|
+
if (metadata.dpiX && metadata.dpiY) {
|
|
1139
|
+
metadata.physicalWidth = width / metadata.dpiX;
|
|
1140
|
+
metadata.physicalHeight = height / metadata.dpiY;
|
|
1141
|
+
}
|
|
1142
|
+
// ImageDescription (0x010e)
|
|
1143
|
+
const descOffset = this.getIFDValue(data, ifdOffset, 0x010e, isLittleEndian);
|
|
1144
|
+
if (descOffset && descOffset < data.length) {
|
|
1145
|
+
metadata.description = this.readString(data, descOffset);
|
|
1146
|
+
}
|
|
1147
|
+
// Artist (0x013b)
|
|
1148
|
+
const artistOffset = this.getIFDValue(data, ifdOffset, 0x013b, isLittleEndian);
|
|
1149
|
+
if (artistOffset && artistOffset < data.length) {
|
|
1150
|
+
metadata.author = this.readString(data, artistOffset);
|
|
1151
|
+
}
|
|
1152
|
+
// Copyright (0x8298)
|
|
1153
|
+
const copyrightOffset = this.getIFDValue(data, ifdOffset, 0x8298, isLittleEndian);
|
|
1154
|
+
if (copyrightOffset && copyrightOffset < data.length) {
|
|
1155
|
+
metadata.copyright = this.readString(data, copyrightOffset);
|
|
1156
|
+
}
|
|
1157
|
+
// DateTime (0x0132)
|
|
1158
|
+
const dateTimeOffset = this.getIFDValue(data, ifdOffset, 0x0132, isLittleEndian);
|
|
1159
|
+
if (dateTimeOffset && dateTimeOffset < data.length) {
|
|
1160
|
+
const dateStr = this.readString(data, dateTimeOffset);
|
|
1161
|
+
const match = dateStr.match(/^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/);
|
|
1162
|
+
if (match) {
|
|
1163
|
+
metadata.creationDate = new Date(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]), parseInt(match[4]), parseInt(match[5]), parseInt(match[6]));
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return Promise.resolve(Object.keys(metadata).length > 0 ? metadata : undefined);
|
|
1167
|
+
}
|
|
969
1168
|
}
|
|
970
1169
|
exports.TIFFFormat = TIFFFormat;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageData, ImageFormat, ImageMetadata,
|
|
1
|
+
import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, WebPEncoderOptions } from "../types.js";
|
|
2
2
|
/**
|
|
3
3
|
* WebP format handler
|
|
4
4
|
* Implements a basic WebP decoder and encoder
|
|
@@ -19,14 +19,14 @@ export declare class WebPFormat implements ImageFormat {
|
|
|
19
19
|
* @param data Raw WebP image data
|
|
20
20
|
* @returns Decoded image data with RGBA pixels
|
|
21
21
|
*/
|
|
22
|
-
decode(data: Uint8Array): Promise<ImageData>;
|
|
22
|
+
decode(data: Uint8Array, settings?: ImageDecoderOptions): Promise<ImageData>;
|
|
23
23
|
/**
|
|
24
24
|
* Encode RGBA image data to WebP format
|
|
25
25
|
* @param imageData Image data to encode
|
|
26
26
|
* @param options Optional WebP encoding options
|
|
27
27
|
* @returns Encoded WebP image bytes
|
|
28
28
|
*/
|
|
29
|
-
encode(imageData: ImageData, options?:
|
|
29
|
+
encode(imageData: ImageData, options?: WebPEncoderOptions): Promise<Uint8Array>;
|
|
30
30
|
private readUint24LE;
|
|
31
31
|
private decodeUsingRuntime;
|
|
32
32
|
private parseEXIF;
|
|
@@ -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
|