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
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 { APNGEncoderOptions, ASCIIEncoderOptions, AVIFEncoderOptions, FrameMetadata, GIFEncoderOptions, HEICEncoderOptions, ImageData, ImageDecoderOptions, ImageFormat, ImageFrame, ImageMetadata, JPEGEncoderOptions, MultiFrameImageData, PNGEncoderOptions, 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";
@@ -59,4 +59,6 @@ export { PPMFormat } from "./src/formats/ppm.js";
59
59
  export { ASCIIFormat } from "./src/formats/ascii.js";
60
60
  export { HEICFormat } from "./src/formats/heic.js";
61
61
  export { AVIFFormat } from "./src/formats/avif.js";
62
+ export { decodeBase64, encodeBase64, parseDataUrl, toDataUrl } from "./src/utils/base64.js";
63
+ export { cmykToRgb, cmykToRgba, rgbaToCmyk, rgbToCmyk } from "./src/utils/image_processing.js";
62
64
  //# sourceMappingURL=mod.d.ts.map
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);
@@ -58,3 +58,5 @@ export { PPMFormat } from "./src/formats/ppm.js";
58
58
  export { ASCIIFormat } from "./src/formats/ascii.js";
59
59
  export { HEICFormat } from "./src/formats/heic.js";
60
60
  export { AVIFFormat } from "./src/formats/avif.js";
61
+ export { decodeBase64, encodeBase64, parseDataUrl, toDataUrl } from "./src/utils/base64.js";
62
+ export { cmykToRgb, cmykToRgba, rgbaToCmyk, rgbToCmyk } from "./src/utils/image_processing.js";
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat, ImageMetadata, MultiFrameImageData } from "../types.js";
1
+ import type { APNGEncoderOptions, 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,27 @@ 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
+ * @param options Encoding options (compressionLevel 0-9, default 6)
39
40
  * @returns Encoded APNG image bytes
40
41
  */
41
- encode(imageData: ImageData): Promise<Uint8Array>;
42
+ encode(imageData: ImageData, options?: APNGEncoderOptions): Promise<Uint8Array>;
42
43
  /**
43
44
  * Encode multi-frame image data to APNG format
44
45
  * @param imageData Multi-frame image data to encode
46
+ * @param options Encoding options (compressionLevel 0-9, default 6)
45
47
  * @returns Encoded APNG image bytes
46
48
  */
47
- encodeFrames(imageData: MultiFrameImageData): Promise<Uint8Array>;
49
+ encodeFrames(imageData: MultiFrameImageData, options?: APNGEncoderOptions): Promise<Uint8Array>;
48
50
  private decodeFrameData;
49
51
  /**
50
52
  * 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";
@@ -252,9 +250,10 @@ export class APNGFormat extends PNGBase {
252
250
  /**
253
251
  * Encode RGBA image data to APNG format (single frame)
254
252
  * @param imageData Image data to encode
253
+ * @param options Encoding options (compressionLevel 0-9, default 6)
255
254
  * @returns Encoded APNG image bytes
256
255
  */
257
- encode(imageData) {
256
+ encode(imageData, options) {
258
257
  // For single frame, create a multi-frame with one frame
259
258
  const multiFrame = {
260
259
  width: imageData.width,
@@ -267,15 +266,21 @@ export class APNGFormat extends PNGBase {
267
266
  }],
268
267
  metadata: imageData.metadata,
269
268
  };
270
- return this.encodeFrames(multiFrame);
269
+ return this.encodeFrames(multiFrame, options);
271
270
  }
272
271
  /**
273
272
  * Encode multi-frame image data to APNG format
274
273
  * @param imageData Multi-frame image data to encode
274
+ * @param options Encoding options (compressionLevel 0-9, default 6)
275
275
  * @returns Encoded APNG image bytes
276
276
  */
277
- async encodeFrames(imageData) {
277
+ async encodeFrames(imageData, options) {
278
278
  const { width, height, frames, metadata } = imageData;
279
+ const compressionLevel = options?.compressionLevel ?? 6;
280
+ // Validate compression level
281
+ if (compressionLevel < 0 || compressionLevel > 9) {
282
+ throw new Error("Compression level must be between 0 and 9");
283
+ }
279
284
  if (frames.length === 0) {
280
285
  throw new Error("No frames to encode");
281
286
  }
@@ -333,7 +338,7 @@ export class APNGFormat extends PNGBase {
333
338
  fctl[25] = 0; // blend_op: APNG_BLEND_OP_SOURCE
334
339
  chunks.push(this.createChunk("fcTL", fctl));
335
340
  // Filter and compress frame data
336
- const filtered = this.filterData(frame.data, frame.width, frame.height);
341
+ const filtered = this.filterData(frame.data, frame.width, frame.height, compressionLevel);
337
342
  const compressed = await this.deflate(filtered);
338
343
  if (i === 0) {
339
344
  // First frame uses IDAT
@@ -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 { AVIFEncoderOptions, 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?: AVIFEncoderOptions): 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,9 @@ 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;
84
+ const requestedQuality = options?.quality;
81
85
  // Try to use runtime encoding if available
82
86
  if (typeof OffscreenCanvas !== "undefined") {
83
87
  try {
@@ -88,20 +92,26 @@ export class AVIFFormat {
88
92
  const imgDataData = new Uint8ClampedArray(data);
89
93
  imgData.data.set(imgDataData);
90
94
  ctx.putImageData(imgData, 0, 0);
95
+ const quality = requestedQuality === undefined
96
+ ? undefined
97
+ : (requestedQuality <= 1
98
+ ? Math.max(0, Math.min(1, requestedQuality))
99
+ : Math.max(1, Math.min(100, requestedQuality)) / 100);
91
100
  // Try to encode as AVIF
92
101
  const blob = await canvas.convertToBlob({
93
102
  type: "image/avif",
103
+ ...(quality === undefined ? {} : { quality }),
94
104
  });
105
+ if (blob.type !== "image/avif") {
106
+ throw new Error(`Runtime did not encode AVIF (got '${blob.type || "(empty)"}')`);
107
+ }
95
108
  const arrayBuffer = await blob.arrayBuffer();
96
109
  const encoded = new Uint8Array(arrayBuffer);
97
110
  // Note: Metadata injection for AVIF is complex and would require
98
111
  // parsing and modifying the ISOBMFF container structure
99
112
  // For now, we rely on the runtime encoder to preserve metadata
100
113
  // 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
- }
114
+ // Future enhancement: inject metadata into AVIF container
105
115
  return encoded;
106
116
  }
107
117
  }
@@ -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 { 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;
@@ -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,12 +153,12 @@ 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 {
158
160
  const encoder = new GIFEncoder(width, height, data);
159
- const encoded = encoder.encode();
161
+ const encoded = encoder.encode(options);
160
162
  // Inject metadata if present
161
163
  if (metadata && Object.keys(metadata).length > 0) {
162
164
  const injected = this.injectMetadata(encoded, metadata);
@@ -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);
@@ -233,7 +237,7 @@ export class GIFFormat {
233
237
  /**
234
238
  * Encode multi-frame image data to animated GIF
235
239
  */
236
- encodeFrames(imageData, _options) {
240
+ encodeFrames(imageData, options) {
237
241
  if (imageData.frames.length === 0) {
238
242
  throw new Error("No frames to encode");
239
243
  }
@@ -243,7 +247,7 @@ export class GIFFormat {
243
247
  const delay = frame.frameMetadata?.delay ?? 100;
244
248
  encoder.addFrame(frame.data, delay);
245
249
  }
246
- return Promise.resolve(encoder.encode());
250
+ return Promise.resolve(encoder.encode(options));
247
251
  }
248
252
  mapDisposalMethod(disposal) {
249
253
  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
@@ -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,9 @@ 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;
85
+ const requestedQuality = options?.quality;
82
86
  // Try to use runtime encoding if available
83
87
  if (typeof OffscreenCanvas !== "undefined") {
84
88
  try {
@@ -89,20 +93,26 @@ export class HEICFormat {
89
93
  const imgDataData = new Uint8ClampedArray(data);
90
94
  imgData.data.set(imgDataData);
91
95
  ctx.putImageData(imgData, 0, 0);
96
+ const quality = requestedQuality === undefined
97
+ ? undefined
98
+ : (requestedQuality <= 1
99
+ ? Math.max(0, Math.min(1, requestedQuality))
100
+ : Math.max(1, Math.min(100, requestedQuality)) / 100);
92
101
  // Try to encode as HEIC
93
102
  const blob = await canvas.convertToBlob({
94
103
  type: "image/heic",
104
+ ...(quality === undefined ? {} : { quality }),
95
105
  });
106
+ if (blob.type !== "image/heic") {
107
+ throw new Error(`Runtime did not encode HEIC (got '${blob.type || "(empty)"}')`);
108
+ }
96
109
  const arrayBuffer = await blob.arrayBuffer();
97
110
  const encoded = new Uint8Array(arrayBuffer);
98
111
  // Note: Metadata injection for HEIC is complex and would require
99
112
  // parsing and modifying the ISOBMFF container structure
100
113
  // For now, we rely on the runtime encoder to preserve metadata
101
114
  // 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
- }
115
+ // Future enhancement: inject metadata into HEIC container
106
116
  return encoded;
107
117
  }
108
118
  }
@@ -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;