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.
- package/README.md +109 -1
- package/esm/mod.d.ts +3 -1
- package/esm/mod.js +2 -0
- package/esm/src/formats/apng.d.ts +5 -3
- package/esm/src/formats/apng.js +11 -4
- package/esm/src/formats/avif.d.ts +2 -2
- package/esm/src/formats/avif.js +11 -1
- package/esm/src/formats/gif.d.ts +3 -3
- package/esm/src/formats/gif.js +4 -4
- package/esm/src/formats/heic.d.ts +2 -2
- package/esm/src/formats/heic.js +11 -1
- package/esm/src/formats/png.d.ts +3 -2
- package/esm/src/formats/png.js +8 -2
- package/esm/src/formats/png_base.d.ts +42 -1
- package/esm/src/formats/png_base.js +198 -5
- package/esm/src/formats/tiff.js +76 -6
- package/esm/src/image.d.ts +15 -0
- package/esm/src/image.js +29 -1
- package/esm/src/types.d.ts +66 -0
- package/esm/src/utils/base64.d.ts +32 -0
- package/esm/src/utils/base64.js +173 -0
- 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 +88 -0
- package/package.json +1 -1
- package/script/mod.d.ts +3 -1
- package/script/mod.js +11 -1
- package/script/src/formats/apng.d.ts +5 -3
- package/script/src/formats/apng.js +11 -4
- package/script/src/formats/avif.d.ts +2 -2
- package/script/src/formats/avif.js +11 -1
- package/script/src/formats/gif.d.ts +3 -3
- package/script/src/formats/gif.js +4 -4
- package/script/src/formats/heic.d.ts +2 -2
- package/script/src/formats/heic.js +11 -1
- package/script/src/formats/png.d.ts +3 -2
- package/script/src/formats/png.js +8 -2
- package/script/src/formats/png_base.d.ts +42 -1
- package/script/src/formats/png_base.js +198 -5
- package/script/src/formats/tiff.js +76 -6
- package/script/src/image.d.ts +15 -0
- package/script/src/image.js +28 -0
- package/script/src/types.d.ts +66 -0
- package/script/src/utils/base64.d.ts +32 -0
- package/script/src/utils/base64.js +179 -0
- 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 +92 -0
package/README.md
CHANGED
|
@@ -196,6 +196,112 @@ const imageWithWarnings = await Image.decode(data, {
|
|
|
196
196
|
```
|
|
197
197
|
|
|
198
198
|
**Note:** When using `Image.decode()`, the library automatically tries runtime-optimized decoders
|
|
199
|
+
|
|
200
|
+
## CMYK Color Space Support
|
|
201
|
+
|
|
202
|
+
The library provides utilities for working with CMYK (Cyan, Magenta, Yellow, Key/Black) color space,
|
|
203
|
+
commonly used in professional printing and color manipulation.
|
|
204
|
+
|
|
205
|
+
### Color Conversion Utilities
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
import { cmykToRgb, rgbToCmyk } from "jsr:@cross/image";
|
|
209
|
+
|
|
210
|
+
// Convert RGB to CMYK
|
|
211
|
+
const [c, m, y, k] = rgbToCmyk(255, 0, 0); // Red
|
|
212
|
+
console.log({ c, m, y, k }); // { c: 0, m: 1, y: 1, k: 0 }
|
|
213
|
+
|
|
214
|
+
// Convert CMYK back to RGB
|
|
215
|
+
const [r, g, b] = cmykToRgb(c, m, y, k);
|
|
216
|
+
console.log({ r, g, b }); // { r: 255, g: 0, b: 0 }
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Image-Level CMYK Operations
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import { Image } from "jsr:@cross/image";
|
|
223
|
+
|
|
224
|
+
// Load an image and convert to CMYK
|
|
225
|
+
const data = await Deno.readFile("photo.jpg");
|
|
226
|
+
const image = await Image.decode(data);
|
|
227
|
+
|
|
228
|
+
// Get CMYK representation (Float32Array with 4 values per pixel)
|
|
229
|
+
const cmykData = image.toCMYK();
|
|
230
|
+
|
|
231
|
+
// Create an image from CMYK data
|
|
232
|
+
const restored = Image.fromCMYK(cmykData, image.width, image.height);
|
|
233
|
+
|
|
234
|
+
// Save the restored image
|
|
235
|
+
await Deno.writeFile("output.png", await restored.encode("png"));
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Batch Conversion
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
import { cmykToRgba, rgbaToCmyk } from "jsr:@cross/image";
|
|
242
|
+
|
|
243
|
+
// Convert entire image data to CMYK
|
|
244
|
+
const rgbaData = new Uint8Array([255, 0, 0, 255]); // Red pixel
|
|
245
|
+
const cmykData = rgbaToCmyk(rgbaData);
|
|
246
|
+
|
|
247
|
+
// Convert CMYK data back to RGBA
|
|
248
|
+
const rgbaRestored = cmykToRgba(cmykData);
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Use Cases:**
|
|
252
|
+
|
|
253
|
+
- Pre-press and print preparation workflows
|
|
254
|
+
- Color space conversion for professional printing
|
|
255
|
+
- Color analysis and manipulation in CMYK space
|
|
256
|
+
- Educational tools for understanding color models
|
|
257
|
+
|
|
258
|
+
### CMYK TIFF Support
|
|
259
|
+
|
|
260
|
+
TIFF format has native support for CMYK images:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
import { Image } from "jsr:@cross/image";
|
|
264
|
+
|
|
265
|
+
// Decode CMYK TIFF (automatically converted to RGBA)
|
|
266
|
+
const cmykTiff = await Deno.readFile("cmyk-image.tif");
|
|
267
|
+
const image = await Image.decode(cmykTiff); // Automatic CMYK → RGBA conversion
|
|
268
|
+
|
|
269
|
+
// Encode image as CMYK TIFF
|
|
270
|
+
const output = await image.encode("tiff", { cmyk: true });
|
|
271
|
+
await Deno.writeFile("output-cmyk.tif", output);
|
|
272
|
+
|
|
273
|
+
// CMYK works with all TIFF compression methods
|
|
274
|
+
const compressed = await image.encode("tiff", {
|
|
275
|
+
cmyk: true,
|
|
276
|
+
compression: "lzw", // or "packbits", "deflate", "none"
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Benefits:**
|
|
281
|
+
|
|
282
|
+
- **Seamless workflow**: CMYK TIFFs decode automatically, no special handling needed
|
|
283
|
+
- **Print-ready output**: Generate CMYK TIFFs for professional printing workflows
|
|
284
|
+
- **Full compression support**: CMYK works with all TIFF compression methods
|
|
285
|
+
- **Industry standard**: TIFF is the preferred format for CMYK images in print production
|
|
286
|
+
|
|
287
|
+
## Base64 / Data URLs
|
|
288
|
+
|
|
289
|
+
The library includes small utilities for working with base64 and `data:` URLs.
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
import { Image, parseDataUrl, toDataUrl } from "jsr:@cross/image";
|
|
293
|
+
|
|
294
|
+
const image = Image.create(2, 2, 255, 0, 0);
|
|
295
|
+
const pngBytes = await image.encode("png");
|
|
296
|
+
|
|
297
|
+
const dataUrl = toDataUrl("image/png", pngBytes);
|
|
298
|
+
const parsed = parseDataUrl(dataUrl);
|
|
299
|
+
|
|
300
|
+
// parsed.bytes is a Uint8Array containing the PNG
|
|
301
|
+
const roundtrip = await Image.decode(parsed.bytes, "png");
|
|
302
|
+
console.log(roundtrip.width, roundtrip.height);
|
|
303
|
+
```
|
|
304
|
+
|
|
199
305
|
(ImageDecoder API) first, falling back to the pure JS decoder with tolerant mode for maximum
|
|
200
306
|
compatibility.
|
|
201
307
|
|
|
@@ -394,7 +500,7 @@ const jpegSupports = Image.getSupportedMetadata("jpeg");
|
|
|
394
500
|
console.log(jpegSupports); // Includes ISO, camera info, GPS, etc.
|
|
395
501
|
|
|
396
502
|
// Save with metadata
|
|
397
|
-
const jpeg = await image.
|
|
503
|
+
const jpeg = await image.encode("jpeg");
|
|
398
504
|
await Deno.writeFile("output.jpg", jpeg);
|
|
399
505
|
|
|
400
506
|
// Metadata is preserved on reload!
|
|
@@ -488,6 +594,8 @@ Image.getSupportedMetadata("avif"); // Full camera metadata + GPS (19 fields)
|
|
|
488
594
|
Technical details for WebP
|
|
489
595
|
- **[TIFF Implementation](https://cross-image.56k.guru/implementation/tiff-implementation/)** -
|
|
490
596
|
Technical details for TIFF
|
|
597
|
+
- **[GIF Implementation](https://cross-image.56k.guru/implementation/gif-implementation/)** -
|
|
598
|
+
Technical details for GIF
|
|
491
599
|
|
|
492
600
|
## Development
|
|
493
601
|
|
package/esm/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/esm/mod.js
CHANGED
|
@@ -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, 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,
|
|
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,
|
|
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
|
@@ -250,9 +250,10 @@ export class APNGFormat extends PNGBase {
|
|
|
250
250
|
/**
|
|
251
251
|
* Encode RGBA image data to APNG format (single frame)
|
|
252
252
|
* @param imageData Image data to encode
|
|
253
|
+
* @param options Encoding options (compressionLevel 0-9, default 6)
|
|
253
254
|
* @returns Encoded APNG image bytes
|
|
254
255
|
*/
|
|
255
|
-
encode(imageData,
|
|
256
|
+
encode(imageData, options) {
|
|
256
257
|
// For single frame, create a multi-frame with one frame
|
|
257
258
|
const multiFrame = {
|
|
258
259
|
width: imageData.width,
|
|
@@ -265,15 +266,21 @@ export class APNGFormat extends PNGBase {
|
|
|
265
266
|
}],
|
|
266
267
|
metadata: imageData.metadata,
|
|
267
268
|
};
|
|
268
|
-
return this.encodeFrames(multiFrame,
|
|
269
|
+
return this.encodeFrames(multiFrame, options);
|
|
269
270
|
}
|
|
270
271
|
/**
|
|
271
272
|
* Encode multi-frame image data to APNG format
|
|
272
273
|
* @param imageData Multi-frame image data to encode
|
|
274
|
+
* @param options Encoding options (compressionLevel 0-9, default 6)
|
|
273
275
|
* @returns Encoded APNG image bytes
|
|
274
276
|
*/
|
|
275
|
-
async encodeFrames(imageData,
|
|
277
|
+
async encodeFrames(imageData, options) {
|
|
276
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
|
+
}
|
|
277
284
|
if (frames.length === 0) {
|
|
278
285
|
throw new Error("No frames to encode");
|
|
279
286
|
}
|
|
@@ -331,7 +338,7 @@ export class APNGFormat extends PNGBase {
|
|
|
331
338
|
fctl[25] = 0; // blend_op: APNG_BLEND_OP_SOURCE
|
|
332
339
|
chunks.push(this.createChunk("fcTL", fctl));
|
|
333
340
|
// Filter and compress frame data
|
|
334
|
-
const filtered = this.filterData(frame.data, frame.width, frame.height);
|
|
341
|
+
const filtered = this.filterData(frame.data, frame.width, frame.height, compressionLevel);
|
|
335
342
|
const compressed = await this.deflate(filtered);
|
|
336
343
|
if (i === 0) {
|
|
337
344
|
// 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,
|
|
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
|
@@ -79,8 +79,9 @@ export class AVIFFormat {
|
|
|
79
79
|
* @param imageData Image data to encode
|
|
80
80
|
* @returns Encoded AVIF image bytes
|
|
81
81
|
*/
|
|
82
|
-
async encode(imageData,
|
|
82
|
+
async encode(imageData, options) {
|
|
83
83
|
const { width, height, data, metadata: _metadata } = imageData;
|
|
84
|
+
const requestedQuality = options?.quality;
|
|
84
85
|
// Try to use runtime encoding if available
|
|
85
86
|
if (typeof OffscreenCanvas !== "undefined") {
|
|
86
87
|
try {
|
|
@@ -91,10 +92,19 @@ export class AVIFFormat {
|
|
|
91
92
|
const imgDataData = new Uint8ClampedArray(data);
|
|
92
93
|
imgData.data.set(imgDataData);
|
|
93
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);
|
|
94
100
|
// Try to encode as AVIF
|
|
95
101
|
const blob = await canvas.convertToBlob({
|
|
96
102
|
type: "image/avif",
|
|
103
|
+
...(quality === undefined ? {} : { quality }),
|
|
97
104
|
});
|
|
105
|
+
if (blob.type !== "image/avif") {
|
|
106
|
+
throw new Error(`Runtime did not encode AVIF (got '${blob.type || "(empty)"}')`);
|
|
107
|
+
}
|
|
98
108
|
const arrayBuffer = await blob.arrayBuffer();
|
|
99
109
|
const encoded = new Uint8Array(arrayBuffer);
|
|
100
110
|
// Note: Metadata injection for AVIF is complex and would require
|
package/esm/src/formats/gif.d.ts
CHANGED
|
@@ -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,
|
|
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,
|
|
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
|
@@ -153,12 +153,12 @@ export class GIFFormat {
|
|
|
153
153
|
* @param imageData Image data to encode
|
|
154
154
|
* @returns Encoded GIF image bytes
|
|
155
155
|
*/
|
|
156
|
-
async encode(imageData,
|
|
156
|
+
async encode(imageData, options) {
|
|
157
157
|
const { width, height, data, metadata } = imageData;
|
|
158
158
|
// Try pure-JS encoder first
|
|
159
159
|
try {
|
|
160
160
|
const encoder = new GIFEncoder(width, height, data);
|
|
161
|
-
const encoded = encoder.encode();
|
|
161
|
+
const encoded = encoder.encode(options);
|
|
162
162
|
// Inject metadata if present
|
|
163
163
|
if (metadata && Object.keys(metadata).length > 0) {
|
|
164
164
|
const injected = this.injectMetadata(encoded, metadata);
|
|
@@ -237,7 +237,7 @@ export class GIFFormat {
|
|
|
237
237
|
/**
|
|
238
238
|
* Encode multi-frame image data to animated GIF
|
|
239
239
|
*/
|
|
240
|
-
encodeFrames(imageData,
|
|
240
|
+
encodeFrames(imageData, options) {
|
|
241
241
|
if (imageData.frames.length === 0) {
|
|
242
242
|
throw new Error("No frames to encode");
|
|
243
243
|
}
|
|
@@ -247,7 +247,7 @@ export class GIFFormat {
|
|
|
247
247
|
const delay = frame.frameMetadata?.delay ?? 100;
|
|
248
248
|
encoder.addFrame(frame.data, delay);
|
|
249
249
|
}
|
|
250
|
-
return Promise.resolve(encoder.encode());
|
|
250
|
+
return Promise.resolve(encoder.encode(options));
|
|
251
251
|
}
|
|
252
252
|
mapDisposalMethod(disposal) {
|
|
253
253
|
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,
|
|
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
|
@@ -80,8 +80,9 @@ export class HEICFormat {
|
|
|
80
80
|
* @param imageData Image data to encode
|
|
81
81
|
* @returns Encoded HEIC image bytes
|
|
82
82
|
*/
|
|
83
|
-
async encode(imageData,
|
|
83
|
+
async encode(imageData, options) {
|
|
84
84
|
const { width, height, data, metadata: _metadata } = imageData;
|
|
85
|
+
const requestedQuality = options?.quality;
|
|
85
86
|
// Try to use runtime encoding if available
|
|
86
87
|
if (typeof OffscreenCanvas !== "undefined") {
|
|
87
88
|
try {
|
|
@@ -92,10 +93,19 @@ export class HEICFormat {
|
|
|
92
93
|
const imgDataData = new Uint8ClampedArray(data);
|
|
93
94
|
imgData.data.set(imgDataData);
|
|
94
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);
|
|
95
101
|
// Try to encode as HEIC
|
|
96
102
|
const blob = await canvas.convertToBlob({
|
|
97
103
|
type: "image/heic",
|
|
104
|
+
...(quality === undefined ? {} : { quality }),
|
|
98
105
|
});
|
|
106
|
+
if (blob.type !== "image/heic") {
|
|
107
|
+
throw new Error(`Runtime did not encode HEIC (got '${blob.type || "(empty)"}')`);
|
|
108
|
+
}
|
|
99
109
|
const arrayBuffer = await blob.arrayBuffer();
|
|
100
110
|
const encoded = new Uint8Array(arrayBuffer);
|
|
101
111
|
// Note: Metadata injection for HEIC is complex and would require
|
package/esm/src/formats/png.d.ts
CHANGED
|
@@ -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,
|
|
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
|
package/esm/src/formats/png.js
CHANGED
|
@@ -110,10 +110,16 @@ export class PNGFormat extends PNGBase {
|
|
|
110
110
|
/**
|
|
111
111
|
* Encode RGBA image data to PNG format
|
|
112
112
|
* @param imageData Image data to encode
|
|
113
|
+
* @param options Encoding options (compressionLevel 0-9, default 6)
|
|
113
114
|
* @returns Encoded PNG image bytes
|
|
114
115
|
*/
|
|
115
|
-
async encode(imageData,
|
|
116
|
+
async encode(imageData, options) {
|
|
116
117
|
const { width, height, data, metadata } = imageData;
|
|
118
|
+
const compressionLevel = options?.compressionLevel ?? 6;
|
|
119
|
+
// Validate compression level
|
|
120
|
+
if (compressionLevel < 0 || compressionLevel > 9) {
|
|
121
|
+
throw new Error("Compression level must be between 0 and 9");
|
|
122
|
+
}
|
|
117
123
|
// Prepare IHDR chunk
|
|
118
124
|
const ihdr = new Uint8Array(13);
|
|
119
125
|
this.writeUint32(ihdr, 0, width);
|
|
@@ -124,7 +130,7 @@ export class PNGFormat extends PNGBase {
|
|
|
124
130
|
ihdr[11] = 0; // filter method
|
|
125
131
|
ihdr[12] = 0; // interlace method
|
|
126
132
|
// Filter and compress image data
|
|
127
|
-
const filtered = this.filterData(data, width, height);
|
|
133
|
+
const filtered = this.filterData(data, width, height, compressionLevel);
|
|
128
134
|
const compressed = await this.deflate(filtered);
|
|
129
135
|
// Build PNG
|
|
130
136
|
const chunks = [];
|
|
@@ -43,7 +43,48 @@ export declare abstract class PNGBase {
|
|
|
43
43
|
/**
|
|
44
44
|
* Filter PNG data for encoding (using filter type 0 - None)
|
|
45
45
|
*/
|
|
46
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Apply PNG filter to image data based on compression level
|
|
48
|
+
* @param data Raw RGBA pixel data
|
|
49
|
+
* @param width Image width
|
|
50
|
+
* @param height Image height
|
|
51
|
+
* @param compressionLevel Compression level (0-9, default 6)
|
|
52
|
+
* @returns Filtered data with filter type byte per scanline
|
|
53
|
+
*/
|
|
54
|
+
protected filterData(data: Uint8Array, width: number, height: number, compressionLevel?: number): Uint8Array;
|
|
55
|
+
/**
|
|
56
|
+
* Apply filter type 0 (None) - no filtering
|
|
57
|
+
*/
|
|
58
|
+
private applyNoFilter;
|
|
59
|
+
/**
|
|
60
|
+
* Apply filter type 1 (Sub) - subtract left pixel
|
|
61
|
+
*/
|
|
62
|
+
private applySubFilter;
|
|
63
|
+
/**
|
|
64
|
+
* Apply filter type 2 (Up) - subtract above pixel
|
|
65
|
+
*/
|
|
66
|
+
private applyUpFilter;
|
|
67
|
+
/**
|
|
68
|
+
* Apply filter type 3 (Average) - subtract average of left and above
|
|
69
|
+
*/
|
|
70
|
+
private applyAverageFilter;
|
|
71
|
+
/**
|
|
72
|
+
* Apply filter type 4 (Paeth) - Paeth predictor
|
|
73
|
+
*/
|
|
74
|
+
private applyPaethFilter;
|
|
75
|
+
/**
|
|
76
|
+
* Calculate sum of absolute differences for a filtered scanline
|
|
77
|
+
* Lower values indicate better compression potential
|
|
78
|
+
*/
|
|
79
|
+
private calculateFilterScore;
|
|
80
|
+
/**
|
|
81
|
+
* Apply adaptive filtering - choose best filter per scanline
|
|
82
|
+
*/
|
|
83
|
+
private applyAdaptiveFilter;
|
|
84
|
+
/**
|
|
85
|
+
* Filter a single scanline with specified filter type
|
|
86
|
+
*/
|
|
87
|
+
private filterScanline;
|
|
47
88
|
/**
|
|
48
89
|
* Get bytes per pixel for a given color type and bit depth
|
|
49
90
|
*/
|