cross-image 0.4.0 → 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 (50) hide show
  1. package/README.md +109 -1
  2. package/esm/mod.d.ts +3 -1
  3. package/esm/mod.js +2 -0
  4. package/esm/src/formats/apng.d.ts +5 -3
  5. package/esm/src/formats/apng.js +11 -4
  6. package/esm/src/formats/avif.d.ts +2 -2
  7. package/esm/src/formats/avif.js +11 -1
  8. package/esm/src/formats/gif.d.ts +3 -3
  9. package/esm/src/formats/gif.js +4 -4
  10. package/esm/src/formats/heic.d.ts +2 -2
  11. package/esm/src/formats/heic.js +11 -1
  12. package/esm/src/formats/png.d.ts +3 -2
  13. package/esm/src/formats/png.js +8 -2
  14. package/esm/src/formats/png_base.d.ts +42 -1
  15. package/esm/src/formats/png_base.js +198 -5
  16. package/esm/src/formats/tiff.js +76 -6
  17. package/esm/src/image.d.ts +15 -0
  18. package/esm/src/image.js +29 -1
  19. package/esm/src/types.d.ts +66 -0
  20. package/esm/src/utils/base64.d.ts +32 -0
  21. package/esm/src/utils/base64.js +173 -0
  22. package/esm/src/utils/gif_encoder.d.ts +3 -1
  23. package/esm/src/utils/gif_encoder.js +4 -2
  24. package/esm/src/utils/image_processing.d.ts +31 -0
  25. package/esm/src/utils/image_processing.js +88 -0
  26. package/package.json +1 -1
  27. package/script/mod.d.ts +3 -1
  28. package/script/mod.js +11 -1
  29. package/script/src/formats/apng.d.ts +5 -3
  30. package/script/src/formats/apng.js +11 -4
  31. package/script/src/formats/avif.d.ts +2 -2
  32. package/script/src/formats/avif.js +11 -1
  33. package/script/src/formats/gif.d.ts +3 -3
  34. package/script/src/formats/gif.js +4 -4
  35. package/script/src/formats/heic.d.ts +2 -2
  36. package/script/src/formats/heic.js +11 -1
  37. package/script/src/formats/png.d.ts +3 -2
  38. package/script/src/formats/png.js +8 -2
  39. package/script/src/formats/png_base.d.ts +42 -1
  40. package/script/src/formats/png_base.js +198 -5
  41. package/script/src/formats/tiff.js +76 -6
  42. package/script/src/image.d.ts +15 -0
  43. package/script/src/image.js +28 -0
  44. package/script/src/types.d.ts +66 -0
  45. package/script/src/utils/base64.d.ts +32 -0
  46. package/script/src/utils/base64.js +179 -0
  47. package/script/src/utils/gif_encoder.d.ts +3 -1
  48. package/script/src/utils/gif_encoder.js +4 -2
  49. package/script/src/utils/image_processing.d.ts +31 -0
  50. package/script/src/utils/image_processing.js +92 -0
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Base64 utilities.
3
+ *
4
+ * Designed to work across Deno, Node.js, and Bun without external dependencies.
5
+ */
6
+ const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
7
+ const DECODE_TABLE = (() => {
8
+ const table = new Uint8Array(256);
9
+ table.fill(0xff);
10
+ for (let i = 0; i < BASE64_ALPHABET.length; i++) {
11
+ table[BASE64_ALPHABET.charCodeAt(i)] = i;
12
+ }
13
+ return table;
14
+ })();
15
+ function stripWhitespace(input) {
16
+ // Avoid regex to keep this fast and allocation-light.
17
+ let out = "";
18
+ for (let i = 0; i < input.length; i++) {
19
+ const code = input.charCodeAt(i);
20
+ // Space, tab, CR, LF
21
+ if (code === 0x20 || code === 0x09 || code === 0x0d || code === 0x0a)
22
+ continue;
23
+ out += input[i];
24
+ }
25
+ return out;
26
+ }
27
+ function normalizeBase64(input) {
28
+ // Accept URL-safe alphabet in decode paths.
29
+ let s = stripWhitespace(input.trim());
30
+ if (s.length === 0)
31
+ return s;
32
+ // Map base64url to base64.
33
+ if (s.includes("-") || s.includes("_")) {
34
+ s = s.replaceAll("-", "+").replaceAll("_", "/");
35
+ }
36
+ const remainder = s.length % 4;
37
+ if (remainder === 1) {
38
+ throw new Error("Invalid base64: length must not be 1 (mod 4)");
39
+ }
40
+ if (remainder === 2)
41
+ s += "==";
42
+ if (remainder === 3)
43
+ s += "=";
44
+ return s;
45
+ }
46
+ /**
47
+ * Encode bytes into a standard Base64 string.
48
+ */
49
+ export function encodeBase64(bytes) {
50
+ if (bytes.length === 0)
51
+ return "";
52
+ const outLen = Math.ceil(bytes.length / 3) * 4;
53
+ const out = new Array(outLen);
54
+ let i = 0;
55
+ let o = 0;
56
+ for (; i + 2 < bytes.length; i += 3) {
57
+ const n = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
58
+ out[o++] = BASE64_ALPHABET[(n >> 18) & 63];
59
+ out[o++] = BASE64_ALPHABET[(n >> 12) & 63];
60
+ out[o++] = BASE64_ALPHABET[(n >> 6) & 63];
61
+ out[o++] = BASE64_ALPHABET[n & 63];
62
+ }
63
+ const remaining = bytes.length - i;
64
+ if (remaining === 1) {
65
+ const n = bytes[i] << 16;
66
+ out[o++] = BASE64_ALPHABET[(n >> 18) & 63];
67
+ out[o++] = BASE64_ALPHABET[(n >> 12) & 63];
68
+ out[o++] = "=";
69
+ out[o++] = "=";
70
+ }
71
+ else if (remaining === 2) {
72
+ const n = (bytes[i] << 16) | (bytes[i + 1] << 8);
73
+ out[o++] = BASE64_ALPHABET[(n >> 18) & 63];
74
+ out[o++] = BASE64_ALPHABET[(n >> 12) & 63];
75
+ out[o++] = BASE64_ALPHABET[(n >> 6) & 63];
76
+ out[o++] = "=";
77
+ }
78
+ return out.join("");
79
+ }
80
+ /**
81
+ * Decode a Base64 (or Base64URL) string into bytes.
82
+ *
83
+ * - Whitespace is ignored.
84
+ * - Missing padding is tolerated.
85
+ * - `-`/`_` are accepted as URL-safe variants.
86
+ */
87
+ export function decodeBase64(base64) {
88
+ const s = normalizeBase64(base64);
89
+ if (s.length === 0)
90
+ return new Uint8Array(0);
91
+ let padding = 0;
92
+ if (s.endsWith("=="))
93
+ padding = 2;
94
+ else if (s.endsWith("="))
95
+ padding = 1;
96
+ const quadCount = s.length / 4;
97
+ const outLen = quadCount * 3 - padding;
98
+ const out = new Uint8Array(outLen);
99
+ let o = 0;
100
+ for (let i = 0; i < s.length; i += 4) {
101
+ const c0 = s.charCodeAt(i);
102
+ const c1 = s.charCodeAt(i + 1);
103
+ const c2 = s.charCodeAt(i + 2);
104
+ const c3 = s.charCodeAt(i + 3);
105
+ const v0 = DECODE_TABLE[c0];
106
+ const v1 = DECODE_TABLE[c1];
107
+ if (v0 === 0xff || v1 === 0xff) {
108
+ throw new Error("Invalid base64: invalid character");
109
+ }
110
+ const isPad2 = c2 === 0x3d; // '='
111
+ const isPad3 = c3 === 0x3d;
112
+ const v2 = isPad2 ? 0 : DECODE_TABLE[c2];
113
+ const v3 = isPad3 ? 0 : DECODE_TABLE[c3];
114
+ if ((!isPad2 && v2 === 0xff) || (!isPad3 && v3 === 0xff)) {
115
+ throw new Error("Invalid base64: invalid character");
116
+ }
117
+ const n = (v0 << 18) | (v1 << 12) | (v2 << 6) | v3;
118
+ out[o++] = (n >> 16) & 0xff;
119
+ if (!isPad2) {
120
+ if (o >= out.length)
121
+ break;
122
+ out[o++] = (n >> 8) & 0xff;
123
+ }
124
+ if (!isPad3) {
125
+ if (o >= out.length)
126
+ break;
127
+ out[o++] = n & 0xff;
128
+ }
129
+ if (isPad2 || isPad3) {
130
+ // Padding is only valid in the final quartet.
131
+ if (i + 4 !== s.length) {
132
+ throw new Error("Invalid base64: padding can only appear at the end");
133
+ }
134
+ // If third char is padding, fourth must also be padding.
135
+ if (isPad2 && !isPad3) {
136
+ throw new Error("Invalid base64: invalid padding");
137
+ }
138
+ }
139
+ }
140
+ return out;
141
+ }
142
+ /**
143
+ * Create a base64 data URL.
144
+ */
145
+ export function toDataUrl(mime, bytes) {
146
+ if (!mime)
147
+ throw new Error("mime is required");
148
+ return `data:${mime};base64,${encodeBase64(bytes)}`;
149
+ }
150
+ /**
151
+ * Parse a base64 data URL.
152
+ *
153
+ * Supports `data:<mime>;base64,<payload>`.
154
+ */
155
+ export function parseDataUrl(url) {
156
+ if (!url.startsWith("data:")) {
157
+ throw new Error("Invalid data URL: must start with 'data:'");
158
+ }
159
+ const commaIndex = url.indexOf(",");
160
+ if (commaIndex === -1) {
161
+ throw new Error("Invalid data URL: missing ',' separator");
162
+ }
163
+ const meta = url.slice(5, commaIndex);
164
+ const payload = url.slice(commaIndex + 1);
165
+ // We only support base64 payloads.
166
+ const metaParts = meta.split(";");
167
+ const mime = metaParts[0] || "application/octet-stream";
168
+ const isBase64 = metaParts.some((p) => p.toLowerCase() === "base64");
169
+ if (!isBase64) {
170
+ throw new Error("Invalid data URL: only base64 payloads are supported");
171
+ }
172
+ return { mime, bytes: decodeBase64(payload) };
173
+ }
@@ -25,6 +25,8 @@ export declare class GIFEncoder {
25
25
  private quantize;
26
26
  private nextPowerOf2;
27
27
  private getBitsPerColor;
28
- encode(): Uint8Array;
28
+ encode(options?: {
29
+ loop?: number;
30
+ }): Uint8Array;
29
31
  }
30
32
  //# sourceMappingURL=gif_encoder.d.ts.map
@@ -165,11 +165,13 @@ export class GIFEncoder {
165
165
  }
166
166
  return Math.max(2, bits);
167
167
  }
168
- encode() {
168
+ encode(options) {
169
169
  if (this.frames.length === 0) {
170
170
  throw new Error("No frames to encode");
171
171
  }
172
172
  const output = [];
173
+ // Get loop count from options (default to 0 = infinite)
174
+ const loopCount = options?.loop ?? 0;
173
175
  // Quantize first frame for Global Color Table
174
176
  const firstFrame = this.frames[0];
175
177
  const { palette: globalPalette, indexed: firstIndexed } = this.quantize(firstFrame.data);
@@ -206,7 +208,7 @@ export class GIFEncoder {
206
208
  this.writeString(output, "NETSCAPE2.0");
207
209
  output.push(3); // Sub-block Size
208
210
  output.push(1); // Loop Indicator (1 = loop)
209
- this.writeUint16LE(output, 0); // Loop Count (0 = infinite)
211
+ this.writeUint16LE(output, loopCount); // Loop Count (0 = infinite, 1+ = specific count)
210
212
  output.push(0); // Block Terminator
211
213
  }
212
214
  // Encode frames
@@ -186,4 +186,35 @@ export declare function flipHorizontal(data: Uint8Array, width: number, height:
186
186
  * @returns Flipped image data
187
187
  */
188
188
  export declare function flipVertical(data: Uint8Array, width: number, height: number): Uint8Array;
189
+ /**
190
+ * Convert RGB color to CMYK color space
191
+ * @param r Red component (0-255)
192
+ * @param g Green component (0-255)
193
+ * @param b Blue component (0-255)
194
+ * @returns CMYK values: [c (0-1), m (0-1), y (0-1), k (0-1)]
195
+ */
196
+ export declare function rgbToCmyk(r: number, g: number, b: number): [number, number, number, number];
197
+ /**
198
+ * Convert CMYK color to RGB color space
199
+ * @param c Cyan component (0-1)
200
+ * @param m Magenta component (0-1)
201
+ * @param y Yellow component (0-1)
202
+ * @param k Key/Black component (0-1)
203
+ * @returns RGB values: [r (0-255), g (0-255), b (0-255)]
204
+ */
205
+ export declare function cmykToRgb(c: number, m: number, y: number, k: number): [number, number, number];
206
+ /**
207
+ * Convert RGBA image data to CMYK representation
208
+ * Returns a Float32Array with 4 values per pixel (C, M, Y, K) in 0-1 range
209
+ * @param data Image data (RGBA)
210
+ * @returns CMYK image data as Float32Array (4 values per pixel)
211
+ */
212
+ export declare function rgbaToCmyk(data: Uint8Array): Float32Array;
213
+ /**
214
+ * Convert CMYK image data to RGBA representation
215
+ * @param cmykData CMYK image data (4 values per pixel in 0-1 range)
216
+ * @param alpha Optional alpha value for all pixels (0-255, default: 255)
217
+ * @returns RGBA image data
218
+ */
219
+ export declare function cmykToRgba(cmykData: Float32Array, alpha?: number): Uint8Array;
189
220
  //# sourceMappingURL=image_processing.d.ts.map
@@ -743,3 +743,91 @@ export function flipVertical(data, width, height) {
743
743
  }
744
744
  return result;
745
745
  }
746
+ /**
747
+ * Convert RGB color to CMYK color space
748
+ * @param r Red component (0-255)
749
+ * @param g Green component (0-255)
750
+ * @param b Blue component (0-255)
751
+ * @returns CMYK values: [c (0-1), m (0-1), y (0-1), k (0-1)]
752
+ */
753
+ export function rgbToCmyk(r, g, b) {
754
+ // Normalize RGB values to 0-1 range
755
+ const rNorm = r / 255;
756
+ const gNorm = g / 255;
757
+ const bNorm = b / 255;
758
+ // Calculate K (key/black)
759
+ const k = 1 - Math.max(rNorm, gNorm, bNorm);
760
+ // If K is 1 (pure black), CMY are undefined but conventionally set to 0
761
+ if (k === 1) {
762
+ return [0, 0, 0, 1];
763
+ }
764
+ // Calculate CMY components
765
+ const c = (1 - rNorm - k) / (1 - k);
766
+ const m = (1 - gNorm - k) / (1 - k);
767
+ const y = (1 - bNorm - k) / (1 - k);
768
+ return [c, m, y, k];
769
+ }
770
+ /**
771
+ * Convert CMYK color to RGB color space
772
+ * @param c Cyan component (0-1)
773
+ * @param m Magenta component (0-1)
774
+ * @param y Yellow component (0-1)
775
+ * @param k Key/Black component (0-1)
776
+ * @returns RGB values: [r (0-255), g (0-255), b (0-255)]
777
+ */
778
+ export function cmykToRgb(c, m, y, k) {
779
+ // Convert CMYK to RGB
780
+ const r = 255 * (1 - c) * (1 - k);
781
+ const g = 255 * (1 - m) * (1 - k);
782
+ const b = 255 * (1 - y) * (1 - k);
783
+ return [
784
+ Math.round(Math.max(0, Math.min(255, r))),
785
+ Math.round(Math.max(0, Math.min(255, g))),
786
+ Math.round(Math.max(0, Math.min(255, b))),
787
+ ];
788
+ }
789
+ /**
790
+ * Convert RGBA image data to CMYK representation
791
+ * Returns a Float32Array with 4 values per pixel (C, M, Y, K) in 0-1 range
792
+ * @param data Image data (RGBA)
793
+ * @returns CMYK image data as Float32Array (4 values per pixel)
794
+ */
795
+ export function rgbaToCmyk(data) {
796
+ const pixelCount = data.length / 4;
797
+ const result = new Float32Array(pixelCount * 4);
798
+ for (let i = 0; i < data.length; i += 4) {
799
+ const r = data[i];
800
+ const g = data[i + 1];
801
+ const b = data[i + 2];
802
+ const [c, m, y, k] = rgbToCmyk(r, g, b);
803
+ const outIdx = (i / 4) * 4;
804
+ result[outIdx] = c;
805
+ result[outIdx + 1] = m;
806
+ result[outIdx + 2] = y;
807
+ result[outIdx + 3] = k;
808
+ }
809
+ return result;
810
+ }
811
+ /**
812
+ * Convert CMYK image data to RGBA representation
813
+ * @param cmykData CMYK image data (4 values per pixel in 0-1 range)
814
+ * @param alpha Optional alpha value for all pixels (0-255, default: 255)
815
+ * @returns RGBA image data
816
+ */
817
+ export function cmykToRgba(cmykData, alpha = 255) {
818
+ const pixelCount = cmykData.length / 4;
819
+ const result = new Uint8Array(pixelCount * 4);
820
+ for (let i = 0; i < cmykData.length; i += 4) {
821
+ const c = cmykData[i];
822
+ const m = cmykData[i + 1];
823
+ const y = cmykData[i + 2];
824
+ const k = cmykData[i + 3];
825
+ const [r, g, b] = cmykToRgb(c, m, y, k);
826
+ const outIdx = i;
827
+ result[outIdx] = r;
828
+ result[outIdx + 1] = g;
829
+ result[outIdx + 2] = b;
830
+ result[outIdx + 3] = alpha;
831
+ }
832
+ return result;
833
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cross-image",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "A pure JavaScript, dependency-free, cross-runtime image processing library for Deno, Node.js, and Bun.",
5
5
  "keywords": [
6
6
  "image",
package/script/mod.d.ts CHANGED
@@ -43,7 +43,7 @@
43
43
  * ```
44
44
  */
45
45
  export { Image } from "./src/image.js";
46
- export type { ASCIIEncoderOptions, FrameMetadata, ImageData, ImageDecoderOptions, ImageFormat, ImageFrame, ImageMetadata, JPEGEncoderOptions, MultiFrameImageData, ResizeOptions, TIFFEncoderOptions, WebPEncoderOptions, } 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";
@@ -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/script/mod.js CHANGED
@@ -44,7 +44,7 @@
44
44
  * ```
45
45
  */
46
46
  Object.defineProperty(exports, "__esModule", { value: true });
47
- exports.AVIFFormat = exports.HEICFormat = exports.ASCIIFormat = exports.PPMFormat = exports.PCXFormat = exports.PAMFormat = exports.DNGFormat = exports.ICOFormat = exports.BMPFormat = exports.TIFFFormat = exports.GIFFormat = exports.WebPFormat = exports.JPEGFormat = exports.APNGFormat = exports.PNGFormat = exports.Image = void 0;
47
+ exports.rgbToCmyk = exports.rgbaToCmyk = exports.cmykToRgba = exports.cmykToRgb = exports.toDataUrl = exports.parseDataUrl = exports.encodeBase64 = exports.decodeBase64 = exports.AVIFFormat = exports.HEICFormat = exports.ASCIIFormat = exports.PPMFormat = exports.PCXFormat = exports.PAMFormat = exports.DNGFormat = exports.ICOFormat = exports.BMPFormat = exports.TIFFFormat = exports.GIFFormat = exports.WebPFormat = exports.JPEGFormat = exports.APNGFormat = exports.PNGFormat = exports.Image = void 0;
48
48
  var image_js_1 = require("./src/image.js");
49
49
  Object.defineProperty(exports, "Image", { enumerable: true, get: function () { return image_js_1.Image; } });
50
50
  var png_js_1 = require("./src/formats/png.js");
@@ -77,3 +77,13 @@ var heic_js_1 = require("./src/formats/heic.js");
77
77
  Object.defineProperty(exports, "HEICFormat", { enumerable: true, get: function () { return heic_js_1.HEICFormat; } });
78
78
  var avif_js_1 = require("./src/formats/avif.js");
79
79
  Object.defineProperty(exports, "AVIFFormat", { enumerable: true, get: function () { return avif_js_1.AVIFFormat; } });
80
+ var base64_js_1 = require("./src/utils/base64.js");
81
+ Object.defineProperty(exports, "decodeBase64", { enumerable: true, get: function () { return base64_js_1.decodeBase64; } });
82
+ Object.defineProperty(exports, "encodeBase64", { enumerable: true, get: function () { return base64_js_1.encodeBase64; } });
83
+ Object.defineProperty(exports, "parseDataUrl", { enumerable: true, get: function () { return base64_js_1.parseDataUrl; } });
84
+ Object.defineProperty(exports, "toDataUrl", { enumerable: true, get: function () { return base64_js_1.toDataUrl; } });
85
+ var image_processing_js_1 = require("./src/utils/image_processing.js");
86
+ Object.defineProperty(exports, "cmykToRgb", { enumerable: true, get: function () { return image_processing_js_1.cmykToRgb; } });
87
+ Object.defineProperty(exports, "cmykToRgba", { enumerable: true, get: function () { return image_processing_js_1.cmykToRgba; } });
88
+ Object.defineProperty(exports, "rgbaToCmyk", { enumerable: true, get: function () { return image_processing_js_1.rgbaToCmyk; } });
89
+ Object.defineProperty(exports, "rgbToCmyk", { enumerable: true, get: function () { return image_processing_js_1.rgbToCmyk; } });
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageDecoderOptions, 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
@@ -36,15 +36,17 @@ export declare class APNGFormat extends PNGBase implements ImageFormat {
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, _options?: unknown): 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, _options?: unknown): 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
@@ -253,9 +253,10 @@ class APNGFormat extends png_base_js_1.PNGBase {
253
253
  /**
254
254
  * Encode RGBA image data to APNG format (single frame)
255
255
  * @param imageData Image data to encode
256
+ * @param options Encoding options (compressionLevel 0-9, default 6)
256
257
  * @returns Encoded APNG image bytes
257
258
  */
258
- encode(imageData, _options) {
259
+ encode(imageData, options) {
259
260
  // For single frame, create a multi-frame with one frame
260
261
  const multiFrame = {
261
262
  width: imageData.width,
@@ -268,15 +269,21 @@ class APNGFormat extends png_base_js_1.PNGBase {
268
269
  }],
269
270
  metadata: imageData.metadata,
270
271
  };
271
- return this.encodeFrames(multiFrame, _options);
272
+ return this.encodeFrames(multiFrame, options);
272
273
  }
273
274
  /**
274
275
  * Encode multi-frame image data to APNG format
275
276
  * @param imageData Multi-frame image data to encode
277
+ * @param options Encoding options (compressionLevel 0-9, default 6)
276
278
  * @returns Encoded APNG image bytes
277
279
  */
278
- async encodeFrames(imageData, _options) {
280
+ async encodeFrames(imageData, options) {
279
281
  const { width, height, frames, metadata } = imageData;
282
+ const compressionLevel = options?.compressionLevel ?? 6;
283
+ // Validate compression level
284
+ if (compressionLevel < 0 || compressionLevel > 9) {
285
+ throw new Error("Compression level must be between 0 and 9");
286
+ }
280
287
  if (frames.length === 0) {
281
288
  throw new Error("No frames to encode");
282
289
  }
@@ -334,7 +341,7 @@ class APNGFormat extends png_base_js_1.PNGBase {
334
341
  fctl[25] = 0; // blend_op: APNG_BLEND_OP_SOURCE
335
342
  chunks.push(this.createChunk("fcTL", fctl));
336
343
  // Filter and compress frame data
337
- const filtered = this.filterData(frame.data, frame.width, frame.height);
344
+ const filtered = this.filterData(frame.data, frame.width, frame.height, compressionLevel);
338
345
  const compressed = await this.deflate(filtered);
339
346
  if (i === 0) {
340
347
  // First frame uses IDAT
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageDecoderOptions, 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)
@@ -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, _options?: unknown): 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
@@ -82,8 +82,9 @@ class AVIFFormat {
82
82
  * @param imageData Image data to encode
83
83
  * @returns Encoded AVIF image bytes
84
84
  */
85
- async encode(imageData, _options) {
85
+ async encode(imageData, options) {
86
86
  const { width, height, data, metadata: _metadata } = imageData;
87
+ const requestedQuality = options?.quality;
87
88
  // Try to use runtime encoding if available
88
89
  if (typeof OffscreenCanvas !== "undefined") {
89
90
  try {
@@ -94,10 +95,19 @@ class AVIFFormat {
94
95
  const imgDataData = new Uint8ClampedArray(data);
95
96
  imgData.data.set(imgDataData);
96
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);
97
103
  // Try to encode as AVIF
98
104
  const blob = await canvas.convertToBlob({
99
105
  type: "image/avif",
106
+ ...(quality === undefined ? {} : { quality }),
100
107
  });
108
+ if (blob.type !== "image/avif") {
109
+ throw new Error(`Runtime did not encode AVIF (got '${blob.type || "(empty)"}')`);
110
+ }
101
111
  const arrayBuffer = await blob.arrayBuffer();
102
112
  const encoded = new Uint8Array(arrayBuffer);
103
113
  // Note: Metadata injection for AVIF is complex and would require
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageDecoderOptions, 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
@@ -39,7 +39,7 @@ export declare class GIFFormat implements ImageFormat {
39
39
  * @param imageData Image data to encode
40
40
  * @returns Encoded GIF image bytes
41
41
  */
42
- encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
42
+ encode(imageData: ImageData, options?: GIFEncoderOptions): Promise<Uint8Array>;
43
43
  /**
44
44
  * Decode all frames from an animated GIF
45
45
  */
@@ -47,7 +47,7 @@ export declare class GIFFormat implements ImageFormat {
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;
@@ -156,12 +156,12 @@ class GIFFormat {
156
156
  * @param imageData Image data to encode
157
157
  * @returns Encoded GIF image bytes
158
158
  */
159
- async encode(imageData, _options) {
159
+ async encode(imageData, options) {
160
160
  const { width, height, data, metadata } = imageData;
161
161
  // Try pure-JS encoder first
162
162
  try {
163
163
  const encoder = new gif_encoder_js_1.GIFEncoder(width, height, data);
164
- const encoded = encoder.encode();
164
+ const encoded = encoder.encode(options);
165
165
  // Inject metadata if present
166
166
  if (metadata && Object.keys(metadata).length > 0) {
167
167
  const injected = this.injectMetadata(encoded, metadata);
@@ -240,7 +240,7 @@ class GIFFormat {
240
240
  /**
241
241
  * Encode multi-frame image data to animated GIF
242
242
  */
243
- encodeFrames(imageData, _options) {
243
+ encodeFrames(imageData, options) {
244
244
  if (imageData.frames.length === 0) {
245
245
  throw new Error("No frames to encode");
246
246
  }
@@ -250,7 +250,7 @@ class GIFFormat {
250
250
  const delay = frame.frameMetadata?.delay ?? 100;
251
251
  encoder.addFrame(frame.data, delay);
252
252
  }
253
- return Promise.resolve(encoder.encode());
253
+ return Promise.resolve(encoder.encode(options));
254
254
  }
255
255
  mapDisposalMethod(disposal) {
256
256
  switch (disposal) {
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageDecoderOptions, 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)
@@ -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, _options?: unknown): 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
@@ -83,8 +83,9 @@ class HEICFormat {
83
83
  * @param imageData Image data to encode
84
84
  * @returns Encoded HEIC image bytes
85
85
  */
86
- async encode(imageData, _options) {
86
+ async encode(imageData, options) {
87
87
  const { width, height, data, metadata: _metadata } = imageData;
88
+ const requestedQuality = options?.quality;
88
89
  // Try to use runtime encoding if available
89
90
  if (typeof OffscreenCanvas !== "undefined") {
90
91
  try {
@@ -95,10 +96,19 @@ class HEICFormat {
95
96
  const imgDataData = new Uint8ClampedArray(data);
96
97
  imgData.data.set(imgDataData);
97
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);
98
104
  // Try to encode as HEIC
99
105
  const blob = await canvas.convertToBlob({
100
106
  type: "image/heic",
107
+ ...(quality === undefined ? {} : { quality }),
101
108
  });
109
+ if (blob.type !== "image/heic") {
110
+ throw new Error(`Runtime did not encode HEIC (got '${blob.type || "(empty)"}')`);
111
+ }
102
112
  const arrayBuffer = await blob.arrayBuffer();
103
113
  const encoded = new Uint8Array(arrayBuffer);
104
114
  // Note: Metadata injection for HEIC is complex and would require
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageDecoderOptions, 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
@@ -24,9 +24,10 @@ export declare class PNGFormat extends PNGBase implements ImageFormat {
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, _options?: unknown): 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
@@ -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, _options) {
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 = [];