cross-image 0.2.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +507 -333
  3. package/esm/mod.d.ts +4 -4
  4. package/esm/mod.js +2 -2
  5. package/esm/src/formats/apng.d.ts +5 -5
  6. package/esm/src/formats/apng.js +7 -9
  7. package/esm/src/formats/ascii.d.ts +3 -3
  8. package/esm/src/formats/ascii.js +1 -1
  9. package/esm/src/formats/avif.d.ts +3 -3
  10. package/esm/src/formats/avif.js +7 -7
  11. package/esm/src/formats/bmp.d.ts +3 -3
  12. package/esm/src/formats/bmp.js +2 -2
  13. package/esm/src/formats/dng.d.ts +1 -1
  14. package/esm/src/formats/dng.js +1 -1
  15. package/esm/src/formats/gif.d.ts +4 -4
  16. package/esm/src/formats/gif.js +14 -10
  17. package/esm/src/formats/heic.d.ts +3 -3
  18. package/esm/src/formats/heic.js +7 -7
  19. package/esm/src/formats/ico.d.ts +3 -3
  20. package/esm/src/formats/ico.js +4 -4
  21. package/esm/src/formats/jpeg.d.ts +3 -3
  22. package/esm/src/formats/jpeg.js +23 -11
  23. package/esm/src/formats/pam.d.ts +3 -3
  24. package/esm/src/formats/pam.js +2 -2
  25. package/esm/src/formats/pcx.d.ts +3 -3
  26. package/esm/src/formats/pcx.js +2 -2
  27. package/esm/src/formats/png.d.ts +3 -3
  28. package/esm/src/formats/png.js +2 -2
  29. package/esm/src/formats/png_base.js +2 -5
  30. package/esm/src/formats/ppm.d.ts +3 -3
  31. package/esm/src/formats/ppm.js +2 -2
  32. package/esm/src/formats/tiff.d.ts +7 -18
  33. package/esm/src/formats/tiff.js +86 -21
  34. package/esm/src/formats/webp.d.ts +3 -3
  35. package/esm/src/formats/webp.js +11 -8
  36. package/esm/src/image.d.ts +11 -3
  37. package/esm/src/image.js +37 -21
  38. package/esm/src/types.d.ts +56 -4
  39. package/esm/src/utils/gif_decoder.d.ts +4 -1
  40. package/esm/src/utils/gif_decoder.js +91 -65
  41. package/esm/src/utils/image_processing.js +144 -70
  42. package/esm/src/utils/jpeg_decoder.d.ts +17 -4
  43. package/esm/src/utils/jpeg_decoder.js +448 -83
  44. package/esm/src/utils/jpeg_encoder.d.ts +15 -1
  45. package/esm/src/utils/jpeg_encoder.js +263 -24
  46. package/esm/src/utils/resize.js +51 -20
  47. package/esm/src/utils/tiff_deflate.d.ts +18 -0
  48. package/esm/src/utils/tiff_deflate.js +27 -0
  49. package/esm/src/utils/tiff_packbits.d.ts +24 -0
  50. package/esm/src/utils/tiff_packbits.js +90 -0
  51. package/esm/src/utils/webp_decoder.d.ts +3 -1
  52. package/esm/src/utils/webp_decoder.js +144 -63
  53. package/esm/src/utils/webp_encoder.js +5 -11
  54. package/package.json +1 -1
  55. package/script/mod.d.ts +4 -4
  56. package/script/mod.js +2 -2
  57. package/script/src/formats/apng.d.ts +5 -5
  58. package/script/src/formats/apng.js +7 -9
  59. package/script/src/formats/ascii.d.ts +3 -3
  60. package/script/src/formats/ascii.js +1 -1
  61. package/script/src/formats/avif.d.ts +3 -3
  62. package/script/src/formats/avif.js +7 -7
  63. package/script/src/formats/bmp.d.ts +3 -3
  64. package/script/src/formats/bmp.js +2 -2
  65. package/script/src/formats/dng.d.ts +1 -1
  66. package/script/src/formats/dng.js +1 -1
  67. package/script/src/formats/gif.d.ts +4 -4
  68. package/script/src/formats/gif.js +14 -10
  69. package/script/src/formats/heic.d.ts +3 -3
  70. package/script/src/formats/heic.js +7 -7
  71. package/script/src/formats/ico.d.ts +3 -3
  72. package/script/src/formats/ico.js +4 -4
  73. package/script/src/formats/jpeg.d.ts +3 -3
  74. package/script/src/formats/jpeg.js +23 -11
  75. package/script/src/formats/pam.d.ts +3 -3
  76. package/script/src/formats/pam.js +2 -2
  77. package/script/src/formats/pcx.d.ts +3 -3
  78. package/script/src/formats/pcx.js +2 -2
  79. package/script/src/formats/png.d.ts +3 -3
  80. package/script/src/formats/png.js +2 -2
  81. package/script/src/formats/png_base.js +2 -5
  82. package/script/src/formats/ppm.d.ts +3 -3
  83. package/script/src/formats/ppm.js +2 -2
  84. package/script/src/formats/tiff.d.ts +7 -18
  85. package/script/src/formats/tiff.js +86 -21
  86. package/script/src/formats/webp.d.ts +3 -3
  87. package/script/src/formats/webp.js +11 -8
  88. package/script/src/image.d.ts +11 -3
  89. package/script/src/image.js +36 -20
  90. package/script/src/types.d.ts +56 -4
  91. package/script/src/utils/gif_decoder.d.ts +4 -1
  92. package/script/src/utils/gif_decoder.js +91 -65
  93. package/script/src/utils/image_processing.js +144 -70
  94. package/script/src/utils/jpeg_decoder.d.ts +17 -4
  95. package/script/src/utils/jpeg_decoder.js +448 -83
  96. package/script/src/utils/jpeg_encoder.d.ts +15 -1
  97. package/script/src/utils/jpeg_encoder.js +263 -24
  98. package/script/src/utils/resize.js +51 -20
  99. package/script/src/utils/tiff_deflate.d.ts +18 -0
  100. package/script/src/utils/tiff_deflate.js +31 -0
  101. package/script/src/utils/tiff_packbits.d.ts +24 -0
  102. package/script/src/utils/tiff_packbits.js +94 -0
  103. package/script/src/utils/webp_decoder.d.ts +3 -1
  104. package/script/src/utils/webp_decoder.js +144 -63
  105. package/script/src/utils/webp_encoder.js +5 -11
@@ -19,9 +19,11 @@
19
19
  * @see https://developers.google.com/speed/webp/docs/riff_container
20
20
  * @see https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification
21
21
  */
22
+ import type { ImageDecoderOptions } from "../types.js";
22
23
  export declare class WebPDecoder {
23
24
  private data;
24
- constructor(data: Uint8Array);
25
+ private options;
26
+ constructor(data: Uint8Array, settings?: ImageDecoderOptions);
25
27
  decode(): {
26
28
  width: number;
27
29
  height: number;
@@ -93,7 +93,6 @@ class HuffmanTable {
93
93
  const bit = reader.readBits(1);
94
94
  node = bit === 0 ? node.left : node.right;
95
95
  if (!node) {
96
- // console.log("Invalid Huffman code - walked off tree");
97
96
  throw new Error("Invalid Huffman code");
98
97
  }
99
98
  }
@@ -164,14 +163,24 @@ class BitReader {
164
163
  }
165
164
  }
166
165
  export class WebPDecoder {
167
- constructor(data) {
166
+ constructor(data, settings = {}) {
168
167
  Object.defineProperty(this, "data", {
169
168
  enumerable: true,
170
169
  configurable: true,
171
170
  writable: true,
172
171
  value: void 0
173
172
  });
173
+ Object.defineProperty(this, "options", {
174
+ enumerable: true,
175
+ configurable: true,
176
+ writable: true,
177
+ value: void 0
178
+ });
174
179
  this.data = data;
180
+ this.options = {
181
+ tolerantDecoding: settings.tolerantDecoding ?? true,
182
+ onWarning: settings.onWarning,
183
+ };
175
184
  }
176
185
  decode() {
177
186
  // Verify WebP signature
@@ -278,66 +287,142 @@ export class WebPDecoder {
278
287
  const numPixels = width * height;
279
288
  // Color cache for repeated colors
280
289
  const colorCache = new Uint32Array(colorCacheSize);
281
- for (let i = 0; i < numPixels;) {
282
- // Read green channel (which determines the code type)
283
- const green = huffmanTables.green.readSymbol(reader);
284
- if (green < 256) {
285
- // Literal pixel
286
- const red = huffmanTables.red.readSymbol(reader);
287
- const blue = huffmanTables.blue.readSymbol(reader);
288
- const alpha = alphaUsed !== 0
289
- ? huffmanTables.alpha.readSymbol(reader)
290
- : 255;
291
- pixelData[pixelIndex++] = red;
292
- pixelData[pixelIndex++] = green;
293
- pixelData[pixelIndex++] = blue;
294
- pixelData[pixelIndex++] = alpha;
295
- // Add to color cache if enabled
296
- if (useColorCache) {
297
- const color = (alpha << 24) | (blue << 16) | (green << 8) | red;
298
- colorCache[i % colorCacheSize] = color;
290
+ if (this.options.tolerantDecoding) {
291
+ // Tolerant mode: continue decoding even if errors occur
292
+ try {
293
+ for (let i = 0; i < numPixels;) {
294
+ // Read green channel (which determines the code type)
295
+ const green = huffmanTables.green.readSymbol(reader);
296
+ if (green < 256) {
297
+ // Literal pixel
298
+ const red = huffmanTables.red.readSymbol(reader);
299
+ const blue = huffmanTables.blue.readSymbol(reader);
300
+ const alpha = alphaUsed !== 0 ? huffmanTables.alpha.readSymbol(reader) : 255;
301
+ pixelData[pixelIndex++] = red;
302
+ pixelData[pixelIndex++] = green;
303
+ pixelData[pixelIndex++] = blue;
304
+ pixelData[pixelIndex++] = alpha;
305
+ // Add to color cache if enabled
306
+ if (useColorCache) {
307
+ const color = (alpha << 24) | (blue << 16) | (green << 8) | red;
308
+ colorCache[i % colorCacheSize] = color;
309
+ }
310
+ i++;
311
+ }
312
+ else if (green < 256 + 24) {
313
+ // Backward reference (LZ77)
314
+ const lengthSymbol = green - 256;
315
+ const length = this.getLength(lengthSymbol, reader);
316
+ const distancePrefix = huffmanTables.distance.readSymbol(reader);
317
+ const distance = this.getDistance(distancePrefix, reader);
318
+ // Copy pixels from earlier in the stream
319
+ const srcIndex = pixelIndex - distance * 4;
320
+ if (srcIndex < 0) {
321
+ throw new Error("Invalid backward reference");
322
+ }
323
+ for (let j = 0; j < length; j++) {
324
+ pixelData[pixelIndex++] = pixelData[srcIndex + j * 4];
325
+ pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 1];
326
+ pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 2];
327
+ pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 3];
328
+ // Add to color cache
329
+ if (useColorCache) {
330
+ const color = (pixelData[pixelIndex - 1] << 24) |
331
+ (pixelData[pixelIndex - 2] << 16) |
332
+ (pixelData[pixelIndex - 3] << 8) |
333
+ pixelData[pixelIndex - 4];
334
+ colorCache[(i + j) % colorCacheSize] = color;
335
+ }
336
+ }
337
+ i += length;
338
+ }
339
+ else {
340
+ // Color cache reference
341
+ const cacheIndex = green - 256 - 24;
342
+ if (cacheIndex >= colorCacheSize) {
343
+ throw new Error("Invalid color cache index");
344
+ }
345
+ const color = colorCache[cacheIndex];
346
+ pixelData[pixelIndex++] = color & 0xff; // R
347
+ pixelData[pixelIndex++] = (color >> 8) & 0xff; // G
348
+ pixelData[pixelIndex++] = (color >> 16) & 0xff; // B
349
+ pixelData[pixelIndex++] = (color >> 24) & 0xff; // A
350
+ i++;
351
+ }
299
352
  }
300
- i++;
301
353
  }
302
- else if (green < 256 + 24) {
303
- // Backward reference (LZ77)
304
- const lengthSymbol = green - 256;
305
- const length = this.getLength(lengthSymbol, reader);
306
- const distancePrefix = huffmanTables.distance.readSymbol(reader);
307
- const distance = this.getDistance(distancePrefix, reader);
308
- // Copy pixels from earlier in the stream
309
- const srcIndex = pixelIndex - distance * 4;
310
- if (srcIndex < 0) {
311
- throw new Error("Invalid backward reference");
354
+ catch (e) {
355
+ // Tolerant decoding: fill remaining pixels with gray (128, 128, 128, 255)
356
+ this.options.onWarning?.(`WebP VP8L: Partial decode at pixel ${pixelIndex / 4}/${numPixels}`, e);
357
+ while (pixelIndex < pixelData.length) {
358
+ pixelData[pixelIndex++] = 128; // R
359
+ pixelData[pixelIndex++] = 128; // G
360
+ pixelData[pixelIndex++] = 128; // B
361
+ pixelData[pixelIndex++] = 255; // A
312
362
  }
313
- for (let j = 0; j < length; j++) {
314
- pixelData[pixelIndex++] = pixelData[srcIndex + j * 4];
315
- pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 1];
316
- pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 2];
317
- pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 3];
318
- // Add to color cache
363
+ }
364
+ }
365
+ else {
366
+ // Non-tolerant mode: throw on first error
367
+ for (let i = 0; i < numPixels;) {
368
+ // Read green channel (which determines the code type)
369
+ const green = huffmanTables.green.readSymbol(reader);
370
+ if (green < 256) {
371
+ // Literal pixel
372
+ const red = huffmanTables.red.readSymbol(reader);
373
+ const blue = huffmanTables.blue.readSymbol(reader);
374
+ const alpha = alphaUsed !== 0 ? huffmanTables.alpha.readSymbol(reader) : 255;
375
+ pixelData[pixelIndex++] = red;
376
+ pixelData[pixelIndex++] = green;
377
+ pixelData[pixelIndex++] = blue;
378
+ pixelData[pixelIndex++] = alpha;
379
+ // Add to color cache if enabled
319
380
  if (useColorCache) {
320
- const color = (pixelData[pixelIndex - 1] << 24) |
321
- (pixelData[pixelIndex - 2] << 16) |
322
- (pixelData[pixelIndex - 3] << 8) |
323
- pixelData[pixelIndex - 4];
324
- colorCache[(i + j) % colorCacheSize] = color;
381
+ const color = (alpha << 24) | (blue << 16) | (green << 8) | red;
382
+ colorCache[i % colorCacheSize] = color;
325
383
  }
384
+ i++;
326
385
  }
327
- i += length;
328
- }
329
- else {
330
- // Color cache reference
331
- const cacheIndex = green - 256 - 24;
332
- if (cacheIndex >= colorCacheSize) {
333
- throw new Error("Invalid color cache index");
386
+ else if (green < 256 + 24) {
387
+ // Backward reference (LZ77)
388
+ const lengthSymbol = green - 256;
389
+ const length = this.getLength(lengthSymbol, reader);
390
+ const distancePrefix = huffmanTables.distance.readSymbol(reader);
391
+ const distance = this.getDistance(distancePrefix, reader);
392
+ // Copy pixels from earlier in the stream
393
+ const srcIndex = pixelIndex - distance * 4;
394
+ if (srcIndex < 0) {
395
+ throw new Error("Invalid backward reference");
396
+ }
397
+ for (let j = 0; j < length; j++) {
398
+ pixelData[pixelIndex++] = pixelData[srcIndex + j * 4];
399
+ pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 1];
400
+ pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 2];
401
+ pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 3];
402
+ // Add to color cache
403
+ if (useColorCache) {
404
+ const color = (pixelData[pixelIndex - 1] << 24) |
405
+ (pixelData[pixelIndex - 2] << 16) |
406
+ (pixelData[pixelIndex - 3] << 8) |
407
+ pixelData[pixelIndex - 4];
408
+ colorCache[(i + j) % colorCacheSize] = color;
409
+ }
410
+ }
411
+ i += length;
412
+ }
413
+ else {
414
+ // Color cache reference
415
+ const cacheIndex = green - 256 - 24;
416
+ if (cacheIndex >= colorCacheSize) {
417
+ throw new Error("Invalid color cache index");
418
+ }
419
+ const color = colorCache[cacheIndex];
420
+ pixelData[pixelIndex++] = color & 0xff; // R
421
+ pixelData[pixelIndex++] = (color >> 8) & 0xff; // G
422
+ pixelData[pixelIndex++] = (color >> 16) & 0xff; // B
423
+ pixelData[pixelIndex++] = (color >> 24) & 0xff; // A
424
+ i++;
334
425
  }
335
- const color = colorCache[cacheIndex];
336
- pixelData[pixelIndex++] = color & 0xff; // R
337
- pixelData[pixelIndex++] = (color >> 8) & 0xff; // G
338
- pixelData[pixelIndex++] = (color >> 16) & 0xff; // B
339
- pixelData[pixelIndex++] = (color >> 24) & 0xff; // A
340
- i++;
341
426
  }
342
427
  }
343
428
  return pixelData;
@@ -378,9 +463,7 @@ export class WebPDecoder {
378
463
  const isFirstEightBits = reader.readBits(1);
379
464
  const symbols = [];
380
465
  for (let i = 0; i < numSymbols; i++) {
381
- const symbolBits = isFirstEightBits
382
- ? reader.readBits(8)
383
- : reader.readBits(1);
466
+ const symbolBits = isFirstEightBits ? reader.readBits(8) : reader.readBits(1);
384
467
  symbols.push(symbolBits);
385
468
  }
386
469
  // Build simple Huffman table
@@ -396,9 +479,7 @@ export class WebPDecoder {
396
479
  }
397
480
  else {
398
481
  // Complex code - read code lengths
399
- const maxSymbol = isGreen
400
- ? (256 + 24 + (useColorCache ? (1 << colorCacheBits) : 0))
401
- : 256;
482
+ const maxSymbol = isGreen ? (256 + 24 + (useColorCache ? (1 << colorCacheBits) : 0)) : 256;
402
483
  const codeLengths = this.readCodeLengths(reader, maxSymbol);
403
484
  this.buildHuffmanTable(table, codeLengths);
404
485
  }
@@ -406,7 +487,7 @@ export class WebPDecoder {
406
487
  readCodeLengths(reader, maxSymbol) {
407
488
  // Read code length codes (used to encode the actual code lengths)
408
489
  const numCodeLengthCodes = reader.readBits(4) + 4;
409
- const codeLengthCodeLengths = new Array(19).fill(0);
490
+ const codeLengthCodeLengths = new Uint8Array(19);
410
491
  // Code length code order
411
492
  const codeLengthCodeOrder = [
412
493
  17,
@@ -442,7 +523,7 @@ export class WebPDecoder {
442
523
  const codeLengthTable = new HuffmanTable();
443
524
  this.buildHuffmanTable(codeLengthTable, codeLengthCodeLengths);
444
525
  // Read actual code lengths
445
- const codeLengths = new Array(maxSymbol).fill(0);
526
+ const codeLengths = new Uint8Array(maxSymbol);
446
527
  let i = 0;
447
528
  while (i < maxSymbol) {
448
529
  const code = codeLengthTable.readSymbol(reader);
@@ -369,7 +369,7 @@ export class WebPEncoder {
369
369
  * Returns an array where index is the symbol and value is the code length
370
370
  */
371
371
  calculateCodeLengths(frequencies, maxSymbol, maxCodeLength = 15) {
372
- const codeLengths = new Array(maxSymbol).fill(0);
372
+ const codeLengths = new Uint8Array(maxSymbol);
373
373
  // Get symbols with non-zero frequencies
374
374
  const symbols = Array.from(frequencies.keys()).sort((a, b) => a - b);
375
375
  if (symbols.length === 0)
@@ -377,7 +377,6 @@ export class WebPEncoder {
377
377
  // For a single symbol, use code length 1
378
378
  // (Canonical Huffman codes require length >= 1)
379
379
  if (symbols.length === 1) {
380
- // console.log(`Single symbol ${symbols[0]}, forcing length 1`);
381
380
  codeLengths[symbols[0]] = 1;
382
381
  return codeLengths;
383
382
  }
@@ -424,7 +423,6 @@ export class WebPEncoder {
424
423
  // If tree is too deep, flatten frequencies and rebuild
425
424
  let attempts = 0;
426
425
  while (maxDepth > maxCodeLength && attempts < 5) {
427
- // console.log(`Tree too deep (${maxDepth} > ${maxCodeLength}), flattening...`);
428
426
  attempts++;
429
427
  // Add bias to frequencies to flatten the tree
430
428
  // Increase bias with each attempt
@@ -439,11 +437,8 @@ export class WebPEncoder {
439
437
  maxDepth = 0;
440
438
  checkDepth(root, 0);
441
439
  }
442
- if (maxDepth > maxCodeLength) {
443
- console.warn(`Failed to reduce Huffman tree depth to ${maxCodeLength} (current: ${maxDepth})`);
444
- // Force hard limit by sorting and assigning lengths?
445
- // For now, let's just see if this is happening.
446
- }
440
+ // If tree depth couldn't be reduced, encoding may fail but we'll try anyway
441
+ // This is an internal limitation that doesn't affect most images
447
442
  // Calculate code lengths by traversing tree (iterative to avoid deep recursion)
448
443
  const stack = [{
449
444
  node: root,
@@ -485,7 +480,7 @@ export class WebPEncoder {
485
480
  }
486
481
  }
487
482
  // Count symbols at each length
488
- const lengthCounts = new Array(maxLength + 1).fill(0);
483
+ const lengthCounts = new Uint32Array(maxLength + 1);
489
484
  for (let i = 0; i < codeLengths.length; i++) {
490
485
  if (codeLengths[i] > 0) {
491
486
  lengthCounts[codeLengths[i]]++;
@@ -493,7 +488,7 @@ export class WebPEncoder {
493
488
  }
494
489
  // Generate first code for each length
495
490
  let code = 0;
496
- const nextCode = new Array(maxLength + 1).fill(0);
491
+ const nextCode = new Uint32Array(maxLength + 1);
497
492
  for (let len = 1; len <= maxLength; len++) {
498
493
  code = (code + lengthCounts[len - 1]) << 1;
499
494
  nextCode[len] = code;
@@ -625,7 +620,6 @@ export class WebPEncoder {
625
620
  }
626
621
  numCodeLengthCodes = Math.max(4, numCodeLengthCodes);
627
622
  // Write number of code length codes
628
- // console.log(`Complex Huffman: numCodeLengthCodes=${numCodeLengthCodes}, rleEncoded.length=${rleEncoded.length}`);
629
623
  writer.writeBits(numCodeLengthCodes - 4, 4);
630
624
  // Write code length code lengths
631
625
  for (let i = 0; i < numCodeLengthCodes; i++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cross-image",
3
- "version": "0.2.4",
3
+ "version": "0.4.0",
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
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * @example
9
9
  * ```ts
10
- * import { Image } from "@cross/image";
10
+ * import { Image } from "jsr:@cross/image";
11
11
  *
12
12
  * // Decode an image
13
13
  * const data = await Deno.readFile("input.png");
@@ -26,7 +26,7 @@
26
26
  *
27
27
  * @example
28
28
  * ```ts
29
- * import { Image } from "@cross/image";
29
+ * import { Image } from "jsr:@cross/image";
30
30
  *
31
31
  * // Create a blank canvas
32
32
  * const canvas = Image.create(400, 300, 255, 255, 255);
@@ -43,13 +43,13 @@
43
43
  * ```
44
44
  */
45
45
  export { Image } from "./src/image.js";
46
- export type { ASCIIOptions, FrameMetadata, ImageData, ImageFormat, ImageFrame, ImageMetadata, MultiFrameImageData, ResizeOptions, WebPEncodeOptions, } from "./src/types.js";
46
+ export type { ASCIIEncoderOptions, FrameMetadata, ImageData, ImageDecoderOptions, ImageFormat, ImageFrame, ImageMetadata, JPEGEncoderOptions, MultiFrameImageData, ResizeOptions, TIFFEncoderOptions, WebPEncoderOptions, } from "./src/types.js";
47
47
  export { PNGFormat } from "./src/formats/png.js";
48
48
  export { APNGFormat } from "./src/formats/apng.js";
49
49
  export { JPEGFormat } from "./src/formats/jpeg.js";
50
50
  export { WebPFormat } from "./src/formats/webp.js";
51
51
  export { GIFFormat } from "./src/formats/gif.js";
52
- export { type TIFFEncodeOptions, TIFFFormat } from "./src/formats/tiff.js";
52
+ export { TIFFFormat } from "./src/formats/tiff.js";
53
53
  export { BMPFormat } from "./src/formats/bmp.js";
54
54
  export { ICOFormat } from "./src/formats/ico.js";
55
55
  export { DNGFormat } from "./src/formats/dng.js";
package/script/mod.js CHANGED
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @example
10
10
  * ```ts
11
- * import { Image } from "@cross/image";
11
+ * import { Image } from "jsr:@cross/image";
12
12
  *
13
13
  * // Decode an image
14
14
  * const data = await Deno.readFile("input.png");
@@ -27,7 +27,7 @@
27
27
  *
28
28
  * @example
29
29
  * ```ts
30
- * import { Image } from "@cross/image";
30
+ * import { Image } from "jsr:@cross/image";
31
31
  *
32
32
  * // Create a blank canvas
33
33
  * const canvas = Image.create(400, 300, 255, 255, 255);
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat, ImageMetadata, MultiFrameImageData } from "../types.js";
1
+ import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, MultiFrameImageData } from "../types.js";
2
2
  import { PNGBase } from "./png_base.js";
3
3
  /**
4
4
  * APNG (Animated PNG) format handler
@@ -26,25 +26,25 @@ export declare class APNGFormat extends PNGBase implements ImageFormat {
26
26
  * @param data Raw APNG image data
27
27
  * @returns Decoded image data with RGBA pixels of first frame
28
28
  */
29
- decode(data: Uint8Array): Promise<ImageData>;
29
+ decode(data: Uint8Array, settings?: ImageDecoderOptions): Promise<ImageData>;
30
30
  /**
31
31
  * Decode all frames from APNG image
32
32
  * @param data Raw APNG image data
33
33
  * @returns Decoded multi-frame image data
34
34
  */
35
- decodeFrames(data: Uint8Array): Promise<MultiFrameImageData>;
35
+ decodeFrames(data: Uint8Array, _settings?: ImageDecoderOptions): Promise<MultiFrameImageData>;
36
36
  /**
37
37
  * Encode RGBA image data to APNG format (single frame)
38
38
  * @param imageData Image data to encode
39
39
  * @returns Encoded APNG image bytes
40
40
  */
41
- encode(imageData: ImageData): Promise<Uint8Array>;
41
+ encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
42
42
  /**
43
43
  * Encode multi-frame image data to APNG format
44
44
  * @param imageData Multi-frame image data to encode
45
45
  * @returns Encoded APNG image bytes
46
46
  */
47
- encodeFrames(imageData: MultiFrameImageData): Promise<Uint8Array>;
47
+ encodeFrames(imageData: MultiFrameImageData, _options?: unknown): Promise<Uint8Array>;
48
48
  private decodeFrameData;
49
49
  /**
50
50
  * Get the list of metadata fields supported by APNG format
@@ -70,8 +70,8 @@ class APNGFormat extends png_base_js_1.PNGBase {
70
70
  * @param data Raw APNG image data
71
71
  * @returns Decoded image data with RGBA pixels of first frame
72
72
  */
73
- async decode(data) {
74
- const frames = await this.decodeFrames(data);
73
+ async decode(data, settings) {
74
+ const frames = await this.decodeFrames(data, settings);
75
75
  const firstFrame = frames.frames[0];
76
76
  return {
77
77
  width: firstFrame.width,
@@ -85,7 +85,7 @@ class APNGFormat extends png_base_js_1.PNGBase {
85
85
  * @param data Raw APNG image data
86
86
  * @returns Decoded multi-frame image data
87
87
  */
88
- async decodeFrames(data) {
88
+ async decodeFrames(data, _settings) {
89
89
  if (!this.canDecode(data)) {
90
90
  throw new Error("Invalid APNG signature or missing acTL chunk");
91
91
  }
@@ -172,9 +172,7 @@ class APNGFormat extends png_base_js_1.PNGBase {
172
172
  const delayDen = this.readUint16(chunk.data, 22);
173
173
  const disposeOp = chunk.data[24];
174
174
  const blendOp = chunk.data[25];
175
- const delay = delayDen === 0
176
- ? delayNum * 10
177
- : Math.round((delayNum / delayDen) * 1000);
175
+ const delay = delayDen === 0 ? delayNum * 10 : Math.round((delayNum / delayDen) * 1000);
178
176
  let disposal = "none";
179
177
  if (disposeOp === 1)
180
178
  disposal = "background";
@@ -257,7 +255,7 @@ class APNGFormat extends png_base_js_1.PNGBase {
257
255
  * @param imageData Image data to encode
258
256
  * @returns Encoded APNG image bytes
259
257
  */
260
- encode(imageData) {
258
+ encode(imageData, _options) {
261
259
  // For single frame, create a multi-frame with one frame
262
260
  const multiFrame = {
263
261
  width: imageData.width,
@@ -270,14 +268,14 @@ class APNGFormat extends png_base_js_1.PNGBase {
270
268
  }],
271
269
  metadata: imageData.metadata,
272
270
  };
273
- return this.encodeFrames(multiFrame);
271
+ return this.encodeFrames(multiFrame, _options);
274
272
  }
275
273
  /**
276
274
  * Encode multi-frame image data to APNG format
277
275
  * @param imageData Multi-frame image data to encode
278
276
  * @returns Encoded APNG image bytes
279
277
  */
280
- async encodeFrames(imageData) {
278
+ async encodeFrames(imageData, _options) {
281
279
  const { width, height, frames, metadata } = imageData;
282
280
  if (frames.length === 0) {
283
281
  throw new Error("No frames to encode");
@@ -1,4 +1,4 @@
1
- import type { ASCIIOptions, ImageData, ImageFormat, ImageMetadata } from "../types.js";
1
+ import type { ASCIIEncoderOptions, ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata } from "../types.js";
2
2
  /**
3
3
  * ASCII format handler
4
4
  * Converts images to ASCII art text representation
@@ -22,14 +22,14 @@ export declare class ASCIIFormat implements ImageFormat {
22
22
  * @param data Raw ASCII art data
23
23
  * @returns Decoded image data with grayscale RGBA pixels
24
24
  */
25
- decode(data: Uint8Array): Promise<ImageData>;
25
+ decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
26
26
  /**
27
27
  * Encode RGBA image data to ASCII art
28
28
  * @param imageData Image data to encode
29
29
  * @param options Optional ASCII encoding options
30
30
  * @returns Encoded ASCII art as UTF-8 bytes
31
31
  */
32
- encode(imageData: ImageData, options?: ASCIIOptions): Promise<Uint8Array>;
32
+ encode(imageData: ImageData, options?: ASCIIEncoderOptions): Promise<Uint8Array>;
33
33
  /**
34
34
  * Parse options from the options line
35
35
  */
@@ -71,7 +71,7 @@ class ASCIIFormat {
71
71
  * @param data Raw ASCII art data
72
72
  * @returns Decoded image data with grayscale RGBA pixels
73
73
  */
74
- decode(data) {
74
+ decode(data, _options) {
75
75
  if (!this.canDecode(data)) {
76
76
  throw new Error("Invalid ASCII art signature");
77
77
  }
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat, ImageMetadata } from "../types.js";
1
+ import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata } from "../types.js";
2
2
  /**
3
3
  * AVIF format handler
4
4
  * Supports AVIF images using runtime APIs (ImageDecoder/OffscreenCanvas)
@@ -21,7 +21,7 @@ export declare class AVIFFormat implements ImageFormat {
21
21
  * @param data Raw AVIF image data
22
22
  * @returns Decoded image data with RGBA pixels
23
23
  */
24
- decode(data: Uint8Array): Promise<ImageData>;
24
+ decode(data: Uint8Array, settings?: ImageDecoderOptions): Promise<ImageData>;
25
25
  /**
26
26
  * Encode RGBA image data to AVIF format
27
27
  * Uses runtime APIs (OffscreenCanvas) for encoding
@@ -32,7 +32,7 @@ export declare class AVIFFormat implements ImageFormat {
32
32
  * @param imageData Image data to encode
33
33
  * @returns Encoded AVIF image bytes
34
34
  */
35
- encode(imageData: ImageData): Promise<Uint8Array>;
35
+ encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
36
36
  /**
37
37
  * Decode using runtime APIs
38
38
  * @param data Raw AVIF data
@@ -52,10 +52,13 @@ class AVIFFormat {
52
52
  * @param data Raw AVIF image data
53
53
  * @returns Decoded image data with RGBA pixels
54
54
  */
55
- async decode(data) {
55
+ async decode(data, settings) {
56
56
  if (!this.canDecode(data)) {
57
57
  throw new Error("Invalid AVIF signature");
58
58
  }
59
+ if (settings?.runtimeDecoding === "never") {
60
+ throw new Error("AVIF decoding requires runtime APIs; set runtimeDecoding to 'prefer'");
61
+ }
59
62
  // Extract metadata before decoding pixels
60
63
  const metadata = await this.extractMetadata(data);
61
64
  // Use runtime decoder
@@ -79,8 +82,8 @@ class AVIFFormat {
79
82
  * @param imageData Image data to encode
80
83
  * @returns Encoded AVIF image bytes
81
84
  */
82
- async encode(imageData) {
83
- const { width, height, data, metadata } = imageData;
85
+ async encode(imageData, _options) {
86
+ const { width, height, data, metadata: _metadata } = imageData;
84
87
  // Try to use runtime encoding if available
85
88
  if (typeof OffscreenCanvas !== "undefined") {
86
89
  try {
@@ -101,10 +104,7 @@ class AVIFFormat {
101
104
  // parsing and modifying the ISOBMFF container structure
102
105
  // For now, we rely on the runtime encoder to preserve metadata
103
106
  // if it was passed through the canvas
104
- if (metadata) {
105
- // Future enhancement: inject metadata into AVIF container
106
- console.warn("AVIF metadata injection not yet implemented, metadata may be lost");
107
- }
107
+ // Future enhancement: inject metadata into AVIF container
108
108
  return encoded;
109
109
  }
110
110
  }
@@ -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
  */
@@ -41,7 +41,7 @@ class BMPFormat {
41
41
  * @param data Raw BMP image data
42
42
  * @returns Decoded image data with RGBA pixels
43
43
  */
44
- decode(data) {
44
+ decode(data, _options) {
45
45
  if (!this.canDecode(data)) {
46
46
  throw new Error("Invalid BMP signature");
47
47
  }
@@ -118,7 +118,7 @@ class BMPFormat {
118
118
  * @param imageData Image data to encode
119
119
  * @returns Encoded BMP image bytes
120
120
  */
121
- encode(imageData) {
121
+ encode(imageData, _options) {
122
122
  const { width, height, data, metadata } = imageData;
123
123
  // Calculate sizes
124
124
  const bytesPerPixel = 4; // We'll encode as 32-bit RGBA
@@ -22,7 +22,7 @@ export declare class DNGFormat extends TIFFFormat {
22
22
  * @param imageData Image data to encode
23
23
  * @returns Encoded DNG image bytes
24
24
  */
25
- encode(imageData: ImageData): Promise<Uint8Array>;
25
+ encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
26
26
  /**
27
27
  * Get the list of metadata fields supported by DNG format
28
28
  * DNG is based on TIFF, so it supports all TIFF metadata fields