cross-image 0.2.4 → 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 +507 -333
- package/esm/mod.d.ts +4 -4
- package/esm/mod.js +2 -2
- package/esm/src/formats/apng.d.ts +5 -5
- package/esm/src/formats/apng.js +7 -9
- package/esm/src/formats/ascii.d.ts +3 -3
- package/esm/src/formats/ascii.js +1 -1
- package/esm/src/formats/avif.d.ts +3 -3
- package/esm/src/formats/avif.js +7 -7
- package/esm/src/formats/bmp.d.ts +3 -3
- package/esm/src/formats/bmp.js +2 -2
- package/esm/src/formats/dng.d.ts +1 -1
- package/esm/src/formats/dng.js +1 -1
- package/esm/src/formats/gif.d.ts +4 -4
- package/esm/src/formats/gif.js +14 -10
- package/esm/src/formats/heic.d.ts +3 -3
- package/esm/src/formats/heic.js +7 -7
- package/esm/src/formats/ico.d.ts +3 -3
- package/esm/src/formats/ico.js +4 -4
- package/esm/src/formats/jpeg.d.ts +3 -3
- package/esm/src/formats/jpeg.js +23 -11
- package/esm/src/formats/pam.d.ts +3 -3
- package/esm/src/formats/pam.js +2 -2
- package/esm/src/formats/pcx.d.ts +3 -3
- package/esm/src/formats/pcx.js +2 -2
- package/esm/src/formats/png.d.ts +3 -3
- package/esm/src/formats/png.js +2 -2
- package/esm/src/formats/png_base.js +2 -5
- package/esm/src/formats/ppm.d.ts +3 -3
- package/esm/src/formats/ppm.js +2 -2
- package/esm/src/formats/tiff.d.ts +7 -18
- package/esm/src/formats/tiff.js +86 -21
- package/esm/src/formats/webp.d.ts +3 -3
- package/esm/src/formats/webp.js +11 -8
- package/esm/src/image.d.ts +11 -3
- package/esm/src/image.js +37 -21
- package/esm/src/types.d.ts +56 -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 +1 -1
- package/script/mod.d.ts +4 -4
- package/script/mod.js +2 -2
- package/script/src/formats/apng.d.ts +5 -5
- package/script/src/formats/apng.js +7 -9
- package/script/src/formats/ascii.d.ts +3 -3
- package/script/src/formats/ascii.js +1 -1
- package/script/src/formats/avif.d.ts +3 -3
- package/script/src/formats/avif.js +7 -7
- package/script/src/formats/bmp.d.ts +3 -3
- package/script/src/formats/bmp.js +2 -2
- package/script/src/formats/dng.d.ts +1 -1
- package/script/src/formats/dng.js +1 -1
- package/script/src/formats/gif.d.ts +4 -4
- package/script/src/formats/gif.js +14 -10
- package/script/src/formats/heic.d.ts +3 -3
- package/script/src/formats/heic.js +7 -7
- package/script/src/formats/ico.d.ts +3 -3
- package/script/src/formats/ico.js +4 -4
- package/script/src/formats/jpeg.d.ts +3 -3
- package/script/src/formats/jpeg.js +23 -11
- package/script/src/formats/pam.d.ts +3 -3
- package/script/src/formats/pam.js +2 -2
- package/script/src/formats/pcx.d.ts +3 -3
- package/script/src/formats/pcx.js +2 -2
- package/script/src/formats/png.d.ts +3 -3
- package/script/src/formats/png.js +2 -2
- package/script/src/formats/png_base.js +2 -5
- package/script/src/formats/ppm.d.ts +3 -3
- package/script/src/formats/ppm.js +2 -2
- package/script/src/formats/tiff.d.ts +7 -18
- package/script/src/formats/tiff.js +86 -21
- package/script/src/formats/webp.d.ts +3 -3
- package/script/src/formats/webp.js +11 -8
- package/script/src/image.d.ts +11 -3
- package/script/src/image.js +36 -20
- package/script/src/types.d.ts +56 -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/pam.js
CHANGED
|
@@ -53,7 +53,7 @@ export class PAMFormat {
|
|
|
53
53
|
* @param data Raw PAM image data
|
|
54
54
|
* @returns Decoded image data with RGBA pixels
|
|
55
55
|
*/
|
|
56
|
-
decode(data) {
|
|
56
|
+
decode(data, _options) {
|
|
57
57
|
if (!this.canDecode(data)) {
|
|
58
58
|
throw new Error("Invalid PAM signature");
|
|
59
59
|
}
|
|
@@ -150,7 +150,7 @@ export class PAMFormat {
|
|
|
150
150
|
* @param imageData Image data to encode
|
|
151
151
|
* @returns Encoded PAM image bytes
|
|
152
152
|
*/
|
|
153
|
-
encode(imageData) {
|
|
153
|
+
encode(imageData, _options) {
|
|
154
154
|
const { width, height, data } = imageData;
|
|
155
155
|
// Validate input
|
|
156
156
|
if (data.length !== width * height * 4) {
|
package/esm/src/formats/pcx.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageData, ImageFormat, ImageMetadata } from "../types.js";
|
|
1
|
+
import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata } from "../types.js";
|
|
2
2
|
/**
|
|
3
3
|
* PCX format handler
|
|
4
4
|
* Implements PCX decoder and encoder
|
|
@@ -7,8 +7,8 @@ export declare class PCXFormat implements ImageFormat {
|
|
|
7
7
|
readonly name = "pcx";
|
|
8
8
|
readonly mimeType = "image/x-pcx";
|
|
9
9
|
canDecode(data: Uint8Array): boolean;
|
|
10
|
-
decode(data: Uint8Array): Promise<ImageData>;
|
|
11
|
-
encode(image: ImageData): Promise<Uint8Array>;
|
|
10
|
+
decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
|
|
11
|
+
encode(image: ImageData, _options?: unknown): Promise<Uint8Array>;
|
|
12
12
|
/**
|
|
13
13
|
* Get the list of metadata fields supported by PCX format
|
|
14
14
|
*/
|
package/esm/src/formats/pcx.js
CHANGED
|
@@ -28,7 +28,7 @@ export class PCXFormat {
|
|
|
28
28
|
data[1] === 5) &&
|
|
29
29
|
data[2] === 1;
|
|
30
30
|
}
|
|
31
|
-
decode(data) {
|
|
31
|
+
decode(data, _options) {
|
|
32
32
|
if (!this.canDecode(data)) {
|
|
33
33
|
return Promise.reject(new Error("Invalid PCX data"));
|
|
34
34
|
}
|
|
@@ -134,7 +134,7 @@ export class PCXFormat {
|
|
|
134
134
|
data: rgba,
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
|
-
encode(image) {
|
|
137
|
+
encode(image, _options) {
|
|
138
138
|
const width = image.width;
|
|
139
139
|
const height = image.height;
|
|
140
140
|
const data = image.data;
|
package/esm/src/formats/png.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageData, ImageFormat, ImageMetadata } from "../types.js";
|
|
1
|
+
import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata } from "../types.js";
|
|
2
2
|
import { PNGBase } from "./png_base.js";
|
|
3
3
|
/**
|
|
4
4
|
* PNG format handler
|
|
@@ -20,13 +20,13 @@ export declare class PNGFormat extends PNGBase implements ImageFormat {
|
|
|
20
20
|
* @param data Raw PNG image data
|
|
21
21
|
* @returns Decoded image data with RGBA pixels
|
|
22
22
|
*/
|
|
23
|
-
decode(data: Uint8Array): Promise<ImageData>;
|
|
23
|
+
decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
|
|
24
24
|
/**
|
|
25
25
|
* Encode RGBA image data to PNG format
|
|
26
26
|
* @param imageData Image data to encode
|
|
27
27
|
* @returns Encoded PNG image bytes
|
|
28
28
|
*/
|
|
29
|
-
encode(imageData: ImageData): Promise<Uint8Array>;
|
|
29
|
+
encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
|
|
30
30
|
/**
|
|
31
31
|
* Get the list of metadata fields supported by PNG format
|
|
32
32
|
* Delegates to PNGBase implementation
|
package/esm/src/formats/png.js
CHANGED
|
@@ -40,7 +40,7 @@ export class PNGFormat extends PNGBase {
|
|
|
40
40
|
* @param data Raw PNG image data
|
|
41
41
|
* @returns Decoded image data with RGBA pixels
|
|
42
42
|
*/
|
|
43
|
-
async decode(data) {
|
|
43
|
+
async decode(data, _options) {
|
|
44
44
|
if (!this.canDecode(data)) {
|
|
45
45
|
throw new Error("Invalid PNG signature");
|
|
46
46
|
}
|
|
@@ -112,7 +112,7 @@ export class PNGFormat extends PNGBase {
|
|
|
112
112
|
* @param imageData Image data to encode
|
|
113
113
|
* @returns Encoded PNG image bytes
|
|
114
114
|
*/
|
|
115
|
-
async encode(imageData) {
|
|
115
|
+
async encode(imageData, _options) {
|
|
116
116
|
const { width, height, data, metadata } = imageData;
|
|
117
117
|
// Prepare IHDR chunk
|
|
118
118
|
const ihdr = new Uint8Array(13);
|
|
@@ -105,9 +105,7 @@ export class PNGBase {
|
|
|
105
105
|
for (let x = 0; x < scanline.length; x++) {
|
|
106
106
|
const left = x >= bytesPerPixel ? scanline[x - bytesPerPixel] : 0;
|
|
107
107
|
const above = prevLine ? prevLine[x] : 0;
|
|
108
|
-
const upperLeft = (x >= bytesPerPixel && prevLine)
|
|
109
|
-
? prevLine[x - bytesPerPixel]
|
|
110
|
-
: 0;
|
|
108
|
+
const upperLeft = (x >= bytesPerPixel && prevLine) ? prevLine[x - bytesPerPixel] : 0;
|
|
111
109
|
switch (filterType) {
|
|
112
110
|
case 0: // None
|
|
113
111
|
break;
|
|
@@ -121,8 +119,7 @@ export class PNGBase {
|
|
|
121
119
|
scanline[x] = (scanline[x] + Math.floor((left + above) / 2)) & 0xff;
|
|
122
120
|
break;
|
|
123
121
|
case 4: // Paeth
|
|
124
|
-
scanline[x] =
|
|
125
|
-
(scanline[x] + this.paethPredictor(left, above, upperLeft)) & 0xff;
|
|
122
|
+
scanline[x] = (scanline[x] + this.paethPredictor(left, above, upperLeft)) & 0xff;
|
|
126
123
|
break;
|
|
127
124
|
}
|
|
128
125
|
}
|
package/esm/src/formats/ppm.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageData, ImageFormat, ImageMetadata } 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,14 +34,14 @@ 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
|
*/
|
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) {
|
|
@@ -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;
|
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);
|
|
@@ -1003,9 +1065,12 @@ export class TIFFFormat {
|
|
|
1003
1065
|
else if (compression === 5) {
|
|
1004
1066
|
metadata.compression = "lzw";
|
|
1005
1067
|
}
|
|
1006
|
-
else if (compression === 7) {
|
|
1068
|
+
else if (compression === 7 || compression === 6) {
|
|
1007
1069
|
metadata.compression = "jpeg";
|
|
1008
1070
|
}
|
|
1071
|
+
else if (compression === 8) {
|
|
1072
|
+
metadata.compression = "deflate";
|
|
1073
|
+
}
|
|
1009
1074
|
else if (compression === 32773) {
|
|
1010
1075
|
metadata.compression = "packbits";
|
|
1011
1076
|
}
|
|
@@ -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;
|
package/esm/src/formats/webp.js
CHANGED
|
@@ -42,7 +42,7 @@ export class WebPFormat {
|
|
|
42
42
|
* @param data Raw WebP image data
|
|
43
43
|
* @returns Decoded image data with RGBA pixels
|
|
44
44
|
*/
|
|
45
|
-
async decode(data) {
|
|
45
|
+
async decode(data, settings) {
|
|
46
46
|
if (!this.canDecode(data)) {
|
|
47
47
|
throw new Error("Invalid WebP signature");
|
|
48
48
|
}
|
|
@@ -108,7 +108,7 @@ export class WebPFormat {
|
|
|
108
108
|
validateImageDimensions(width, height);
|
|
109
109
|
// For a pure JS implementation, we'd need to implement full WebP decoding
|
|
110
110
|
// which is very complex. Instead, we'll use the browser/runtime's decoder.
|
|
111
|
-
const rgba = await this.decodeUsingRuntime(data, width, height);
|
|
111
|
+
const rgba = await this.decodeUsingRuntime(data, width, height, settings);
|
|
112
112
|
return {
|
|
113
113
|
width,
|
|
114
114
|
height,
|
|
@@ -171,9 +171,10 @@ export class WebPFormat {
|
|
|
171
171
|
readUint24LE(data, offset) {
|
|
172
172
|
return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16);
|
|
173
173
|
}
|
|
174
|
-
async decodeUsingRuntime(data, _width, _height) {
|
|
174
|
+
async decodeUsingRuntime(data, _width, _height, settings) {
|
|
175
175
|
// Try to use ImageDecoder API if available (Deno, modern browsers)
|
|
176
|
-
if (
|
|
176
|
+
if (settings?.runtimeDecoding !== "never" &&
|
|
177
|
+
typeof ImageDecoder !== "undefined") {
|
|
177
178
|
try {
|
|
178
179
|
const decoder = new ImageDecoder({ data, type: "image/webp" });
|
|
179
180
|
const result = await decoder.decode();
|
|
@@ -188,15 +189,17 @@ export class WebPFormat {
|
|
|
188
189
|
bitmap.close();
|
|
189
190
|
return new Uint8Array(imageData.data.buffer);
|
|
190
191
|
}
|
|
191
|
-
catch (
|
|
192
|
+
catch (_error) {
|
|
192
193
|
// ImageDecoder API failed, fall through to pure JS decoder
|
|
193
|
-
console.warn("WebP decoding with ImageDecoder failed, using pure JS decoder:", error);
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
|
-
// Fallback to pure JavaScript decoder (VP8L lossless only)
|
|
196
|
+
// Fallback to pure JavaScript decoder (VP8L lossless only) with tolerant mode
|
|
197
197
|
try {
|
|
198
198
|
const { WebPDecoder } = await import("../utils/webp_decoder.js");
|
|
199
|
-
const decoder = new WebPDecoder(data
|
|
199
|
+
const decoder = new WebPDecoder(data, {
|
|
200
|
+
tolerantDecoding: settings?.tolerantDecoding ?? true,
|
|
201
|
+
onWarning: settings?.onWarning,
|
|
202
|
+
});
|
|
200
203
|
const result = decoder.decode();
|
|
201
204
|
return result.data;
|
|
202
205
|
}
|
package/esm/src/image.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageFormat, ImageMetadata, MultiFrameImageData, ResizeOptions } from "./types.js";
|
|
1
|
+
import type { ImageDecoderOptions, ImageFormat, ImageMetadata, MultiFrameImageData, ResizeOptions } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Main Image class for reading, manipulating, and saving images
|
|
4
4
|
*/
|
|
@@ -78,7 +78,11 @@ export declare class Image {
|
|
|
78
78
|
* @param format Optional format hint (e.g., "png", "jpeg", "webp")
|
|
79
79
|
* @returns Image instance
|
|
80
80
|
*/
|
|
81
|
-
static decode(data: Uint8Array
|
|
81
|
+
static decode(data: Uint8Array): Promise<Image>;
|
|
82
|
+
static decode(data: Uint8Array, format: string): Promise<Image>;
|
|
83
|
+
static decode(data: Uint8Array, format: string, options?: ImageDecoderOptions): Promise<Image>;
|
|
84
|
+
static decode(data: Uint8Array, options?: ImageDecoderOptions): Promise<Image>;
|
|
85
|
+
private static decodeWithSettings;
|
|
82
86
|
/**
|
|
83
87
|
* Get supported metadata fields for a specific format
|
|
84
88
|
* @param format Format name (e.g., "jpeg", "png", "webp")
|
|
@@ -108,7 +112,11 @@ export declare class Image {
|
|
|
108
112
|
* @param format Optional format hint (e.g., "gif", "tiff")
|
|
109
113
|
* @returns MultiFrameImageData with all frames
|
|
110
114
|
*/
|
|
111
|
-
static decodeFrames(data: Uint8Array
|
|
115
|
+
static decodeFrames(data: Uint8Array): Promise<MultiFrameImageData>;
|
|
116
|
+
static decodeFrames(data: Uint8Array, format: string): Promise<MultiFrameImageData>;
|
|
117
|
+
static decodeFrames(data: Uint8Array, format: string, options?: ImageDecoderOptions): Promise<MultiFrameImageData>;
|
|
118
|
+
static decodeFrames(data: Uint8Array, options?: ImageDecoderOptions): Promise<MultiFrameImageData>;
|
|
119
|
+
private static decodeFramesWithSettings;
|
|
112
120
|
/**
|
|
113
121
|
* Read all frames from a multi-frame image (GIF animation, multi-page TIFF)
|
|
114
122
|
* @deprecated Use `decodeFrames()` instead. This method will be removed in a future version.
|
package/esm/src/image.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resizeBicubic, resizeBilinear, resizeNearest
|
|
1
|
+
import { resizeBicubic, resizeBilinear, resizeNearest } from "./utils/resize.js";
|
|
2
2
|
import { adjustBrightness, adjustContrast, adjustExposure, adjustHue, adjustSaturation, boxBlur, composite, crop, fillRect, flipHorizontal, flipVertical, gaussianBlur, grayscale, invert, medianFilter, rotate180, rotate270, rotate90, sepia, sharpen, } from "./utils/image_processing.js";
|
|
3
3
|
import { PNGFormat } from "./formats/png.js";
|
|
4
4
|
import { APNGFormat } from "./formats/apng.js";
|
|
@@ -158,26 +158,37 @@ export class Image {
|
|
|
158
158
|
static getFormats() {
|
|
159
159
|
return Image.formats;
|
|
160
160
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
161
|
+
static async decode(data, formatOrOptions, options) {
|
|
162
|
+
// Backward-compatible overloads:
|
|
163
|
+
// - decode(data)
|
|
164
|
+
// - decode(data, "jpeg")
|
|
165
|
+
// New:
|
|
166
|
+
// - decode(data, { tolerantDecoding, onWarning, runtimeDecoding })
|
|
167
|
+
// - decode(data, "jpeg", { ...options })
|
|
168
|
+
if (typeof formatOrOptions === "string") {
|
|
169
|
+
return await Image.decodeWithSettings(data, formatOrOptions, options);
|
|
170
|
+
}
|
|
171
|
+
return await Image.decodeWithSettings(data, undefined, formatOrOptions);
|
|
172
|
+
}
|
|
173
|
+
static async decodeWithSettings(data, format, settings) {
|
|
168
174
|
const image = new Image();
|
|
175
|
+
const normalizedSettings = {
|
|
176
|
+
tolerantDecoding: settings?.tolerantDecoding ?? true,
|
|
177
|
+
onWarning: settings?.onWarning,
|
|
178
|
+
runtimeDecoding: settings?.runtimeDecoding ?? "prefer",
|
|
179
|
+
};
|
|
169
180
|
// Try specified format first
|
|
170
181
|
if (format) {
|
|
171
182
|
const handler = Image.formats.find((f) => f.name === format);
|
|
172
183
|
if (handler && handler.canDecode(data)) {
|
|
173
|
-
image.imageData = await handler.decode(data);
|
|
184
|
+
image.imageData = await handler.decode(data, normalizedSettings);
|
|
174
185
|
return image;
|
|
175
186
|
}
|
|
176
187
|
}
|
|
177
188
|
// Auto-detect format
|
|
178
189
|
for (const handler of Image.formats) {
|
|
179
190
|
if (handler.canDecode(data)) {
|
|
180
|
-
image.imageData = await handler.decode(data);
|
|
191
|
+
image.imageData = await handler.decode(data, normalizedSettings);
|
|
181
192
|
return image;
|
|
182
193
|
}
|
|
183
194
|
}
|
|
@@ -227,26 +238,31 @@ export class Image {
|
|
|
227
238
|
* @returns Image instance
|
|
228
239
|
*/
|
|
229
240
|
static read(data, format) {
|
|
230
|
-
return Image.decode(data, format);
|
|
241
|
+
return format ? Image.decode(data, format) : Image.decode(data);
|
|
231
242
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
static async
|
|
243
|
+
static async decodeFrames(data, formatOrOptions, options) {
|
|
244
|
+
if (typeof formatOrOptions === "string") {
|
|
245
|
+
return await Image.decodeFramesWithSettings(data, formatOrOptions, options);
|
|
246
|
+
}
|
|
247
|
+
return await Image.decodeFramesWithSettings(data, undefined, formatOrOptions);
|
|
248
|
+
}
|
|
249
|
+
static async decodeFramesWithSettings(data, format, settings) {
|
|
250
|
+
const normalizedSettings = {
|
|
251
|
+
tolerantDecoding: settings?.tolerantDecoding ?? true,
|
|
252
|
+
onWarning: settings?.onWarning,
|
|
253
|
+
runtimeDecoding: settings?.runtimeDecoding ?? "prefer",
|
|
254
|
+
};
|
|
239
255
|
// Try specified format first
|
|
240
256
|
if (format) {
|
|
241
257
|
const handler = Image.formats.find((f) => f.name === format);
|
|
242
258
|
if (handler && handler.canDecode(data) && handler.decodeFrames) {
|
|
243
|
-
return await handler.decodeFrames(data);
|
|
259
|
+
return await handler.decodeFrames(data, normalizedSettings);
|
|
244
260
|
}
|
|
245
261
|
}
|
|
246
262
|
// Auto-detect format
|
|
247
263
|
for (const handler of Image.formats) {
|
|
248
264
|
if (handler.canDecode(data) && handler.decodeFrames) {
|
|
249
|
-
return await handler.decodeFrames(data);
|
|
265
|
+
return await handler.decodeFrames(data, normalizedSettings);
|
|
250
266
|
}
|
|
251
267
|
}
|
|
252
268
|
throw new Error("Unsupported or unrecognized multi-frame image format");
|
|
@@ -259,7 +275,7 @@ export class Image {
|
|
|
259
275
|
* @returns MultiFrameImageData with all frames
|
|
260
276
|
*/
|
|
261
277
|
static readFrames(data, format) {
|
|
262
|
-
return Image.decodeFrames(data, format);
|
|
278
|
+
return format ? Image.decodeFrames(data, format) : Image.decodeFrames(data);
|
|
263
279
|
}
|
|
264
280
|
/**
|
|
265
281
|
* Encode multi-frame image data to bytes in the specified format
|