cross-image 0.2.4 → 0.4.1
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 +615 -333
- package/esm/mod.d.ts +6 -4
- package/esm/mod.js +4 -2
- package/esm/src/formats/apng.d.ts +7 -5
- package/esm/src/formats/apng.js +15 -10
- 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 +17 -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 +5 -5
- package/esm/src/formats/gif.js +17 -13
- package/esm/src/formats/heic.d.ts +3 -3
- package/esm/src/formats/heic.js +17 -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 +4 -3
- package/esm/src/formats/png.js +9 -3
- package/esm/src/formats/png_base.d.ts +42 -1
- package/esm/src/formats/png_base.js +200 -10
- 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 +162 -27
- package/esm/src/formats/webp.d.ts +3 -3
- package/esm/src/formats/webp.js +11 -8
- package/esm/src/image.d.ts +26 -3
- package/esm/src/image.js +66 -22
- package/esm/src/types.d.ts +122 -4
- package/esm/src/utils/base64.d.ts +32 -0
- package/esm/src/utils/base64.js +173 -0
- package/esm/src/utils/gif_decoder.d.ts +4 -1
- package/esm/src/utils/gif_decoder.js +91 -65
- package/esm/src/utils/gif_encoder.d.ts +3 -1
- package/esm/src/utils/gif_encoder.js +4 -2
- package/esm/src/utils/image_processing.d.ts +31 -0
- package/esm/src/utils/image_processing.js +232 -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 +6 -4
- package/script/mod.js +13 -3
- package/script/src/formats/apng.d.ts +7 -5
- package/script/src/formats/apng.js +15 -10
- 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 +17 -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 +5 -5
- package/script/src/formats/gif.js +17 -13
- package/script/src/formats/heic.d.ts +3 -3
- package/script/src/formats/heic.js +17 -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 +4 -3
- package/script/src/formats/png.js +9 -3
- package/script/src/formats/png_base.d.ts +42 -1
- package/script/src/formats/png_base.js +200 -10
- 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 +162 -27
- package/script/src/formats/webp.d.ts +3 -3
- package/script/src/formats/webp.js +11 -8
- package/script/src/image.d.ts +26 -3
- package/script/src/image.js +64 -20
- package/script/src/types.d.ts +122 -4
- package/script/src/utils/base64.d.ts +32 -0
- package/script/src/utils/base64.js +179 -0
- package/script/src/utils/gif_decoder.d.ts +4 -1
- package/script/src/utils/gif_decoder.js +91 -65
- package/script/src/utils/gif_encoder.d.ts +3 -1
- package/script/src/utils/gif_encoder.js +4 -2
- package/script/src/utils/image_processing.d.ts +31 -0
- package/script/src/utils/image_processing.js +236 -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
|
@@ -52,10 +52,13 @@ class AVIFFormat {
|
|
|
52
52
|
* @param data Raw AVIF image data
|
|
53
53
|
* @returns Decoded image data with RGBA pixels
|
|
54
54
|
*/
|
|
55
|
-
async decode(data) {
|
|
55
|
+
async decode(data, settings) {
|
|
56
56
|
if (!this.canDecode(data)) {
|
|
57
57
|
throw new Error("Invalid AVIF signature");
|
|
58
58
|
}
|
|
59
|
+
if (settings?.runtimeDecoding === "never") {
|
|
60
|
+
throw new Error("AVIF decoding requires runtime APIs; set runtimeDecoding to 'prefer'");
|
|
61
|
+
}
|
|
59
62
|
// Extract metadata before decoding pixels
|
|
60
63
|
const metadata = await this.extractMetadata(data);
|
|
61
64
|
// Use runtime decoder
|
|
@@ -79,8 +82,9 @@ class AVIFFormat {
|
|
|
79
82
|
* @param imageData Image data to encode
|
|
80
83
|
* @returns Encoded AVIF image bytes
|
|
81
84
|
*/
|
|
82
|
-
async encode(imageData) {
|
|
83
|
-
const { width, height, data, metadata } = imageData;
|
|
85
|
+
async encode(imageData, options) {
|
|
86
|
+
const { width, height, data, metadata: _metadata } = imageData;
|
|
87
|
+
const requestedQuality = options?.quality;
|
|
84
88
|
// Try to use runtime encoding if available
|
|
85
89
|
if (typeof OffscreenCanvas !== "undefined") {
|
|
86
90
|
try {
|
|
@@ -91,20 +95,26 @@ class AVIFFormat {
|
|
|
91
95
|
const imgDataData = new Uint8ClampedArray(data);
|
|
92
96
|
imgData.data.set(imgDataData);
|
|
93
97
|
ctx.putImageData(imgData, 0, 0);
|
|
98
|
+
const quality = requestedQuality === undefined
|
|
99
|
+
? undefined
|
|
100
|
+
: (requestedQuality <= 1
|
|
101
|
+
? Math.max(0, Math.min(1, requestedQuality))
|
|
102
|
+
: Math.max(1, Math.min(100, requestedQuality)) / 100);
|
|
94
103
|
// Try to encode as AVIF
|
|
95
104
|
const blob = await canvas.convertToBlob({
|
|
96
105
|
type: "image/avif",
|
|
106
|
+
...(quality === undefined ? {} : { quality }),
|
|
97
107
|
});
|
|
108
|
+
if (blob.type !== "image/avif") {
|
|
109
|
+
throw new Error(`Runtime did not encode AVIF (got '${blob.type || "(empty)"}')`);
|
|
110
|
+
}
|
|
98
111
|
const arrayBuffer = await blob.arrayBuffer();
|
|
99
112
|
const encoded = new Uint8Array(arrayBuffer);
|
|
100
113
|
// Note: Metadata injection for AVIF is complex and would require
|
|
101
114
|
// parsing and modifying the ISOBMFF container structure
|
|
102
115
|
// For now, we rely on the runtime encoder to preserve metadata
|
|
103
116
|
// if it was passed through the canvas
|
|
104
|
-
|
|
105
|
-
// Future enhancement: inject metadata into AVIF container
|
|
106
|
-
console.warn("AVIF metadata injection not yet implemented, metadata may be lost");
|
|
107
|
-
}
|
|
117
|
+
// Future enhancement: inject metadata into AVIF container
|
|
108
118
|
return encoded;
|
|
109
119
|
}
|
|
110
120
|
}
|
|
@@ -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
|
* BMP format handler
|
|
4
4
|
* Implements a pure JavaScript BMP decoder and encoder
|
|
@@ -19,13 +19,13 @@ export declare class BMPFormat implements ImageFormat {
|
|
|
19
19
|
* @param data Raw BMP image data
|
|
20
20
|
* @returns Decoded image data with RGBA pixels
|
|
21
21
|
*/
|
|
22
|
-
decode(data: Uint8Array): Promise<ImageData>;
|
|
22
|
+
decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
|
|
23
23
|
/**
|
|
24
24
|
* Encode RGBA image data to BMP format
|
|
25
25
|
* @param imageData Image data to encode
|
|
26
26
|
* @returns Encoded BMP image bytes
|
|
27
27
|
*/
|
|
28
|
-
encode(imageData: ImageData): Promise<Uint8Array>;
|
|
28
|
+
encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
|
|
29
29
|
/**
|
|
30
30
|
* Get the list of metadata fields supported by BMP format
|
|
31
31
|
*/
|
|
@@ -41,7 +41,7 @@ class BMPFormat {
|
|
|
41
41
|
* @param data Raw BMP image data
|
|
42
42
|
* @returns Decoded image data with RGBA pixels
|
|
43
43
|
*/
|
|
44
|
-
decode(data) {
|
|
44
|
+
decode(data, _options) {
|
|
45
45
|
if (!this.canDecode(data)) {
|
|
46
46
|
throw new Error("Invalid BMP signature");
|
|
47
47
|
}
|
|
@@ -118,7 +118,7 @@ class BMPFormat {
|
|
|
118
118
|
* @param imageData Image data to encode
|
|
119
119
|
* @returns Encoded BMP image bytes
|
|
120
120
|
*/
|
|
121
|
-
encode(imageData) {
|
|
121
|
+
encode(imageData, _options) {
|
|
122
122
|
const { width, height, data, metadata } = imageData;
|
|
123
123
|
// Calculate sizes
|
|
124
124
|
const bytesPerPixel = 4; // We'll encode as 32-bit RGBA
|
|
@@ -22,7 +22,7 @@ export declare class DNGFormat extends TIFFFormat {
|
|
|
22
22
|
* @param imageData Image data to encode
|
|
23
23
|
* @returns Encoded DNG image bytes
|
|
24
24
|
*/
|
|
25
|
-
encode(imageData: ImageData): Promise<Uint8Array>;
|
|
25
|
+
encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
|
|
26
26
|
/**
|
|
27
27
|
* Get the list of metadata fields supported by DNG format
|
|
28
28
|
* DNG is based on TIFF, so it supports all TIFF metadata fields
|
|
@@ -77,7 +77,7 @@ class DNGFormat extends tiff_js_1.TIFFFormat {
|
|
|
77
77
|
* @param imageData Image data to encode
|
|
78
78
|
* @returns Encoded DNG image bytes
|
|
79
79
|
*/
|
|
80
|
-
encode(imageData) {
|
|
80
|
+
encode(imageData, _options) {
|
|
81
81
|
const { width, height, data } = imageData;
|
|
82
82
|
// We'll create a Linear DNG (demosaiced RGB)
|
|
83
83
|
// This is very similar to a standard TIFF but with specific tags.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageData, ImageFormat, ImageMetadata, MultiFrameImageData } from "../types.js";
|
|
1
|
+
import type { GIFEncoderOptions, ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, MultiFrameImageData } from "../types.js";
|
|
2
2
|
/**
|
|
3
3
|
* GIF format handler
|
|
4
4
|
* Now includes pure-JS implementation with custom LZW compression/decompression
|
|
@@ -32,22 +32,22 @@ export declare class GIFFormat implements ImageFormat {
|
|
|
32
32
|
* @param data Raw GIF image data
|
|
33
33
|
* @returns Decoded image data with RGBA pixels of first frame
|
|
34
34
|
*/
|
|
35
|
-
decode(data: Uint8Array): Promise<ImageData>;
|
|
35
|
+
decode(data: Uint8Array, settings?: ImageDecoderOptions): Promise<ImageData>;
|
|
36
36
|
private extractGIFMetadata;
|
|
37
37
|
/**
|
|
38
38
|
* Encode RGBA image data to GIF format (single frame)
|
|
39
39
|
* @param imageData Image data to encode
|
|
40
40
|
* @returns Encoded GIF image bytes
|
|
41
41
|
*/
|
|
42
|
-
encode(imageData: ImageData): Promise<Uint8Array>;
|
|
42
|
+
encode(imageData: ImageData, options?: GIFEncoderOptions): Promise<Uint8Array>;
|
|
43
43
|
/**
|
|
44
44
|
* Decode all frames from an animated GIF
|
|
45
45
|
*/
|
|
46
|
-
decodeFrames(data: Uint8Array): Promise<MultiFrameImageData>;
|
|
46
|
+
decodeFrames(data: Uint8Array, settings?: ImageDecoderOptions): Promise<MultiFrameImageData>;
|
|
47
47
|
/**
|
|
48
48
|
* Encode multi-frame image data to animated GIF
|
|
49
49
|
*/
|
|
50
|
-
encodeFrames(imageData: MultiFrameImageData,
|
|
50
|
+
encodeFrames(imageData: MultiFrameImageData, options?: GIFEncoderOptions): Promise<Uint8Array>;
|
|
51
51
|
private mapDisposalMethod;
|
|
52
52
|
private decodeUsingRuntime;
|
|
53
53
|
private readDataSubBlocks;
|
|
@@ -59,13 +59,16 @@ class GIFFormat {
|
|
|
59
59
|
* @param data Raw GIF image data
|
|
60
60
|
* @returns Decoded image data with RGBA pixels of first frame
|
|
61
61
|
*/
|
|
62
|
-
async decode(data) {
|
|
62
|
+
async decode(data, settings) {
|
|
63
63
|
if (!this.canDecode(data)) {
|
|
64
64
|
throw new Error("Invalid GIF signature");
|
|
65
65
|
}
|
|
66
|
-
// Try pure-JS decoder first
|
|
66
|
+
// Try pure-JS decoder first with tolerant decoding enabled by default
|
|
67
67
|
try {
|
|
68
|
-
const decoder = new gif_decoder_js_1.GIFDecoder(data
|
|
68
|
+
const decoder = new gif_decoder_js_1.GIFDecoder(data, {
|
|
69
|
+
tolerantDecoding: settings?.tolerantDecoding ?? true,
|
|
70
|
+
onWarning: settings?.onWarning,
|
|
71
|
+
});
|
|
69
72
|
const result = decoder.decode();
|
|
70
73
|
// Validate dimensions for security (prevent integer overflow and heap exhaustion)
|
|
71
74
|
(0, security_js_1.validateImageDimensions)(result.width, result.height);
|
|
@@ -78,9 +81,8 @@ class GIFFormat {
|
|
|
78
81
|
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
79
82
|
};
|
|
80
83
|
}
|
|
81
|
-
catch (
|
|
84
|
+
catch (_error) {
|
|
82
85
|
// Fall back to runtime decoder if pure-JS fails
|
|
83
|
-
console.warn("Pure-JS GIF decoder failed, falling back to runtime:", error);
|
|
84
86
|
let pos = 6; // Skip "GIF89a" or "GIF87a"
|
|
85
87
|
const width = (0, byte_utils_js_1.readUint16LE)(data, pos);
|
|
86
88
|
pos += 2;
|
|
@@ -154,12 +156,12 @@ class GIFFormat {
|
|
|
154
156
|
* @param imageData Image data to encode
|
|
155
157
|
* @returns Encoded GIF image bytes
|
|
156
158
|
*/
|
|
157
|
-
async encode(imageData) {
|
|
159
|
+
async encode(imageData, options) {
|
|
158
160
|
const { width, height, data, metadata } = imageData;
|
|
159
161
|
// Try pure-JS encoder first
|
|
160
162
|
try {
|
|
161
163
|
const encoder = new gif_encoder_js_1.GIFEncoder(width, height, data);
|
|
162
|
-
const encoded = encoder.encode();
|
|
164
|
+
const encoded = encoder.encode(options);
|
|
163
165
|
// Inject metadata if present
|
|
164
166
|
if (metadata && Object.keys(metadata).length > 0) {
|
|
165
167
|
const injected = this.injectMetadata(encoded, metadata);
|
|
@@ -167,9 +169,8 @@ class GIFFormat {
|
|
|
167
169
|
}
|
|
168
170
|
return encoded;
|
|
169
171
|
}
|
|
170
|
-
catch (
|
|
172
|
+
catch (_error) {
|
|
171
173
|
// Fall back to runtime encoding if pure-JS fails
|
|
172
|
-
console.warn("Pure-JS GIF encoder failed, falling back to runtime:", error);
|
|
173
174
|
if (typeof OffscreenCanvas !== "undefined") {
|
|
174
175
|
try {
|
|
175
176
|
const canvas = new OffscreenCanvas(width, height);
|
|
@@ -202,12 +203,15 @@ class GIFFormat {
|
|
|
202
203
|
/**
|
|
203
204
|
* Decode all frames from an animated GIF
|
|
204
205
|
*/
|
|
205
|
-
decodeFrames(data) {
|
|
206
|
+
decodeFrames(data, settings) {
|
|
206
207
|
if (!this.canDecode(data)) {
|
|
207
208
|
throw new Error("Invalid GIF signature");
|
|
208
209
|
}
|
|
209
210
|
try {
|
|
210
|
-
const decoder = new gif_decoder_js_1.GIFDecoder(data
|
|
211
|
+
const decoder = new gif_decoder_js_1.GIFDecoder(data, {
|
|
212
|
+
tolerantDecoding: settings?.tolerantDecoding ?? true,
|
|
213
|
+
onWarning: settings?.onWarning,
|
|
214
|
+
});
|
|
211
215
|
const result = decoder.decodeAllFrames();
|
|
212
216
|
// Extract metadata from comment extensions
|
|
213
217
|
const metadata = this.extractGIFMetadata(data);
|
|
@@ -236,7 +240,7 @@ class GIFFormat {
|
|
|
236
240
|
/**
|
|
237
241
|
* Encode multi-frame image data to animated GIF
|
|
238
242
|
*/
|
|
239
|
-
encodeFrames(imageData,
|
|
243
|
+
encodeFrames(imageData, options) {
|
|
240
244
|
if (imageData.frames.length === 0) {
|
|
241
245
|
throw new Error("No frames to encode");
|
|
242
246
|
}
|
|
@@ -246,7 +250,7 @@ class GIFFormat {
|
|
|
246
250
|
const delay = frame.frameMetadata?.delay ?? 100;
|
|
247
251
|
encoder.addFrame(frame.data, delay);
|
|
248
252
|
}
|
|
249
|
-
return Promise.resolve(encoder.encode());
|
|
253
|
+
return Promise.resolve(encoder.encode(options));
|
|
250
254
|
}
|
|
251
255
|
mapDisposalMethod(disposal) {
|
|
252
256
|
switch (disposal) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageData, ImageFormat, ImageMetadata } from "../types.js";
|
|
1
|
+
import type { HEICEncoderOptions, ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata } from "../types.js";
|
|
2
2
|
/**
|
|
3
3
|
* HEIC format handler
|
|
4
4
|
* Supports HEIC/HEIF images using runtime APIs (ImageDecoder/OffscreenCanvas)
|
|
@@ -21,7 +21,7 @@ export declare class HEICFormat implements ImageFormat {
|
|
|
21
21
|
* @param data Raw HEIC image data
|
|
22
22
|
* @returns Decoded image data with RGBA pixels
|
|
23
23
|
*/
|
|
24
|
-
decode(data: Uint8Array): Promise<ImageData>;
|
|
24
|
+
decode(data: Uint8Array, settings?: ImageDecoderOptions): Promise<ImageData>;
|
|
25
25
|
/**
|
|
26
26
|
* Encode RGBA image data to HEIC format
|
|
27
27
|
* Uses runtime APIs (OffscreenCanvas) for encoding
|
|
@@ -32,7 +32,7 @@ export declare class HEICFormat implements ImageFormat {
|
|
|
32
32
|
* @param imageData Image data to encode
|
|
33
33
|
* @returns Encoded HEIC image bytes
|
|
34
34
|
*/
|
|
35
|
-
encode(imageData: ImageData): Promise<Uint8Array>;
|
|
35
|
+
encode(imageData: ImageData, options?: HEICEncoderOptions): Promise<Uint8Array>;
|
|
36
36
|
/**
|
|
37
37
|
* Decode using runtime APIs
|
|
38
38
|
* @param data Raw HEIC data
|
|
@@ -53,10 +53,13 @@ class HEICFormat {
|
|
|
53
53
|
* @param data Raw HEIC image data
|
|
54
54
|
* @returns Decoded image data with RGBA pixels
|
|
55
55
|
*/
|
|
56
|
-
async decode(data) {
|
|
56
|
+
async decode(data, settings) {
|
|
57
57
|
if (!this.canDecode(data)) {
|
|
58
58
|
throw new Error("Invalid HEIC signature");
|
|
59
59
|
}
|
|
60
|
+
if (settings?.runtimeDecoding === "never") {
|
|
61
|
+
throw new Error("HEIC decoding requires runtime APIs; set runtimeDecoding to 'prefer'");
|
|
62
|
+
}
|
|
60
63
|
// Extract metadata before decoding pixels
|
|
61
64
|
const metadata = await this.extractMetadata(data);
|
|
62
65
|
// Use runtime decoder
|
|
@@ -80,8 +83,9 @@ class HEICFormat {
|
|
|
80
83
|
* @param imageData Image data to encode
|
|
81
84
|
* @returns Encoded HEIC image bytes
|
|
82
85
|
*/
|
|
83
|
-
async encode(imageData) {
|
|
84
|
-
const { width, height, data, metadata } = imageData;
|
|
86
|
+
async encode(imageData, options) {
|
|
87
|
+
const { width, height, data, metadata: _metadata } = imageData;
|
|
88
|
+
const requestedQuality = options?.quality;
|
|
85
89
|
// Try to use runtime encoding if available
|
|
86
90
|
if (typeof OffscreenCanvas !== "undefined") {
|
|
87
91
|
try {
|
|
@@ -92,20 +96,26 @@ class HEICFormat {
|
|
|
92
96
|
const imgDataData = new Uint8ClampedArray(data);
|
|
93
97
|
imgData.data.set(imgDataData);
|
|
94
98
|
ctx.putImageData(imgData, 0, 0);
|
|
99
|
+
const quality = requestedQuality === undefined
|
|
100
|
+
? undefined
|
|
101
|
+
: (requestedQuality <= 1
|
|
102
|
+
? Math.max(0, Math.min(1, requestedQuality))
|
|
103
|
+
: Math.max(1, Math.min(100, requestedQuality)) / 100);
|
|
95
104
|
// Try to encode as HEIC
|
|
96
105
|
const blob = await canvas.convertToBlob({
|
|
97
106
|
type: "image/heic",
|
|
107
|
+
...(quality === undefined ? {} : { quality }),
|
|
98
108
|
});
|
|
109
|
+
if (blob.type !== "image/heic") {
|
|
110
|
+
throw new Error(`Runtime did not encode HEIC (got '${blob.type || "(empty)"}')`);
|
|
111
|
+
}
|
|
99
112
|
const arrayBuffer = await blob.arrayBuffer();
|
|
100
113
|
const encoded = new Uint8Array(arrayBuffer);
|
|
101
114
|
// Note: Metadata injection for HEIC is complex and would require
|
|
102
115
|
// parsing and modifying the ISOBMFF container structure
|
|
103
116
|
// For now, we rely on the runtime encoder to preserve metadata
|
|
104
117
|
// if it was passed through the canvas
|
|
105
|
-
|
|
106
|
-
// Future enhancement: inject metadata into HEIC container
|
|
107
|
-
console.warn("HEIC metadata injection not yet implemented, metadata may be lost");
|
|
108
|
-
}
|
|
118
|
+
// Future enhancement: inject metadata into HEIC container
|
|
109
119
|
return encoded;
|
|
110
120
|
}
|
|
111
121
|
}
|
|
@@ -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
|
* ICO format handler
|
|
4
4
|
* Implements a pure JavaScript ICO (Windows Icon) decoder and encoder
|
|
@@ -24,7 +24,7 @@ export declare class ICOFormat implements ImageFormat {
|
|
|
24
24
|
* @param data Raw ICO image data
|
|
25
25
|
* @returns Decoded image data with RGBA pixels
|
|
26
26
|
*/
|
|
27
|
-
decode(data: Uint8Array): Promise<ImageData>;
|
|
27
|
+
decode(data: Uint8Array, options?: ImageDecoderOptions): Promise<ImageData>;
|
|
28
28
|
/**
|
|
29
29
|
* Decode a DIB (Device Independent Bitmap) format
|
|
30
30
|
* This is a BMP without the 14-byte file header
|
|
@@ -36,7 +36,7 @@ export declare class ICOFormat implements ImageFormat {
|
|
|
36
36
|
* @param imageData Image data to encode
|
|
37
37
|
* @returns Encoded ICO image bytes
|
|
38
38
|
*/
|
|
39
|
-
encode(imageData: ImageData): Promise<Uint8Array>;
|
|
39
|
+
encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
|
|
40
40
|
/**
|
|
41
41
|
* Get the list of metadata fields supported by ICO format
|
|
42
42
|
*/
|
|
@@ -52,7 +52,7 @@ class ICOFormat {
|
|
|
52
52
|
* @param data Raw ICO image data
|
|
53
53
|
* @returns Decoded image data with RGBA pixels
|
|
54
54
|
*/
|
|
55
|
-
async decode(data) {
|
|
55
|
+
async decode(data, options) {
|
|
56
56
|
if (!this.canDecode(data)) {
|
|
57
57
|
throw new Error("Invalid ICO signature");
|
|
58
58
|
}
|
|
@@ -103,7 +103,7 @@ class ICOFormat {
|
|
|
103
103
|
imageData[2] === 0x4e &&
|
|
104
104
|
imageData[3] === 0x47) {
|
|
105
105
|
// It's a PNG, decode it
|
|
106
|
-
return await this.pngFormat.decode(imageData);
|
|
106
|
+
return await this.pngFormat.decode(imageData, options);
|
|
107
107
|
}
|
|
108
108
|
// Otherwise, it's a BMP without the file header (DIB format)
|
|
109
109
|
return this.decodeDIB(imageData);
|
|
@@ -185,10 +185,10 @@ class ICOFormat {
|
|
|
185
185
|
* @param imageData Image data to encode
|
|
186
186
|
* @returns Encoded ICO image bytes
|
|
187
187
|
*/
|
|
188
|
-
async encode(imageData) {
|
|
188
|
+
async encode(imageData, _options) {
|
|
189
189
|
const { width, height } = imageData;
|
|
190
190
|
// Encode the image as PNG
|
|
191
|
-
const pngData = await this.pngFormat.encode(imageData);
|
|
191
|
+
const pngData = await this.pngFormat.encode(imageData, undefined);
|
|
192
192
|
// Create ICO file structure
|
|
193
193
|
// ICONDIR (6 bytes) + ICONDIRENTRY (16 bytes) + PNG data
|
|
194
194
|
const icoSize = 6 + 16 + pngData.length;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageData, ImageFormat, ImageMetadata } from "../types.js";
|
|
1
|
+
import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, JPEGEncoderOptions } from "../types.js";
|
|
2
2
|
/**
|
|
3
3
|
* JPEG format handler
|
|
4
4
|
* Implements a basic JPEG decoder and encoder
|
|
@@ -19,13 +19,13 @@ export declare class JPEGFormat implements ImageFormat {
|
|
|
19
19
|
* @param data Raw JPEG 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 JPEG format
|
|
25
25
|
* @param imageData Image data to encode
|
|
26
26
|
* @returns Encoded JPEG image bytes
|
|
27
27
|
*/
|
|
28
|
-
encode(imageData: ImageData): Promise<Uint8Array>;
|
|
28
|
+
encode(imageData: ImageData, options?: JPEGEncoderOptions): Promise<Uint8Array>;
|
|
29
29
|
private injectMetadata;
|
|
30
30
|
private decodeUsingRuntime;
|
|
31
31
|
private parseJFIF;
|
|
@@ -73,7 +73,7 @@ class JPEGFormat {
|
|
|
73
73
|
* @param data Raw JPEG image data
|
|
74
74
|
* @returns Decoded image data with RGBA pixels
|
|
75
75
|
*/
|
|
76
|
-
async decode(data) {
|
|
76
|
+
async decode(data, settings) {
|
|
77
77
|
if (!this.canDecode(data)) {
|
|
78
78
|
throw new Error("Invalid JPEG signature");
|
|
79
79
|
}
|
|
@@ -131,7 +131,7 @@ class JPEGFormat {
|
|
|
131
131
|
(0, security_js_1.validateImageDimensions)(width, height);
|
|
132
132
|
// For a pure JS implementation, we'd need to implement full JPEG decoding
|
|
133
133
|
// which is very complex. Instead, we'll use the browser/runtime's decoder.
|
|
134
|
-
const rgba = await this.decodeUsingRuntime(data, width, height);
|
|
134
|
+
const rgba = await this.decodeUsingRuntime(data, width, height, settings);
|
|
135
135
|
return {
|
|
136
136
|
width,
|
|
137
137
|
height,
|
|
@@ -144,10 +144,13 @@ class JPEGFormat {
|
|
|
144
144
|
* @param imageData Image data to encode
|
|
145
145
|
* @returns Encoded JPEG image bytes
|
|
146
146
|
*/
|
|
147
|
-
async encode(imageData) {
|
|
147
|
+
async encode(imageData, options) {
|
|
148
148
|
const { width, height, data, metadata } = imageData;
|
|
149
|
+
const requestedQuality = options?.quality;
|
|
150
|
+
const requestedProgressive = options?.progressive;
|
|
149
151
|
// Try to use runtime encoding if available (better quality)
|
|
150
|
-
|
|
152
|
+
// Note: progressive output is only supported by the pure-JS encoder.
|
|
153
|
+
if (!requestedProgressive && typeof OffscreenCanvas !== "undefined") {
|
|
151
154
|
try {
|
|
152
155
|
const canvas = new OffscreenCanvas(width, height);
|
|
153
156
|
const ctx = canvas.getContext("2d");
|
|
@@ -156,9 +159,12 @@ class JPEGFormat {
|
|
|
156
159
|
const imgDataData = new Uint8ClampedArray(data);
|
|
157
160
|
imgData.data.set(imgDataData);
|
|
158
161
|
ctx.putImageData(imgData, 0, 0);
|
|
162
|
+
const quality = requestedQuality === undefined
|
|
163
|
+
? 0.9
|
|
164
|
+
: Math.max(1, Math.min(100, requestedQuality)) / 100;
|
|
159
165
|
const blob = await canvas.convertToBlob({
|
|
160
166
|
type: "image/jpeg",
|
|
161
|
-
quality
|
|
167
|
+
quality,
|
|
162
168
|
});
|
|
163
169
|
const arrayBuffer = await blob.arrayBuffer();
|
|
164
170
|
const encoded = new Uint8Array(arrayBuffer);
|
|
@@ -177,7 +183,10 @@ class JPEGFormat {
|
|
|
177
183
|
const { JPEGEncoder } = await Promise.resolve().then(() => __importStar(require("../utils/jpeg_encoder.js")));
|
|
178
184
|
const dpiX = metadata?.dpiX ?? 72;
|
|
179
185
|
const dpiY = metadata?.dpiY ?? 72;
|
|
180
|
-
const encoder = new JPEGEncoder(
|
|
186
|
+
const encoder = new JPEGEncoder({
|
|
187
|
+
quality: requestedQuality,
|
|
188
|
+
progressive: requestedProgressive,
|
|
189
|
+
});
|
|
181
190
|
const encoded = encoder.encode(width, height, data, dpiX, dpiY);
|
|
182
191
|
// Add EXIF metadata if present
|
|
183
192
|
if (metadata && Object.keys(metadata).length > 0) {
|
|
@@ -221,9 +230,10 @@ class JPEGFormat {
|
|
|
221
230
|
result.set(encoded.slice(pos), pos + app1.length);
|
|
222
231
|
return result;
|
|
223
232
|
}
|
|
224
|
-
async decodeUsingRuntime(data, _width, _height) {
|
|
233
|
+
async decodeUsingRuntime(data, _width, _height, settings) {
|
|
225
234
|
// Try to use ImageDecoder API if available (Deno, modern browsers)
|
|
226
|
-
if (
|
|
235
|
+
if (settings?.runtimeDecoding !== "never" &&
|
|
236
|
+
typeof ImageDecoder !== "undefined") {
|
|
227
237
|
try {
|
|
228
238
|
const decoder = new ImageDecoder({ data, type: "image/jpeg" });
|
|
229
239
|
const result = await decoder.decode();
|
|
@@ -238,15 +248,17 @@ class JPEGFormat {
|
|
|
238
248
|
bitmap.close();
|
|
239
249
|
return new Uint8Array(imageData.data.buffer);
|
|
240
250
|
}
|
|
241
|
-
catch (
|
|
251
|
+
catch (_error) {
|
|
242
252
|
// ImageDecoder API failed, fall through to pure JS decoder
|
|
243
|
-
console.warn("JPEG decoding with ImageDecoder failed, using pure JS decoder:", error);
|
|
244
253
|
}
|
|
245
254
|
}
|
|
246
255
|
// Fallback to pure JavaScript decoder
|
|
247
256
|
try {
|
|
248
257
|
const { JPEGDecoder } = await Promise.resolve().then(() => __importStar(require("../utils/jpeg_decoder.js")));
|
|
249
|
-
const decoder = new JPEGDecoder(data
|
|
258
|
+
const decoder = new JPEGDecoder(data, {
|
|
259
|
+
tolerantDecoding: settings?.tolerantDecoding ?? true,
|
|
260
|
+
onWarning: settings?.onWarning,
|
|
261
|
+
});
|
|
250
262
|
return decoder.decode();
|
|
251
263
|
}
|
|
252
264
|
catch (error) {
|
|
@@ -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
|
* PAM format handler
|
|
4
4
|
* Implements the Netpbm PAM (Portable Arbitrary Map) format.
|
|
@@ -32,13 +32,13 @@ export declare class PAMFormat implements ImageFormat {
|
|
|
32
32
|
* @param data Raw PAM image data
|
|
33
33
|
* @returns Decoded image data with RGBA pixels
|
|
34
34
|
*/
|
|
35
|
-
decode(data: Uint8Array): Promise<ImageData>;
|
|
35
|
+
decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
|
|
36
36
|
/**
|
|
37
37
|
* Encode RGBA image data to PAM format
|
|
38
38
|
* @param imageData Image data to encode
|
|
39
39
|
* @returns Encoded PAM image bytes
|
|
40
40
|
*/
|
|
41
|
-
encode(imageData: ImageData): Promise<Uint8Array>;
|
|
41
|
+
encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
|
|
42
42
|
/**
|
|
43
43
|
* Get the list of metadata fields supported by PAM format
|
|
44
44
|
*/
|
|
@@ -56,7 +56,7 @@ class PAMFormat {
|
|
|
56
56
|
* @param data Raw PAM 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 PAM signature");
|
|
62
62
|
}
|
|
@@ -153,7 +153,7 @@ class PAMFormat {
|
|
|
153
153
|
* @param imageData Image data to encode
|
|
154
154
|
* @returns Encoded PAM image bytes
|
|
155
155
|
*/
|
|
156
|
-
encode(imageData) {
|
|
156
|
+
encode(imageData, _options) {
|
|
157
157
|
const { width, height, data } = imageData;
|
|
158
158
|
// Validate input
|
|
159
159
|
if (data.length !== width * height * 4) {
|
|
@@ -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
|
*/
|
|
@@ -31,7 +31,7 @@ class PCXFormat {
|
|
|
31
31
|
data[1] === 5) &&
|
|
32
32
|
data[2] === 1;
|
|
33
33
|
}
|
|
34
|
-
decode(data) {
|
|
34
|
+
decode(data, _options) {
|
|
35
35
|
if (!this.canDecode(data)) {
|
|
36
36
|
return Promise.reject(new Error("Invalid PCX data"));
|
|
37
37
|
}
|
|
@@ -137,7 +137,7 @@ class PCXFormat {
|
|
|
137
137
|
data: rgba,
|
|
138
138
|
});
|
|
139
139
|
}
|
|
140
|
-
encode(image) {
|
|
140
|
+
encode(image, _options) {
|
|
141
141
|
const width = image.width;
|
|
142
142
|
const height = image.height;
|
|
143
143
|
const data = image.data;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageData, ImageFormat, ImageMetadata } from "../types.js";
|
|
1
|
+
import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, PNGEncoderOptions } from "../types.js";
|
|
2
2
|
import { PNGBase } from "./png_base.js";
|
|
3
3
|
/**
|
|
4
4
|
* PNG format handler
|
|
@@ -20,13 +20,14 @@ 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
|
+
* @param options Encoding options (compressionLevel 0-9, default 6)
|
|
27
28
|
* @returns Encoded PNG image bytes
|
|
28
29
|
*/
|
|
29
|
-
encode(imageData: ImageData): Promise<Uint8Array>;
|
|
30
|
+
encode(imageData: ImageData, options?: PNGEncoderOptions): Promise<Uint8Array>;
|
|
30
31
|
/**
|
|
31
32
|
* Get the list of metadata fields supported by PNG format
|
|
32
33
|
* Delegates to PNGBase implementation
|
|
@@ -43,7 +43,7 @@ class PNGFormat extends png_base_js_1.PNGBase {
|
|
|
43
43
|
* @param data Raw PNG image data
|
|
44
44
|
* @returns Decoded image data with RGBA pixels
|
|
45
45
|
*/
|
|
46
|
-
async decode(data) {
|
|
46
|
+
async decode(data, _options) {
|
|
47
47
|
if (!this.canDecode(data)) {
|
|
48
48
|
throw new Error("Invalid PNG signature");
|
|
49
49
|
}
|
|
@@ -113,10 +113,16 @@ class PNGFormat extends png_base_js_1.PNGBase {
|
|
|
113
113
|
/**
|
|
114
114
|
* Encode RGBA image data to PNG format
|
|
115
115
|
* @param imageData Image data to encode
|
|
116
|
+
* @param options Encoding options (compressionLevel 0-9, default 6)
|
|
116
117
|
* @returns Encoded PNG image bytes
|
|
117
118
|
*/
|
|
118
|
-
async encode(imageData) {
|
|
119
|
+
async encode(imageData, options) {
|
|
119
120
|
const { width, height, data, metadata } = imageData;
|
|
121
|
+
const compressionLevel = options?.compressionLevel ?? 6;
|
|
122
|
+
// Validate compression level
|
|
123
|
+
if (compressionLevel < 0 || compressionLevel > 9) {
|
|
124
|
+
throw new Error("Compression level must be between 0 and 9");
|
|
125
|
+
}
|
|
120
126
|
// Prepare IHDR chunk
|
|
121
127
|
const ihdr = new Uint8Array(13);
|
|
122
128
|
this.writeUint32(ihdr, 0, width);
|
|
@@ -127,7 +133,7 @@ class PNGFormat extends png_base_js_1.PNGBase {
|
|
|
127
133
|
ihdr[11] = 0; // filter method
|
|
128
134
|
ihdr[12] = 0; // interlace method
|
|
129
135
|
// Filter and compress image data
|
|
130
|
-
const filtered = this.filterData(data, width, height);
|
|
136
|
+
const filtered = this.filterData(data, width, height, compressionLevel);
|
|
131
137
|
const compressed = await this.deflate(filtered);
|
|
132
138
|
// Build PNG
|
|
133
139
|
const chunks = [];
|