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
package/esm/src/formats/ppm.d.ts
CHANGED
|
@@ -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
|
package/esm/src/formats/ppm.js
CHANGED
|
@@ -56,7 +56,7 @@ export class PPMFormat {
|
|
|
56
56
|
* @param data Raw PPM image data
|
|
57
57
|
* @returns Decoded image data with RGBA pixels
|
|
58
58
|
*/
|
|
59
|
-
decode(data) {
|
|
59
|
+
decode(data, _options) {
|
|
60
60
|
if (!this.canDecode(data)) {
|
|
61
61
|
throw new Error("Invalid PPM signature");
|
|
62
62
|
}
|
|
@@ -208,7 +208,7 @@ export class PPMFormat {
|
|
|
208
208
|
* @param imageData Image data to encode
|
|
209
209
|
* @returns Encoded PPM image bytes
|
|
210
210
|
*/
|
|
211
|
-
encode(imageData) {
|
|
211
|
+
encode(imageData, _options) {
|
|
212
212
|
const { width, height, data } = imageData;
|
|
213
213
|
// Validate input
|
|
214
214
|
if (data.length !== width * height * 4) {
|
|
@@ -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
|
}
|
|
@@ -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
|
package/esm/src/formats/tiff.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { TIFFLZWDecoder, TIFFLZWEncoder } from "../utils/tiff_lzw.js";
|
|
2
|
+
import { packBitsCompress, packBitsDecompress } from "../utils/tiff_packbits.js";
|
|
3
|
+
import { deflateCompress, deflateDecompress } from "../utils/tiff_deflate.js";
|
|
2
4
|
import { validateImageDimensions } from "../utils/security.js";
|
|
3
5
|
// Constants for unit conversions
|
|
4
6
|
const DEFAULT_DPI = 72;
|
|
5
7
|
/**
|
|
6
8
|
* TIFF format handler
|
|
7
|
-
* Implements pure-JS TIFF decoder for uncompressed and
|
|
8
|
-
* and encoder for uncompressed and
|
|
9
|
-
* for
|
|
9
|
+
* Implements pure-JS TIFF decoder for uncompressed, LZW, PackBits, and Deflate-compressed RGB/RGBA images
|
|
10
|
+
* and encoder for uncompressed, LZW, PackBits, and Deflate-compressed RGBA TIFFs. Falls back to ImageDecoder
|
|
11
|
+
* for JPEG-compressed TIFFs.
|
|
10
12
|
* Supports multi-page TIFF files.
|
|
11
13
|
*/
|
|
12
14
|
export class TIFFFormat {
|
|
@@ -52,7 +54,7 @@ export class TIFFFormat {
|
|
|
52
54
|
* @param data Raw TIFF image data
|
|
53
55
|
* @returns Decoded image data with RGBA pixels of first page
|
|
54
56
|
*/
|
|
55
|
-
async decode(data) {
|
|
57
|
+
async decode(data, _options) {
|
|
56
58
|
if (!this.canDecode(data)) {
|
|
57
59
|
throw new Error("Invalid TIFF signature");
|
|
58
60
|
}
|
|
@@ -128,7 +130,7 @@ export class TIFFFormat {
|
|
|
128
130
|
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
129
131
|
};
|
|
130
132
|
}
|
|
131
|
-
encode(imageData, options) {
|
|
133
|
+
async encode(imageData, options) {
|
|
132
134
|
const { width, height, data, metadata } = imageData;
|
|
133
135
|
const opts = options;
|
|
134
136
|
const compression = opts?.compression ?? "none";
|
|
@@ -172,6 +174,16 @@ export class TIFFFormat {
|
|
|
172
174
|
pixelData = encoder.compress(sourceData);
|
|
173
175
|
compressionCode = 5;
|
|
174
176
|
}
|
|
177
|
+
else if (compression === "packbits") {
|
|
178
|
+
// PackBits compress the pixel data
|
|
179
|
+
pixelData = packBitsCompress(sourceData);
|
|
180
|
+
compressionCode = 32773;
|
|
181
|
+
}
|
|
182
|
+
else if (compression === "deflate") {
|
|
183
|
+
// Deflate compress the pixel data
|
|
184
|
+
pixelData = await deflateCompress(sourceData);
|
|
185
|
+
compressionCode = 8;
|
|
186
|
+
}
|
|
175
187
|
else {
|
|
176
188
|
// Uncompressed
|
|
177
189
|
pixelData = sourceData;
|
|
@@ -329,7 +341,7 @@ export class TIFFFormat {
|
|
|
329
341
|
/**
|
|
330
342
|
* Decode all pages from a multi-page TIFF
|
|
331
343
|
*/
|
|
332
|
-
async decodeFrames(data) {
|
|
344
|
+
async decodeFrames(data, _options) {
|
|
333
345
|
if (!this.canDecode(data)) {
|
|
334
346
|
throw new Error("Invalid TIFF signature");
|
|
335
347
|
}
|
|
@@ -392,7 +404,7 @@ export class TIFFFormat {
|
|
|
392
404
|
/**
|
|
393
405
|
* Encode multi-page TIFF
|
|
394
406
|
*/
|
|
395
|
-
encodeFrames(imageData, options) {
|
|
407
|
+
async encodeFrames(imageData, options) {
|
|
396
408
|
const opts = options;
|
|
397
409
|
const compression = opts?.compression ?? "none";
|
|
398
410
|
if (imageData.frames.length === 0) {
|
|
@@ -417,6 +429,12 @@ export class TIFFFormat {
|
|
|
417
429
|
const encoder = new TIFFLZWEncoder();
|
|
418
430
|
pixelData = encoder.compress(frame.data);
|
|
419
431
|
}
|
|
432
|
+
else if (compression === "packbits") {
|
|
433
|
+
pixelData = packBitsCompress(frame.data);
|
|
434
|
+
}
|
|
435
|
+
else if (compression === "deflate") {
|
|
436
|
+
pixelData = await deflateCompress(frame.data);
|
|
437
|
+
}
|
|
420
438
|
else {
|
|
421
439
|
pixelData = frame.data;
|
|
422
440
|
}
|
|
@@ -453,7 +471,19 @@ export class TIFFFormat {
|
|
|
453
471
|
this.writeIFDEntry(result, 0x0102, 3, 4, dataOffset);
|
|
454
472
|
dataOffset += 8;
|
|
455
473
|
// Compression
|
|
456
|
-
|
|
474
|
+
let compressionCode;
|
|
475
|
+
if (compression === "lzw") {
|
|
476
|
+
compressionCode = 5;
|
|
477
|
+
}
|
|
478
|
+
else if (compression === "packbits") {
|
|
479
|
+
compressionCode = 32773;
|
|
480
|
+
}
|
|
481
|
+
else if (compression === "deflate") {
|
|
482
|
+
compressionCode = 8;
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
compressionCode = 1;
|
|
486
|
+
}
|
|
457
487
|
this.writeIFDEntry(result, 0x0103, 3, 1, compressionCode);
|
|
458
488
|
// PhotometricInterpretation
|
|
459
489
|
this.writeIFDEntry(result, 0x0106, 3, 1, 2);
|
|
@@ -464,9 +494,19 @@ export class TIFFFormat {
|
|
|
464
494
|
// RowsPerStrip
|
|
465
495
|
this.writeIFDEntry(result, 0x0116, 4, 1, frame.height);
|
|
466
496
|
// StripByteCounts
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
497
|
+
let pixelDataSize;
|
|
498
|
+
if (compression === "lzw") {
|
|
499
|
+
pixelDataSize = new TIFFLZWEncoder().compress(frame.data).length;
|
|
500
|
+
}
|
|
501
|
+
else if (compression === "packbits") {
|
|
502
|
+
pixelDataSize = packBitsCompress(frame.data).length;
|
|
503
|
+
}
|
|
504
|
+
else if (compression === "deflate") {
|
|
505
|
+
pixelDataSize = (await deflateCompress(frame.data)).length;
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
pixelDataSize = frame.data.length;
|
|
509
|
+
}
|
|
470
510
|
this.writeIFDEntry(result, 0x0117, 4, 1, pixelDataSize);
|
|
471
511
|
// XResolution
|
|
472
512
|
const xResOffset = dataOffset;
|
|
@@ -569,7 +609,7 @@ export class TIFFFormat {
|
|
|
569
609
|
const isLittleEndian = data[0] === 0x49;
|
|
570
610
|
// Try pure JavaScript decoder first
|
|
571
611
|
try {
|
|
572
|
-
const pureJSResult = this.decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian);
|
|
612
|
+
const pureJSResult = await this.decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian);
|
|
573
613
|
if (pureJSResult) {
|
|
574
614
|
return pureJSResult;
|
|
575
615
|
}
|
|
@@ -693,7 +733,7 @@ export class TIFFFormat {
|
|
|
693
733
|
async decodeUsingRuntime(data, width, height) {
|
|
694
734
|
// Try pure JavaScript decoder first for uncompressed TIFFs
|
|
695
735
|
try {
|
|
696
|
-
const pureJSResult = this.decodePureJS(data, width, height);
|
|
736
|
+
const pureJSResult = await this.decodePureJS(data, width, height);
|
|
697
737
|
if (pureJSResult) {
|
|
698
738
|
return pureJSResult;
|
|
699
739
|
}
|
|
@@ -731,10 +771,10 @@ export class TIFFFormat {
|
|
|
731
771
|
return new TextDecoder().decode(data.slice(offset, endIndex));
|
|
732
772
|
}
|
|
733
773
|
/**
|
|
734
|
-
* Pure JavaScript TIFF decoder for uncompressed and
|
|
774
|
+
* Pure JavaScript TIFF decoder for uncompressed, LZW, PackBits, and Deflate-compressed RGB/RGBA images
|
|
735
775
|
* Returns null if the TIFF uses unsupported features
|
|
736
776
|
*/
|
|
737
|
-
decodePureJS(data, width, height) {
|
|
777
|
+
async decodePureJS(data, width, height) {
|
|
738
778
|
// Validate minimum TIFF header size
|
|
739
779
|
if (data.length < 8) {
|
|
740
780
|
return null;
|
|
@@ -745,8 +785,9 @@ export class TIFFFormat {
|
|
|
745
785
|
const ifdOffset = this.readUint32(data, 4, isLittleEndian);
|
|
746
786
|
// Check compression
|
|
747
787
|
const compression = this.getIFDValue(data, ifdOffset, 0x0103, isLittleEndian);
|
|
748
|
-
if (compression !== 1 && compression !== 5
|
|
749
|
-
|
|
788
|
+
if (compression !== 1 && compression !== 5 && compression !== 8 &&
|
|
789
|
+
compression !== 32773) {
|
|
790
|
+
// Support: 1 = uncompressed, 5 = LZW, 8 = Deflate, 32773 = PackBits
|
|
750
791
|
return null;
|
|
751
792
|
}
|
|
752
793
|
// Check photometric interpretation
|
|
@@ -788,6 +829,16 @@ export class TIFFFormat {
|
|
|
788
829
|
const decoder = new TIFFLZWDecoder(compressedData);
|
|
789
830
|
pixelData = decoder.decompress();
|
|
790
831
|
}
|
|
832
|
+
else if (compression === 32773) {
|
|
833
|
+
// PackBits compressed
|
|
834
|
+
const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
|
|
835
|
+
pixelData = packBitsDecompress(compressedData);
|
|
836
|
+
}
|
|
837
|
+
else if (compression === 8) {
|
|
838
|
+
// Deflate compressed
|
|
839
|
+
const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
|
|
840
|
+
pixelData = await deflateDecompress(compressedData);
|
|
841
|
+
}
|
|
791
842
|
else {
|
|
792
843
|
// Uncompressed
|
|
793
844
|
pixelData = data.slice(stripOffset, stripOffset + stripByteCount);
|
|
@@ -839,11 +890,12 @@ export class TIFFFormat {
|
|
|
839
890
|
* Pure JavaScript TIFF decoder for a specific IFD
|
|
840
891
|
* Returns null if the TIFF uses unsupported features
|
|
841
892
|
*/
|
|
842
|
-
decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian) {
|
|
893
|
+
async decodePureJSFromIFD(data, ifdOffset, width, height, isLittleEndian) {
|
|
843
894
|
// Check compression
|
|
844
895
|
const compression = this.getIFDValue(data, ifdOffset, 0x0103, isLittleEndian);
|
|
845
|
-
if (compression !== 1 && compression !== 5
|
|
846
|
-
|
|
896
|
+
if (compression !== 1 && compression !== 5 && compression !== 8 &&
|
|
897
|
+
compression !== 32773) {
|
|
898
|
+
// Support: 1 = uncompressed, 5 = LZW, 8 = Deflate, 32773 = PackBits
|
|
847
899
|
return null;
|
|
848
900
|
}
|
|
849
901
|
// Check photometric interpretation
|
|
@@ -885,6 +937,16 @@ export class TIFFFormat {
|
|
|
885
937
|
const decoder = new TIFFLZWDecoder(compressedData);
|
|
886
938
|
pixelData = decoder.decompress();
|
|
887
939
|
}
|
|
940
|
+
else if (compression === 32773) {
|
|
941
|
+
// PackBits compressed
|
|
942
|
+
const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
|
|
943
|
+
pixelData = packBitsDecompress(compressedData);
|
|
944
|
+
}
|
|
945
|
+
else if (compression === 8) {
|
|
946
|
+
// Deflate compressed
|
|
947
|
+
const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
|
|
948
|
+
pixelData = await deflateDecompress(compressedData);
|
|
949
|
+
}
|
|
888
950
|
else {
|
|
889
951
|
// Uncompressed
|
|
890
952
|
pixelData = data.slice(stripOffset, stripOffset + stripByteCount);
|
|
@@ -963,4 +1025,141 @@ export class TIFFFormat {
|
|
|
963
1025
|
"physicalHeight",
|
|
964
1026
|
];
|
|
965
1027
|
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Extract metadata from TIFF data without fully decoding the pixel data
|
|
1030
|
+
* This quickly parses IFD entries to extract metadata
|
|
1031
|
+
* @param data Raw TIFF data
|
|
1032
|
+
* @returns Extracted metadata or undefined
|
|
1033
|
+
*/
|
|
1034
|
+
extractMetadata(data) {
|
|
1035
|
+
if (!this.canDecode(data)) {
|
|
1036
|
+
return Promise.resolve(undefined);
|
|
1037
|
+
}
|
|
1038
|
+
// Determine byte order
|
|
1039
|
+
const isLittleEndian = data[0] === 0x49 && data[1] === 0x49;
|
|
1040
|
+
if (!isLittleEndian && !(data[0] === 0x4d && data[1] === 0x4d)) {
|
|
1041
|
+
return Promise.resolve(undefined);
|
|
1042
|
+
}
|
|
1043
|
+
// Read IFD offset
|
|
1044
|
+
const ifdOffset = this.readUint32(data, 4, isLittleEndian);
|
|
1045
|
+
if (ifdOffset >= data.length) {
|
|
1046
|
+
return Promise.resolve(undefined);
|
|
1047
|
+
}
|
|
1048
|
+
// Get dimensions for DPI calculation
|
|
1049
|
+
const width = this.getIFDValue(data, ifdOffset, 0x0100, isLittleEndian);
|
|
1050
|
+
const height = this.getIFDValue(data, ifdOffset, 0x0101, isLittleEndian);
|
|
1051
|
+
if (!width || !height) {
|
|
1052
|
+
return Promise.resolve(undefined);
|
|
1053
|
+
}
|
|
1054
|
+
// Extract metadata from TIFF tags
|
|
1055
|
+
const metadata = {
|
|
1056
|
+
format: "tiff",
|
|
1057
|
+
frameCount: 1,
|
|
1058
|
+
bitDepth: 8,
|
|
1059
|
+
};
|
|
1060
|
+
// Get compression type
|
|
1061
|
+
const compression = this.getIFDValue(data, ifdOffset, 0x0103, isLittleEndian);
|
|
1062
|
+
if (compression === 1) {
|
|
1063
|
+
metadata.compression = "none";
|
|
1064
|
+
}
|
|
1065
|
+
else if (compression === 5) {
|
|
1066
|
+
metadata.compression = "lzw";
|
|
1067
|
+
}
|
|
1068
|
+
else if (compression === 7 || compression === 6) {
|
|
1069
|
+
metadata.compression = "jpeg";
|
|
1070
|
+
}
|
|
1071
|
+
else if (compression === 8) {
|
|
1072
|
+
metadata.compression = "deflate";
|
|
1073
|
+
}
|
|
1074
|
+
else if (compression === 32773) {
|
|
1075
|
+
metadata.compression = "packbits";
|
|
1076
|
+
}
|
|
1077
|
+
else if (compression) {
|
|
1078
|
+
metadata.compression = `unknown-${compression}`;
|
|
1079
|
+
}
|
|
1080
|
+
// Get bits per sample
|
|
1081
|
+
const bitsPerSample = this.getIFDValue(data, ifdOffset, 0x0102, isLittleEndian);
|
|
1082
|
+
if (bitsPerSample) {
|
|
1083
|
+
metadata.bitDepth = bitsPerSample;
|
|
1084
|
+
}
|
|
1085
|
+
// Get photometric interpretation for color type
|
|
1086
|
+
const photometric = this.getIFDValue(data, ifdOffset, 0x0106, isLittleEndian);
|
|
1087
|
+
const samplesPerPixel = this.getIFDValue(data, ifdOffset, 0x0115, isLittleEndian);
|
|
1088
|
+
if (photometric === 0 || photometric === 1) {
|
|
1089
|
+
metadata.colorType = "grayscale";
|
|
1090
|
+
}
|
|
1091
|
+
else if (photometric === 2) {
|
|
1092
|
+
if (samplesPerPixel === 3) {
|
|
1093
|
+
metadata.colorType = "rgb";
|
|
1094
|
+
}
|
|
1095
|
+
else if (samplesPerPixel === 4) {
|
|
1096
|
+
metadata.colorType = "rgba";
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
else if (photometric === 3) {
|
|
1100
|
+
metadata.colorType = "indexed";
|
|
1101
|
+
}
|
|
1102
|
+
// Count IFDs (pages/frames) by following the chain
|
|
1103
|
+
let currentIfdOffset = ifdOffset;
|
|
1104
|
+
let frameCount = 0;
|
|
1105
|
+
while (currentIfdOffset > 0 && currentIfdOffset < data.length &&
|
|
1106
|
+
frameCount < 1000) {
|
|
1107
|
+
frameCount++;
|
|
1108
|
+
// Read number of entries in this IFD
|
|
1109
|
+
const numEntries = this.readUint16(data, currentIfdOffset, isLittleEndian);
|
|
1110
|
+
// Next IFD offset is after all entries (2 + numEntries * 12 bytes)
|
|
1111
|
+
const nextIfdOffsetPos = currentIfdOffset + 2 + (numEntries * 12);
|
|
1112
|
+
if (nextIfdOffsetPos + 4 > data.length)
|
|
1113
|
+
break;
|
|
1114
|
+
currentIfdOffset = this.readUint32(data, nextIfdOffsetPos, isLittleEndian);
|
|
1115
|
+
}
|
|
1116
|
+
metadata.frameCount = frameCount;
|
|
1117
|
+
// XResolution (0x011a) and YResolution (0x011b) for DPI
|
|
1118
|
+
const xResOffset = this.getIFDValue(data, ifdOffset, 0x011a, isLittleEndian);
|
|
1119
|
+
const yResOffset = this.getIFDValue(data, ifdOffset, 0x011b, isLittleEndian);
|
|
1120
|
+
if (xResOffset && xResOffset < data.length - 8) {
|
|
1121
|
+
const numerator = this.readUint32(data, xResOffset, isLittleEndian);
|
|
1122
|
+
const denominator = this.readUint32(data, xResOffset + 4, isLittleEndian);
|
|
1123
|
+
if (denominator > 0) {
|
|
1124
|
+
metadata.dpiX = Math.round(numerator / denominator);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
if (yResOffset && yResOffset < data.length - 8) {
|
|
1128
|
+
const numerator = this.readUint32(data, yResOffset, isLittleEndian);
|
|
1129
|
+
const denominator = this.readUint32(data, yResOffset + 4, isLittleEndian);
|
|
1130
|
+
if (denominator > 0) {
|
|
1131
|
+
metadata.dpiY = Math.round(numerator / denominator);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
// Calculate physical dimensions if DPI is available
|
|
1135
|
+
if (metadata.dpiX && metadata.dpiY) {
|
|
1136
|
+
metadata.physicalWidth = width / metadata.dpiX;
|
|
1137
|
+
metadata.physicalHeight = height / metadata.dpiY;
|
|
1138
|
+
}
|
|
1139
|
+
// ImageDescription (0x010e)
|
|
1140
|
+
const descOffset = this.getIFDValue(data, ifdOffset, 0x010e, isLittleEndian);
|
|
1141
|
+
if (descOffset && descOffset < data.length) {
|
|
1142
|
+
metadata.description = this.readString(data, descOffset);
|
|
1143
|
+
}
|
|
1144
|
+
// Artist (0x013b)
|
|
1145
|
+
const artistOffset = this.getIFDValue(data, ifdOffset, 0x013b, isLittleEndian);
|
|
1146
|
+
if (artistOffset && artistOffset < data.length) {
|
|
1147
|
+
metadata.author = this.readString(data, artistOffset);
|
|
1148
|
+
}
|
|
1149
|
+
// Copyright (0x8298)
|
|
1150
|
+
const copyrightOffset = this.getIFDValue(data, ifdOffset, 0x8298, isLittleEndian);
|
|
1151
|
+
if (copyrightOffset && copyrightOffset < data.length) {
|
|
1152
|
+
metadata.copyright = this.readString(data, copyrightOffset);
|
|
1153
|
+
}
|
|
1154
|
+
// DateTime (0x0132)
|
|
1155
|
+
const dateTimeOffset = this.getIFDValue(data, ifdOffset, 0x0132, isLittleEndian);
|
|
1156
|
+
if (dateTimeOffset && dateTimeOffset < data.length) {
|
|
1157
|
+
const dateStr = this.readString(data, dateTimeOffset);
|
|
1158
|
+
const match = dateStr.match(/^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/);
|
|
1159
|
+
if (match) {
|
|
1160
|
+
metadata.creationDate = new Date(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]), parseInt(match[4]), parseInt(match[5]), parseInt(match[6]));
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
return Promise.resolve(Object.keys(metadata).length > 0 ? metadata : undefined);
|
|
1164
|
+
}
|
|
966
1165
|
}
|
|
@@ -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
|