cross-image 0.2.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +615 -333
- package/esm/mod.d.ts +6 -4
- package/esm/mod.js +4 -2
- package/esm/src/formats/apng.d.ts +7 -5
- package/esm/src/formats/apng.js +15 -10
- package/esm/src/formats/ascii.d.ts +3 -3
- package/esm/src/formats/ascii.js +1 -1
- package/esm/src/formats/avif.d.ts +3 -3
- package/esm/src/formats/avif.js +17 -7
- package/esm/src/formats/bmp.d.ts +3 -3
- package/esm/src/formats/bmp.js +2 -2
- package/esm/src/formats/dng.d.ts +1 -1
- package/esm/src/formats/dng.js +1 -1
- package/esm/src/formats/gif.d.ts +5 -5
- package/esm/src/formats/gif.js +17 -13
- package/esm/src/formats/heic.d.ts +3 -3
- package/esm/src/formats/heic.js +17 -7
- package/esm/src/formats/ico.d.ts +3 -3
- package/esm/src/formats/ico.js +4 -4
- package/esm/src/formats/jpeg.d.ts +3 -3
- package/esm/src/formats/jpeg.js +23 -11
- package/esm/src/formats/pam.d.ts +3 -3
- package/esm/src/formats/pam.js +2 -2
- package/esm/src/formats/pcx.d.ts +3 -3
- package/esm/src/formats/pcx.js +2 -2
- package/esm/src/formats/png.d.ts +4 -3
- package/esm/src/formats/png.js +9 -3
- package/esm/src/formats/png_base.d.ts +42 -1
- package/esm/src/formats/png_base.js +200 -10
- package/esm/src/formats/ppm.d.ts +3 -3
- package/esm/src/formats/ppm.js +2 -2
- package/esm/src/formats/tiff.d.ts +7 -18
- package/esm/src/formats/tiff.js +162 -27
- package/esm/src/formats/webp.d.ts +3 -3
- package/esm/src/formats/webp.js +11 -8
- package/esm/src/image.d.ts +26 -3
- package/esm/src/image.js +66 -22
- package/esm/src/types.d.ts +122 -4
- package/esm/src/utils/base64.d.ts +32 -0
- package/esm/src/utils/base64.js +173 -0
- package/esm/src/utils/gif_decoder.d.ts +4 -1
- package/esm/src/utils/gif_decoder.js +91 -65
- package/esm/src/utils/gif_encoder.d.ts +3 -1
- package/esm/src/utils/gif_encoder.js +4 -2
- package/esm/src/utils/image_processing.d.ts +31 -0
- package/esm/src/utils/image_processing.js +232 -70
- package/esm/src/utils/jpeg_decoder.d.ts +17 -4
- package/esm/src/utils/jpeg_decoder.js +448 -83
- package/esm/src/utils/jpeg_encoder.d.ts +15 -1
- package/esm/src/utils/jpeg_encoder.js +263 -24
- package/esm/src/utils/resize.js +51 -20
- package/esm/src/utils/tiff_deflate.d.ts +18 -0
- package/esm/src/utils/tiff_deflate.js +27 -0
- package/esm/src/utils/tiff_packbits.d.ts +24 -0
- package/esm/src/utils/tiff_packbits.js +90 -0
- package/esm/src/utils/webp_decoder.d.ts +3 -1
- package/esm/src/utils/webp_decoder.js +144 -63
- package/esm/src/utils/webp_encoder.js +5 -11
- package/package.json +1 -1
- package/script/mod.d.ts +6 -4
- package/script/mod.js +13 -3
- package/script/src/formats/apng.d.ts +7 -5
- package/script/src/formats/apng.js +15 -10
- package/script/src/formats/ascii.d.ts +3 -3
- package/script/src/formats/ascii.js +1 -1
- package/script/src/formats/avif.d.ts +3 -3
- package/script/src/formats/avif.js +17 -7
- package/script/src/formats/bmp.d.ts +3 -3
- package/script/src/formats/bmp.js +2 -2
- package/script/src/formats/dng.d.ts +1 -1
- package/script/src/formats/dng.js +1 -1
- package/script/src/formats/gif.d.ts +5 -5
- package/script/src/formats/gif.js +17 -13
- package/script/src/formats/heic.d.ts +3 -3
- package/script/src/formats/heic.js +17 -7
- package/script/src/formats/ico.d.ts +3 -3
- package/script/src/formats/ico.js +4 -4
- package/script/src/formats/jpeg.d.ts +3 -3
- package/script/src/formats/jpeg.js +23 -11
- package/script/src/formats/pam.d.ts +3 -3
- package/script/src/formats/pam.js +2 -2
- package/script/src/formats/pcx.d.ts +3 -3
- package/script/src/formats/pcx.js +2 -2
- package/script/src/formats/png.d.ts +4 -3
- package/script/src/formats/png.js +9 -3
- package/script/src/formats/png_base.d.ts +42 -1
- package/script/src/formats/png_base.js +200 -10
- package/script/src/formats/ppm.d.ts +3 -3
- package/script/src/formats/ppm.js +2 -2
- package/script/src/formats/tiff.d.ts +7 -18
- package/script/src/formats/tiff.js +162 -27
- package/script/src/formats/webp.d.ts +3 -3
- package/script/src/formats/webp.js +11 -8
- package/script/src/image.d.ts +26 -3
- package/script/src/image.js +64 -20
- package/script/src/types.d.ts +122 -4
- package/script/src/utils/base64.d.ts +32 -0
- package/script/src/utils/base64.js +179 -0
- package/script/src/utils/gif_decoder.d.ts +4 -1
- package/script/src/utils/gif_decoder.js +91 -65
- package/script/src/utils/gif_encoder.d.ts +3 -1
- package/script/src/utils/gif_encoder.js +4 -2
- package/script/src/utils/image_processing.d.ts +31 -0
- package/script/src/utils/image_processing.js +236 -70
- package/script/src/utils/jpeg_decoder.d.ts +17 -4
- package/script/src/utils/jpeg_decoder.js +448 -83
- package/script/src/utils/jpeg_encoder.d.ts +15 -1
- package/script/src/utils/jpeg_encoder.js +263 -24
- package/script/src/utils/resize.js +51 -20
- package/script/src/utils/tiff_deflate.d.ts +18 -0
- package/script/src/utils/tiff_deflate.js +31 -0
- package/script/src/utils/tiff_packbits.d.ts +24 -0
- package/script/src/utils/tiff_packbits.js +94 -0
- package/script/src/utils/webp_decoder.d.ts +3 -1
- package/script/src/utils/webp_decoder.js +144 -63
- package/script/src/utils/webp_encoder.js +5 -11
package/esm/mod.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @example
|
|
9
9
|
* ```ts
|
|
10
|
-
* import { Image } from "
|
|
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 "
|
|
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 {
|
|
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 {
|
|
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 "
|
|
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 "
|
|
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
|
package/esm/src/formats/apng.js
CHANGED
|
@@ -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 {
|
|
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?:
|
|
32
|
+
encode(imageData: ImageData, options?: ASCIIEncoderOptions): Promise<Uint8Array>;
|
|
33
33
|
/**
|
|
34
34
|
* Parse options from the options line
|
|
35
35
|
*/
|
package/esm/src/formats/ascii.js
CHANGED
|
@@ -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
|
package/esm/src/formats/avif.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/esm/src/formats/bmp.d.ts
CHANGED
|
@@ -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
|
*/
|
package/esm/src/formats/bmp.js
CHANGED
|
@@ -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
|
package/esm/src/formats/dng.d.ts
CHANGED
|
@@ -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
|
package/esm/src/formats/dng.js
CHANGED
|
@@ -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.
|
package/esm/src/formats/gif.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageData, ImageFormat, ImageMetadata, MultiFrameImageData } from "../types.js";
|
|
1
|
+
import type { GIFEncoderOptions, ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, MultiFrameImageData } from "../types.js";
|
|
2
2
|
/**
|
|
3
3
|
* GIF format handler
|
|
4
4
|
* Now includes pure-JS implementation with custom LZW compression/decompression
|
|
@@ -32,22 +32,22 @@ export declare class GIFFormat implements ImageFormat {
|
|
|
32
32
|
* @param data Raw GIF image data
|
|
33
33
|
* @returns Decoded image data with RGBA pixels of first frame
|
|
34
34
|
*/
|
|
35
|
-
decode(data: Uint8Array): Promise<ImageData>;
|
|
35
|
+
decode(data: Uint8Array, settings?: ImageDecoderOptions): Promise<ImageData>;
|
|
36
36
|
private extractGIFMetadata;
|
|
37
37
|
/**
|
|
38
38
|
* Encode RGBA image data to GIF format (single frame)
|
|
39
39
|
* @param imageData Image data to encode
|
|
40
40
|
* @returns Encoded GIF image bytes
|
|
41
41
|
*/
|
|
42
|
-
encode(imageData: ImageData): Promise<Uint8Array>;
|
|
42
|
+
encode(imageData: ImageData, options?: GIFEncoderOptions): Promise<Uint8Array>;
|
|
43
43
|
/**
|
|
44
44
|
* Decode all frames from an animated GIF
|
|
45
45
|
*/
|
|
46
|
-
decodeFrames(data: Uint8Array): Promise<MultiFrameImageData>;
|
|
46
|
+
decodeFrames(data: Uint8Array, settings?: ImageDecoderOptions): Promise<MultiFrameImageData>;
|
|
47
47
|
/**
|
|
48
48
|
* Encode multi-frame image data to animated GIF
|
|
49
49
|
*/
|
|
50
|
-
encodeFrames(imageData: MultiFrameImageData,
|
|
50
|
+
encodeFrames(imageData: MultiFrameImageData, options?: GIFEncoderOptions): Promise<Uint8Array>;
|
|
51
51
|
private mapDisposalMethod;
|
|
52
52
|
private decodeUsingRuntime;
|
|
53
53
|
private readDataSubBlocks;
|
package/esm/src/formats/gif.js
CHANGED
|
@@ -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 (
|
|
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 (
|
|
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,
|
|
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
|
package/esm/src/formats/heic.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/esm/src/formats/ico.d.ts
CHANGED
|
@@ -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
|
*/
|
package/esm/src/formats/ico.js
CHANGED
|
@@ -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;
|