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.
Files changed (117) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +615 -333
  3. package/esm/mod.d.ts +6 -4
  4. package/esm/mod.js +4 -2
  5. package/esm/src/formats/apng.d.ts +7 -5
  6. package/esm/src/formats/apng.js +15 -10
  7. package/esm/src/formats/ascii.d.ts +3 -3
  8. package/esm/src/formats/ascii.js +1 -1
  9. package/esm/src/formats/avif.d.ts +3 -3
  10. package/esm/src/formats/avif.js +17 -7
  11. package/esm/src/formats/bmp.d.ts +3 -3
  12. package/esm/src/formats/bmp.js +2 -2
  13. package/esm/src/formats/dng.d.ts +1 -1
  14. package/esm/src/formats/dng.js +1 -1
  15. package/esm/src/formats/gif.d.ts +5 -5
  16. package/esm/src/formats/gif.js +17 -13
  17. package/esm/src/formats/heic.d.ts +3 -3
  18. package/esm/src/formats/heic.js +17 -7
  19. package/esm/src/formats/ico.d.ts +3 -3
  20. package/esm/src/formats/ico.js +4 -4
  21. package/esm/src/formats/jpeg.d.ts +3 -3
  22. package/esm/src/formats/jpeg.js +23 -11
  23. package/esm/src/formats/pam.d.ts +3 -3
  24. package/esm/src/formats/pam.js +2 -2
  25. package/esm/src/formats/pcx.d.ts +3 -3
  26. package/esm/src/formats/pcx.js +2 -2
  27. package/esm/src/formats/png.d.ts +4 -3
  28. package/esm/src/formats/png.js +9 -3
  29. package/esm/src/formats/png_base.d.ts +42 -1
  30. package/esm/src/formats/png_base.js +200 -10
  31. package/esm/src/formats/ppm.d.ts +3 -3
  32. package/esm/src/formats/ppm.js +2 -2
  33. package/esm/src/formats/tiff.d.ts +7 -18
  34. package/esm/src/formats/tiff.js +162 -27
  35. package/esm/src/formats/webp.d.ts +3 -3
  36. package/esm/src/formats/webp.js +11 -8
  37. package/esm/src/image.d.ts +26 -3
  38. package/esm/src/image.js +66 -22
  39. package/esm/src/types.d.ts +122 -4
  40. package/esm/src/utils/base64.d.ts +32 -0
  41. package/esm/src/utils/base64.js +173 -0
  42. package/esm/src/utils/gif_decoder.d.ts +4 -1
  43. package/esm/src/utils/gif_decoder.js +91 -65
  44. package/esm/src/utils/gif_encoder.d.ts +3 -1
  45. package/esm/src/utils/gif_encoder.js +4 -2
  46. package/esm/src/utils/image_processing.d.ts +31 -0
  47. package/esm/src/utils/image_processing.js +232 -70
  48. package/esm/src/utils/jpeg_decoder.d.ts +17 -4
  49. package/esm/src/utils/jpeg_decoder.js +448 -83
  50. package/esm/src/utils/jpeg_encoder.d.ts +15 -1
  51. package/esm/src/utils/jpeg_encoder.js +263 -24
  52. package/esm/src/utils/resize.js +51 -20
  53. package/esm/src/utils/tiff_deflate.d.ts +18 -0
  54. package/esm/src/utils/tiff_deflate.js +27 -0
  55. package/esm/src/utils/tiff_packbits.d.ts +24 -0
  56. package/esm/src/utils/tiff_packbits.js +90 -0
  57. package/esm/src/utils/webp_decoder.d.ts +3 -1
  58. package/esm/src/utils/webp_decoder.js +144 -63
  59. package/esm/src/utils/webp_encoder.js +5 -11
  60. package/package.json +1 -1
  61. package/script/mod.d.ts +6 -4
  62. package/script/mod.js +13 -3
  63. package/script/src/formats/apng.d.ts +7 -5
  64. package/script/src/formats/apng.js +15 -10
  65. package/script/src/formats/ascii.d.ts +3 -3
  66. package/script/src/formats/ascii.js +1 -1
  67. package/script/src/formats/avif.d.ts +3 -3
  68. package/script/src/formats/avif.js +17 -7
  69. package/script/src/formats/bmp.d.ts +3 -3
  70. package/script/src/formats/bmp.js +2 -2
  71. package/script/src/formats/dng.d.ts +1 -1
  72. package/script/src/formats/dng.js +1 -1
  73. package/script/src/formats/gif.d.ts +5 -5
  74. package/script/src/formats/gif.js +17 -13
  75. package/script/src/formats/heic.d.ts +3 -3
  76. package/script/src/formats/heic.js +17 -7
  77. package/script/src/formats/ico.d.ts +3 -3
  78. package/script/src/formats/ico.js +4 -4
  79. package/script/src/formats/jpeg.d.ts +3 -3
  80. package/script/src/formats/jpeg.js +23 -11
  81. package/script/src/formats/pam.d.ts +3 -3
  82. package/script/src/formats/pam.js +2 -2
  83. package/script/src/formats/pcx.d.ts +3 -3
  84. package/script/src/formats/pcx.js +2 -2
  85. package/script/src/formats/png.d.ts +4 -3
  86. package/script/src/formats/png.js +9 -3
  87. package/script/src/formats/png_base.d.ts +42 -1
  88. package/script/src/formats/png_base.js +200 -10
  89. package/script/src/formats/ppm.d.ts +3 -3
  90. package/script/src/formats/ppm.js +2 -2
  91. package/script/src/formats/tiff.d.ts +7 -18
  92. package/script/src/formats/tiff.js +162 -27
  93. package/script/src/formats/webp.d.ts +3 -3
  94. package/script/src/formats/webp.js +11 -8
  95. package/script/src/image.d.ts +26 -3
  96. package/script/src/image.js +64 -20
  97. package/script/src/types.d.ts +122 -4
  98. package/script/src/utils/base64.d.ts +32 -0
  99. package/script/src/utils/base64.js +179 -0
  100. package/script/src/utils/gif_decoder.d.ts +4 -1
  101. package/script/src/utils/gif_decoder.js +91 -65
  102. package/script/src/utils/gif_encoder.d.ts +3 -1
  103. package/script/src/utils/gif_encoder.js +4 -2
  104. package/script/src/utils/image_processing.d.ts +31 -0
  105. package/script/src/utils/image_processing.js +236 -70
  106. package/script/src/utils/jpeg_decoder.d.ts +17 -4
  107. package/script/src/utils/jpeg_decoder.js +448 -83
  108. package/script/src/utils/jpeg_encoder.d.ts +15 -1
  109. package/script/src/utils/jpeg_encoder.js +263 -24
  110. package/script/src/utils/resize.js +51 -20
  111. package/script/src/utils/tiff_deflate.d.ts +18 -0
  112. package/script/src/utils/tiff_deflate.js +31 -0
  113. package/script/src/utils/tiff_packbits.d.ts +24 -0
  114. package/script/src/utils/tiff_packbits.js +94 -0
  115. package/script/src/utils/webp_decoder.d.ts +3 -1
  116. package/script/src/utils/webp_decoder.js +144 -63
  117. 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
- if (metadata) {
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, _options?: unknown): Promise<Uint8Array>;
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 (error) {
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 (error) {
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, _options) {
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
- if (metadata) {
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
- if (typeof OffscreenCanvas !== "undefined") {
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: 0.9,
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(85); // Quality 85
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 (typeof ImageDecoder !== "undefined") {
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 (error) {
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 = [];