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.
Files changed (105) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +507 -333
  3. package/esm/mod.d.ts +4 -4
  4. package/esm/mod.js +2 -2
  5. package/esm/src/formats/apng.d.ts +5 -5
  6. package/esm/src/formats/apng.js +7 -9
  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 +7 -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 +4 -4
  16. package/esm/src/formats/gif.js +14 -10
  17. package/esm/src/formats/heic.d.ts +3 -3
  18. package/esm/src/formats/heic.js +7 -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 +3 -3
  28. package/esm/src/formats/png.js +2 -2
  29. package/esm/src/formats/png_base.js +2 -5
  30. package/esm/src/formats/ppm.d.ts +3 -3
  31. package/esm/src/formats/ppm.js +2 -2
  32. package/esm/src/formats/tiff.d.ts +7 -18
  33. package/esm/src/formats/tiff.js +86 -21
  34. package/esm/src/formats/webp.d.ts +3 -3
  35. package/esm/src/formats/webp.js +11 -8
  36. package/esm/src/image.d.ts +11 -3
  37. package/esm/src/image.js +37 -21
  38. package/esm/src/types.d.ts +56 -4
  39. package/esm/src/utils/gif_decoder.d.ts +4 -1
  40. package/esm/src/utils/gif_decoder.js +91 -65
  41. package/esm/src/utils/image_processing.js +144 -70
  42. package/esm/src/utils/jpeg_decoder.d.ts +17 -4
  43. package/esm/src/utils/jpeg_decoder.js +448 -83
  44. package/esm/src/utils/jpeg_encoder.d.ts +15 -1
  45. package/esm/src/utils/jpeg_encoder.js +263 -24
  46. package/esm/src/utils/resize.js +51 -20
  47. package/esm/src/utils/tiff_deflate.d.ts +18 -0
  48. package/esm/src/utils/tiff_deflate.js +27 -0
  49. package/esm/src/utils/tiff_packbits.d.ts +24 -0
  50. package/esm/src/utils/tiff_packbits.js +90 -0
  51. package/esm/src/utils/webp_decoder.d.ts +3 -1
  52. package/esm/src/utils/webp_decoder.js +144 -63
  53. package/esm/src/utils/webp_encoder.js +5 -11
  54. package/package.json +1 -1
  55. package/script/mod.d.ts +4 -4
  56. package/script/mod.js +2 -2
  57. package/script/src/formats/apng.d.ts +5 -5
  58. package/script/src/formats/apng.js +7 -9
  59. package/script/src/formats/ascii.d.ts +3 -3
  60. package/script/src/formats/ascii.js +1 -1
  61. package/script/src/formats/avif.d.ts +3 -3
  62. package/script/src/formats/avif.js +7 -7
  63. package/script/src/formats/bmp.d.ts +3 -3
  64. package/script/src/formats/bmp.js +2 -2
  65. package/script/src/formats/dng.d.ts +1 -1
  66. package/script/src/formats/dng.js +1 -1
  67. package/script/src/formats/gif.d.ts +4 -4
  68. package/script/src/formats/gif.js +14 -10
  69. package/script/src/formats/heic.d.ts +3 -3
  70. package/script/src/formats/heic.js +7 -7
  71. package/script/src/formats/ico.d.ts +3 -3
  72. package/script/src/formats/ico.js +4 -4
  73. package/script/src/formats/jpeg.d.ts +3 -3
  74. package/script/src/formats/jpeg.js +23 -11
  75. package/script/src/formats/pam.d.ts +3 -3
  76. package/script/src/formats/pam.js +2 -2
  77. package/script/src/formats/pcx.d.ts +3 -3
  78. package/script/src/formats/pcx.js +2 -2
  79. package/script/src/formats/png.d.ts +3 -3
  80. package/script/src/formats/png.js +2 -2
  81. package/script/src/formats/png_base.js +2 -5
  82. package/script/src/formats/ppm.d.ts +3 -3
  83. package/script/src/formats/ppm.js +2 -2
  84. package/script/src/formats/tiff.d.ts +7 -18
  85. package/script/src/formats/tiff.js +86 -21
  86. package/script/src/formats/webp.d.ts +3 -3
  87. package/script/src/formats/webp.js +11 -8
  88. package/script/src/image.d.ts +11 -3
  89. package/script/src/image.js +36 -20
  90. package/script/src/types.d.ts +56 -4
  91. package/script/src/utils/gif_decoder.d.ts +4 -1
  92. package/script/src/utils/gif_decoder.js +91 -65
  93. package/script/src/utils/image_processing.js +144 -70
  94. package/script/src/utils/jpeg_decoder.d.ts +17 -4
  95. package/script/src/utils/jpeg_decoder.js +448 -83
  96. package/script/src/utils/jpeg_encoder.d.ts +15 -1
  97. package/script/src/utils/jpeg_encoder.js +263 -24
  98. package/script/src/utils/resize.js +51 -20
  99. package/script/src/utils/tiff_deflate.d.ts +18 -0
  100. package/script/src/utils/tiff_deflate.js +31 -0
  101. package/script/src/utils/tiff_packbits.d.ts +24 -0
  102. package/script/src/utils/tiff_packbits.js +94 -0
  103. package/script/src/utils/webp_decoder.d.ts +3 -1
  104. package/script/src/utils/webp_decoder.js +144 -63
  105. package/script/src/utils/webp_encoder.js +5 -11
package/esm/mod.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * @example
9
9
  * ```ts
10
- * import { Image } from "@cross/image";
10
+ * import { Image } from "jsr:@cross/image";
11
11
  *
12
12
  * // Decode an image
13
13
  * const data = await Deno.readFile("input.png");
@@ -26,7 +26,7 @@
26
26
  *
27
27
  * @example
28
28
  * ```ts
29
- * import { Image } from "@cross/image";
29
+ * import { Image } from "jsr:@cross/image";
30
30
  *
31
31
  * // Create a blank canvas
32
32
  * const canvas = Image.create(400, 300, 255, 255, 255);
@@ -43,13 +43,13 @@
43
43
  * ```
44
44
  */
45
45
  export { Image } from "./src/image.js";
46
- export type { ASCIIOptions, FrameMetadata, ImageData, ImageFormat, ImageFrame, ImageMetadata, MultiFrameImageData, ResizeOptions, WebPEncodeOptions, } from "./src/types.js";
46
+ export type { ASCIIEncoderOptions, FrameMetadata, ImageData, ImageDecoderOptions, ImageFormat, ImageFrame, ImageMetadata, JPEGEncoderOptions, MultiFrameImageData, ResizeOptions, TIFFEncoderOptions, WebPEncoderOptions, } from "./src/types.js";
47
47
  export { PNGFormat } from "./src/formats/png.js";
48
48
  export { APNGFormat } from "./src/formats/apng.js";
49
49
  export { JPEGFormat } from "./src/formats/jpeg.js";
50
50
  export { WebPFormat } from "./src/formats/webp.js";
51
51
  export { GIFFormat } from "./src/formats/gif.js";
52
- export { type TIFFEncodeOptions, TIFFFormat } from "./src/formats/tiff.js";
52
+ export { TIFFFormat } from "./src/formats/tiff.js";
53
53
  export { BMPFormat } from "./src/formats/bmp.js";
54
54
  export { ICOFormat } from "./src/formats/ico.js";
55
55
  export { DNGFormat } from "./src/formats/dng.js";
package/esm/mod.js CHANGED
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * @example
9
9
  * ```ts
10
- * import { Image } from "@cross/image";
10
+ * import { Image } from "jsr:@cross/image";
11
11
  *
12
12
  * // Decode an image
13
13
  * const data = await Deno.readFile("input.png");
@@ -26,7 +26,7 @@
26
26
  *
27
27
  * @example
28
28
  * ```ts
29
- * import { Image } from "@cross/image";
29
+ * import { Image } from "jsr:@cross/image";
30
30
  *
31
31
  * // Create a blank canvas
32
32
  * const canvas = Image.create(400, 300, 255, 255, 255);
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat, ImageMetadata, MultiFrameImageData } from "../types.js";
1
+ import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, MultiFrameImageData } from "../types.js";
2
2
  import { PNGBase } from "./png_base.js";
3
3
  /**
4
4
  * APNG (Animated PNG) format handler
@@ -26,25 +26,25 @@ export declare class APNGFormat extends PNGBase implements ImageFormat {
26
26
  * @param data Raw APNG image data
27
27
  * @returns Decoded image data with RGBA pixels of first frame
28
28
  */
29
- decode(data: Uint8Array): Promise<ImageData>;
29
+ decode(data: Uint8Array, settings?: ImageDecoderOptions): Promise<ImageData>;
30
30
  /**
31
31
  * Decode all frames from APNG image
32
32
  * @param data Raw APNG image data
33
33
  * @returns Decoded multi-frame image data
34
34
  */
35
- decodeFrames(data: Uint8Array): Promise<MultiFrameImageData>;
35
+ decodeFrames(data: Uint8Array, _settings?: ImageDecoderOptions): Promise<MultiFrameImageData>;
36
36
  /**
37
37
  * Encode RGBA image data to APNG format (single frame)
38
38
  * @param imageData Image data to encode
39
39
  * @returns Encoded APNG image bytes
40
40
  */
41
- encode(imageData: ImageData): Promise<Uint8Array>;
41
+ encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
42
42
  /**
43
43
  * Encode multi-frame image data to APNG format
44
44
  * @param imageData Multi-frame image data to encode
45
45
  * @returns Encoded APNG image bytes
46
46
  */
47
- encodeFrames(imageData: MultiFrameImageData): Promise<Uint8Array>;
47
+ encodeFrames(imageData: MultiFrameImageData, _options?: unknown): Promise<Uint8Array>;
48
48
  private decodeFrameData;
49
49
  /**
50
50
  * Get the list of metadata fields supported by APNG format
@@ -67,8 +67,8 @@ export class APNGFormat extends PNGBase {
67
67
  * @param data Raw APNG image data
68
68
  * @returns Decoded image data with RGBA pixels of first frame
69
69
  */
70
- async decode(data) {
71
- const frames = await this.decodeFrames(data);
70
+ async decode(data, settings) {
71
+ const frames = await this.decodeFrames(data, settings);
72
72
  const firstFrame = frames.frames[0];
73
73
  return {
74
74
  width: firstFrame.width,
@@ -82,7 +82,7 @@ export class APNGFormat extends PNGBase {
82
82
  * @param data Raw APNG image data
83
83
  * @returns Decoded multi-frame image data
84
84
  */
85
- async decodeFrames(data) {
85
+ async decodeFrames(data, _settings) {
86
86
  if (!this.canDecode(data)) {
87
87
  throw new Error("Invalid APNG signature or missing acTL chunk");
88
88
  }
@@ -169,9 +169,7 @@ export class APNGFormat extends PNGBase {
169
169
  const delayDen = this.readUint16(chunk.data, 22);
170
170
  const disposeOp = chunk.data[24];
171
171
  const blendOp = chunk.data[25];
172
- const delay = delayDen === 0
173
- ? delayNum * 10
174
- : Math.round((delayNum / delayDen) * 1000);
172
+ const delay = delayDen === 0 ? delayNum * 10 : Math.round((delayNum / delayDen) * 1000);
175
173
  let disposal = "none";
176
174
  if (disposeOp === 1)
177
175
  disposal = "background";
@@ -254,7 +252,7 @@ export class APNGFormat extends PNGBase {
254
252
  * @param imageData Image data to encode
255
253
  * @returns Encoded APNG image bytes
256
254
  */
257
- encode(imageData) {
255
+ encode(imageData, _options) {
258
256
  // For single frame, create a multi-frame with one frame
259
257
  const multiFrame = {
260
258
  width: imageData.width,
@@ -267,14 +265,14 @@ export class APNGFormat extends PNGBase {
267
265
  }],
268
266
  metadata: imageData.metadata,
269
267
  };
270
- return this.encodeFrames(multiFrame);
268
+ return this.encodeFrames(multiFrame, _options);
271
269
  }
272
270
  /**
273
271
  * Encode multi-frame image data to APNG format
274
272
  * @param imageData Multi-frame image data to encode
275
273
  * @returns Encoded APNG image bytes
276
274
  */
277
- async encodeFrames(imageData) {
275
+ async encodeFrames(imageData, _options) {
278
276
  const { width, height, frames, metadata } = imageData;
279
277
  if (frames.length === 0) {
280
278
  throw new Error("No frames to encode");
@@ -1,4 +1,4 @@
1
- import type { ASCIIOptions, ImageData, ImageFormat, ImageMetadata } from "../types.js";
1
+ import type { ASCIIEncoderOptions, ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata } from "../types.js";
2
2
  /**
3
3
  * ASCII format handler
4
4
  * Converts images to ASCII art text representation
@@ -22,14 +22,14 @@ export declare class ASCIIFormat implements ImageFormat {
22
22
  * @param data Raw ASCII art data
23
23
  * @returns Decoded image data with grayscale RGBA pixels
24
24
  */
25
- decode(data: Uint8Array): Promise<ImageData>;
25
+ decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
26
26
  /**
27
27
  * Encode RGBA image data to ASCII art
28
28
  * @param imageData Image data to encode
29
29
  * @param options Optional ASCII encoding options
30
30
  * @returns Encoded ASCII art as UTF-8 bytes
31
31
  */
32
- encode(imageData: ImageData, options?: ASCIIOptions): Promise<Uint8Array>;
32
+ encode(imageData: ImageData, options?: ASCIIEncoderOptions): Promise<Uint8Array>;
33
33
  /**
34
34
  * Parse options from the options line
35
35
  */
@@ -68,7 +68,7 @@ export class ASCIIFormat {
68
68
  * @param data Raw ASCII art data
69
69
  * @returns Decoded image data with grayscale RGBA pixels
70
70
  */
71
- decode(data) {
71
+ decode(data, _options) {
72
72
  if (!this.canDecode(data)) {
73
73
  throw new Error("Invalid ASCII art signature");
74
74
  }
@@ -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
  * AVIF format handler
4
4
  * Supports AVIF images using runtime APIs (ImageDecoder/OffscreenCanvas)
@@ -21,7 +21,7 @@ export declare class AVIFFormat implements ImageFormat {
21
21
  * @param data Raw AVIF 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 AVIF format
27
27
  * Uses runtime APIs (OffscreenCanvas) for encoding
@@ -32,7 +32,7 @@ export declare class AVIFFormat implements ImageFormat {
32
32
  * @param imageData Image data to encode
33
33
  * @returns Encoded AVIF image bytes
34
34
  */
35
- encode(imageData: ImageData): Promise<Uint8Array>;
35
+ encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
36
36
  /**
37
37
  * Decode using runtime APIs
38
38
  * @param data Raw AVIF data
@@ -49,10 +49,13 @@ export class AVIFFormat {
49
49
  * @param data Raw AVIF image data
50
50
  * @returns Decoded image data with RGBA pixels
51
51
  */
52
- async decode(data) {
52
+ async decode(data, settings) {
53
53
  if (!this.canDecode(data)) {
54
54
  throw new Error("Invalid AVIF signature");
55
55
  }
56
+ if (settings?.runtimeDecoding === "never") {
57
+ throw new Error("AVIF decoding requires runtime APIs; set runtimeDecoding to 'prefer'");
58
+ }
56
59
  // Extract metadata before decoding pixels
57
60
  const metadata = await this.extractMetadata(data);
58
61
  // Use runtime decoder
@@ -76,8 +79,8 @@ export class AVIFFormat {
76
79
  * @param imageData Image data to encode
77
80
  * @returns Encoded AVIF image bytes
78
81
  */
79
- async encode(imageData) {
80
- const { width, height, data, metadata } = imageData;
82
+ async encode(imageData, _options) {
83
+ const { width, height, data, metadata: _metadata } = imageData;
81
84
  // Try to use runtime encoding if available
82
85
  if (typeof OffscreenCanvas !== "undefined") {
83
86
  try {
@@ -98,10 +101,7 @@ export class AVIFFormat {
98
101
  // parsing and modifying the ISOBMFF container structure
99
102
  // For now, we rely on the runtime encoder to preserve metadata
100
103
  // if it was passed through the canvas
101
- if (metadata) {
102
- // Future enhancement: inject metadata into AVIF container
103
- console.warn("AVIF metadata injection not yet implemented, metadata may be lost");
104
- }
104
+ // Future enhancement: inject metadata into AVIF container
105
105
  return encoded;
106
106
  }
107
107
  }
@@ -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
  */
@@ -38,7 +38,7 @@ export class BMPFormat {
38
38
  * @param data Raw BMP image data
39
39
  * @returns Decoded image data with RGBA pixels
40
40
  */
41
- decode(data) {
41
+ decode(data, _options) {
42
42
  if (!this.canDecode(data)) {
43
43
  throw new Error("Invalid BMP signature");
44
44
  }
@@ -115,7 +115,7 @@ export class BMPFormat {
115
115
  * @param imageData Image data to encode
116
116
  * @returns Encoded BMP image bytes
117
117
  */
118
- encode(imageData) {
118
+ encode(imageData, _options) {
119
119
  const { width, height, data, metadata } = imageData;
120
120
  // Calculate sizes
121
121
  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
@@ -74,7 +74,7 @@ export class DNGFormat extends TIFFFormat {
74
74
  * @param imageData Image data to encode
75
75
  * @returns Encoded DNG image bytes
76
76
  */
77
- encode(imageData) {
77
+ encode(imageData, _options) {
78
78
  const { width, height, data } = imageData;
79
79
  // We'll create a Linear DNG (demosaiced RGB)
80
80
  // 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 { 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,18 +32,18 @@ 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?: unknown): 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
  */
@@ -56,13 +56,16 @@ export class GIFFormat {
56
56
  * @param data Raw GIF image data
57
57
  * @returns Decoded image data with RGBA pixels of first frame
58
58
  */
59
- async decode(data) {
59
+ async decode(data, settings) {
60
60
  if (!this.canDecode(data)) {
61
61
  throw new Error("Invalid GIF signature");
62
62
  }
63
- // Try pure-JS decoder first
63
+ // Try pure-JS decoder first with tolerant decoding enabled by default
64
64
  try {
65
- const decoder = new GIFDecoder(data);
65
+ const decoder = new GIFDecoder(data, {
66
+ tolerantDecoding: settings?.tolerantDecoding ?? true,
67
+ onWarning: settings?.onWarning,
68
+ });
66
69
  const result = decoder.decode();
67
70
  // Validate dimensions for security (prevent integer overflow and heap exhaustion)
68
71
  validateImageDimensions(result.width, result.height);
@@ -75,9 +78,8 @@ export class GIFFormat {
75
78
  metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
76
79
  };
77
80
  }
78
- catch (error) {
81
+ catch (_error) {
79
82
  // Fall back to runtime decoder if pure-JS fails
80
- console.warn("Pure-JS GIF decoder failed, falling back to runtime:", error);
81
83
  let pos = 6; // Skip "GIF89a" or "GIF87a"
82
84
  const width = readUint16LE(data, pos);
83
85
  pos += 2;
@@ -151,7 +153,7 @@ export class GIFFormat {
151
153
  * @param imageData Image data to encode
152
154
  * @returns Encoded GIF image bytes
153
155
  */
154
- async encode(imageData) {
156
+ async encode(imageData, _options) {
155
157
  const { width, height, data, metadata } = imageData;
156
158
  // Try pure-JS encoder first
157
159
  try {
@@ -164,9 +166,8 @@ export class GIFFormat {
164
166
  }
165
167
  return encoded;
166
168
  }
167
- catch (error) {
169
+ catch (_error) {
168
170
  // Fall back to runtime encoding if pure-JS fails
169
- console.warn("Pure-JS GIF encoder failed, falling back to runtime:", error);
170
171
  if (typeof OffscreenCanvas !== "undefined") {
171
172
  try {
172
173
  const canvas = new OffscreenCanvas(width, height);
@@ -199,12 +200,15 @@ export class GIFFormat {
199
200
  /**
200
201
  * Decode all frames from an animated GIF
201
202
  */
202
- decodeFrames(data) {
203
+ decodeFrames(data, settings) {
203
204
  if (!this.canDecode(data)) {
204
205
  throw new Error("Invalid GIF signature");
205
206
  }
206
207
  try {
207
- const decoder = new GIFDecoder(data);
208
+ const decoder = new GIFDecoder(data, {
209
+ tolerantDecoding: settings?.tolerantDecoding ?? true,
210
+ onWarning: settings?.onWarning,
211
+ });
208
212
  const result = decoder.decodeAllFrames();
209
213
  // Extract metadata from comment extensions
210
214
  const metadata = this.extractGIFMetadata(data);
@@ -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
  * 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?: unknown): Promise<Uint8Array>;
36
36
  /**
37
37
  * Decode using runtime APIs
38
38
  * @param data Raw HEIC data
@@ -50,10 +50,13 @@ export class HEICFormat {
50
50
  * @param data Raw HEIC image data
51
51
  * @returns Decoded image data with RGBA pixels
52
52
  */
53
- async decode(data) {
53
+ async decode(data, settings) {
54
54
  if (!this.canDecode(data)) {
55
55
  throw new Error("Invalid HEIC signature");
56
56
  }
57
+ if (settings?.runtimeDecoding === "never") {
58
+ throw new Error("HEIC decoding requires runtime APIs; set runtimeDecoding to 'prefer'");
59
+ }
57
60
  // Extract metadata before decoding pixels
58
61
  const metadata = await this.extractMetadata(data);
59
62
  // Use runtime decoder
@@ -77,8 +80,8 @@ export class HEICFormat {
77
80
  * @param imageData Image data to encode
78
81
  * @returns Encoded HEIC image bytes
79
82
  */
80
- async encode(imageData) {
81
- const { width, height, data, metadata } = imageData;
83
+ async encode(imageData, _options) {
84
+ const { width, height, data, metadata: _metadata } = imageData;
82
85
  // Try to use runtime encoding if available
83
86
  if (typeof OffscreenCanvas !== "undefined") {
84
87
  try {
@@ -99,10 +102,7 @@ export class HEICFormat {
99
102
  // parsing and modifying the ISOBMFF container structure
100
103
  // For now, we rely on the runtime encoder to preserve metadata
101
104
  // if it was passed through the canvas
102
- if (metadata) {
103
- // Future enhancement: inject metadata into HEIC container
104
- console.warn("HEIC metadata injection not yet implemented, metadata may be lost");
105
- }
105
+ // Future enhancement: inject metadata into HEIC container
106
106
  return encoded;
107
107
  }
108
108
  }
@@ -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
  */
@@ -49,7 +49,7 @@ export class ICOFormat {
49
49
  * @param data Raw ICO image data
50
50
  * @returns Decoded image data with RGBA pixels
51
51
  */
52
- async decode(data) {
52
+ async decode(data, options) {
53
53
  if (!this.canDecode(data)) {
54
54
  throw new Error("Invalid ICO signature");
55
55
  }
@@ -100,7 +100,7 @@ export class ICOFormat {
100
100
  imageData[2] === 0x4e &&
101
101
  imageData[3] === 0x47) {
102
102
  // It's a PNG, decode it
103
- return await this.pngFormat.decode(imageData);
103
+ return await this.pngFormat.decode(imageData, options);
104
104
  }
105
105
  // Otherwise, it's a BMP without the file header (DIB format)
106
106
  return this.decodeDIB(imageData);
@@ -182,10 +182,10 @@ export class ICOFormat {
182
182
  * @param imageData Image data to encode
183
183
  * @returns Encoded ICO image bytes
184
184
  */
185
- async encode(imageData) {
185
+ async encode(imageData, _options) {
186
186
  const { width, height } = imageData;
187
187
  // Encode the image as PNG
188
- const pngData = await this.pngFormat.encode(imageData);
188
+ const pngData = await this.pngFormat.encode(imageData, undefined);
189
189
  // Create ICO file structure
190
190
  // ICONDIR (6 bytes) + ICONDIRENTRY (16 bytes) + PNG data
191
191
  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;
@@ -37,7 +37,7 @@ export class JPEGFormat {
37
37
  * @param data Raw JPEG image data
38
38
  * @returns Decoded image data with RGBA pixels
39
39
  */
40
- async decode(data) {
40
+ async decode(data, settings) {
41
41
  if (!this.canDecode(data)) {
42
42
  throw new Error("Invalid JPEG signature");
43
43
  }
@@ -95,7 +95,7 @@ export class JPEGFormat {
95
95
  validateImageDimensions(width, height);
96
96
  // For a pure JS implementation, we'd need to implement full JPEG decoding
97
97
  // which is very complex. Instead, we'll use the browser/runtime's decoder.
98
- const rgba = await this.decodeUsingRuntime(data, width, height);
98
+ const rgba = await this.decodeUsingRuntime(data, width, height, settings);
99
99
  return {
100
100
  width,
101
101
  height,
@@ -108,10 +108,13 @@ export class JPEGFormat {
108
108
  * @param imageData Image data to encode
109
109
  * @returns Encoded JPEG image bytes
110
110
  */
111
- async encode(imageData) {
111
+ async encode(imageData, options) {
112
112
  const { width, height, data, metadata } = imageData;
113
+ const requestedQuality = options?.quality;
114
+ const requestedProgressive = options?.progressive;
113
115
  // Try to use runtime encoding if available (better quality)
114
- if (typeof OffscreenCanvas !== "undefined") {
116
+ // Note: progressive output is only supported by the pure-JS encoder.
117
+ if (!requestedProgressive && typeof OffscreenCanvas !== "undefined") {
115
118
  try {
116
119
  const canvas = new OffscreenCanvas(width, height);
117
120
  const ctx = canvas.getContext("2d");
@@ -120,9 +123,12 @@ export class JPEGFormat {
120
123
  const imgDataData = new Uint8ClampedArray(data);
121
124
  imgData.data.set(imgDataData);
122
125
  ctx.putImageData(imgData, 0, 0);
126
+ const quality = requestedQuality === undefined
127
+ ? 0.9
128
+ : Math.max(1, Math.min(100, requestedQuality)) / 100;
123
129
  const blob = await canvas.convertToBlob({
124
130
  type: "image/jpeg",
125
- quality: 0.9,
131
+ quality,
126
132
  });
127
133
  const arrayBuffer = await blob.arrayBuffer();
128
134
  const encoded = new Uint8Array(arrayBuffer);
@@ -141,7 +147,10 @@ export class JPEGFormat {
141
147
  const { JPEGEncoder } = await import("../utils/jpeg_encoder.js");
142
148
  const dpiX = metadata?.dpiX ?? 72;
143
149
  const dpiY = metadata?.dpiY ?? 72;
144
- const encoder = new JPEGEncoder(85); // Quality 85
150
+ const encoder = new JPEGEncoder({
151
+ quality: requestedQuality,
152
+ progressive: requestedProgressive,
153
+ });
145
154
  const encoded = encoder.encode(width, height, data, dpiX, dpiY);
146
155
  // Add EXIF metadata if present
147
156
  if (metadata && Object.keys(metadata).length > 0) {
@@ -185,9 +194,10 @@ export class JPEGFormat {
185
194
  result.set(encoded.slice(pos), pos + app1.length);
186
195
  return result;
187
196
  }
188
- async decodeUsingRuntime(data, _width, _height) {
197
+ async decodeUsingRuntime(data, _width, _height, settings) {
189
198
  // Try to use ImageDecoder API if available (Deno, modern browsers)
190
- if (typeof ImageDecoder !== "undefined") {
199
+ if (settings?.runtimeDecoding !== "never" &&
200
+ typeof ImageDecoder !== "undefined") {
191
201
  try {
192
202
  const decoder = new ImageDecoder({ data, type: "image/jpeg" });
193
203
  const result = await decoder.decode();
@@ -202,15 +212,17 @@ export class JPEGFormat {
202
212
  bitmap.close();
203
213
  return new Uint8Array(imageData.data.buffer);
204
214
  }
205
- catch (error) {
215
+ catch (_error) {
206
216
  // ImageDecoder API failed, fall through to pure JS decoder
207
- console.warn("JPEG decoding with ImageDecoder failed, using pure JS decoder:", error);
208
217
  }
209
218
  }
210
219
  // Fallback to pure JavaScript decoder
211
220
  try {
212
221
  const { JPEGDecoder } = await import("../utils/jpeg_decoder.js");
213
- const decoder = new JPEGDecoder(data);
222
+ const decoder = new JPEGDecoder(data, {
223
+ tolerantDecoding: settings?.tolerantDecoding ?? true,
224
+ onWarning: settings?.onWarning,
225
+ });
214
226
  return decoder.decode();
215
227
  }
216
228
  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
  */