cross-image 0.2.4 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +615 -333
  3. package/esm/mod.d.ts +6 -4
  4. package/esm/mod.js +4 -2
  5. package/esm/src/formats/apng.d.ts +7 -5
  6. package/esm/src/formats/apng.js +15 -10
  7. package/esm/src/formats/ascii.d.ts +3 -3
  8. package/esm/src/formats/ascii.js +1 -1
  9. package/esm/src/formats/avif.d.ts +3 -3
  10. package/esm/src/formats/avif.js +17 -7
  11. package/esm/src/formats/bmp.d.ts +3 -3
  12. package/esm/src/formats/bmp.js +2 -2
  13. package/esm/src/formats/dng.d.ts +1 -1
  14. package/esm/src/formats/dng.js +1 -1
  15. package/esm/src/formats/gif.d.ts +5 -5
  16. package/esm/src/formats/gif.js +17 -13
  17. package/esm/src/formats/heic.d.ts +3 -3
  18. package/esm/src/formats/heic.js +17 -7
  19. package/esm/src/formats/ico.d.ts +3 -3
  20. package/esm/src/formats/ico.js +4 -4
  21. package/esm/src/formats/jpeg.d.ts +3 -3
  22. package/esm/src/formats/jpeg.js +23 -11
  23. package/esm/src/formats/pam.d.ts +3 -3
  24. package/esm/src/formats/pam.js +2 -2
  25. package/esm/src/formats/pcx.d.ts +3 -3
  26. package/esm/src/formats/pcx.js +2 -2
  27. package/esm/src/formats/png.d.ts +4 -3
  28. package/esm/src/formats/png.js +9 -3
  29. package/esm/src/formats/png_base.d.ts +42 -1
  30. package/esm/src/formats/png_base.js +200 -10
  31. package/esm/src/formats/ppm.d.ts +3 -3
  32. package/esm/src/formats/ppm.js +2 -2
  33. package/esm/src/formats/tiff.d.ts +7 -18
  34. package/esm/src/formats/tiff.js +162 -27
  35. package/esm/src/formats/webp.d.ts +3 -3
  36. package/esm/src/formats/webp.js +11 -8
  37. package/esm/src/image.d.ts +26 -3
  38. package/esm/src/image.js +66 -22
  39. package/esm/src/types.d.ts +122 -4
  40. package/esm/src/utils/base64.d.ts +32 -0
  41. package/esm/src/utils/base64.js +173 -0
  42. package/esm/src/utils/gif_decoder.d.ts +4 -1
  43. package/esm/src/utils/gif_decoder.js +91 -65
  44. package/esm/src/utils/gif_encoder.d.ts +3 -1
  45. package/esm/src/utils/gif_encoder.js +4 -2
  46. package/esm/src/utils/image_processing.d.ts +31 -0
  47. package/esm/src/utils/image_processing.js +232 -70
  48. package/esm/src/utils/jpeg_decoder.d.ts +17 -4
  49. package/esm/src/utils/jpeg_decoder.js +448 -83
  50. package/esm/src/utils/jpeg_encoder.d.ts +15 -1
  51. package/esm/src/utils/jpeg_encoder.js +263 -24
  52. package/esm/src/utils/resize.js +51 -20
  53. package/esm/src/utils/tiff_deflate.d.ts +18 -0
  54. package/esm/src/utils/tiff_deflate.js +27 -0
  55. package/esm/src/utils/tiff_packbits.d.ts +24 -0
  56. package/esm/src/utils/tiff_packbits.js +90 -0
  57. package/esm/src/utils/webp_decoder.d.ts +3 -1
  58. package/esm/src/utils/webp_decoder.js +144 -63
  59. package/esm/src/utils/webp_encoder.js +5 -11
  60. package/package.json +1 -1
  61. package/script/mod.d.ts +6 -4
  62. package/script/mod.js +13 -3
  63. package/script/src/formats/apng.d.ts +7 -5
  64. package/script/src/formats/apng.js +15 -10
  65. package/script/src/formats/ascii.d.ts +3 -3
  66. package/script/src/formats/ascii.js +1 -1
  67. package/script/src/formats/avif.d.ts +3 -3
  68. package/script/src/formats/avif.js +17 -7
  69. package/script/src/formats/bmp.d.ts +3 -3
  70. package/script/src/formats/bmp.js +2 -2
  71. package/script/src/formats/dng.d.ts +1 -1
  72. package/script/src/formats/dng.js +1 -1
  73. package/script/src/formats/gif.d.ts +5 -5
  74. package/script/src/formats/gif.js +17 -13
  75. package/script/src/formats/heic.d.ts +3 -3
  76. package/script/src/formats/heic.js +17 -7
  77. package/script/src/formats/ico.d.ts +3 -3
  78. package/script/src/formats/ico.js +4 -4
  79. package/script/src/formats/jpeg.d.ts +3 -3
  80. package/script/src/formats/jpeg.js +23 -11
  81. package/script/src/formats/pam.d.ts +3 -3
  82. package/script/src/formats/pam.js +2 -2
  83. package/script/src/formats/pcx.d.ts +3 -3
  84. package/script/src/formats/pcx.js +2 -2
  85. package/script/src/formats/png.d.ts +4 -3
  86. package/script/src/formats/png.js +9 -3
  87. package/script/src/formats/png_base.d.ts +42 -1
  88. package/script/src/formats/png_base.js +200 -10
  89. package/script/src/formats/ppm.d.ts +3 -3
  90. package/script/src/formats/ppm.js +2 -2
  91. package/script/src/formats/tiff.d.ts +7 -18
  92. package/script/src/formats/tiff.js +162 -27
  93. package/script/src/formats/webp.d.ts +3 -3
  94. package/script/src/formats/webp.js +11 -8
  95. package/script/src/image.d.ts +26 -3
  96. package/script/src/image.js +64 -20
  97. package/script/src/types.d.ts +122 -4
  98. package/script/src/utils/base64.d.ts +32 -0
  99. package/script/src/utils/base64.js +179 -0
  100. package/script/src/utils/gif_decoder.d.ts +4 -1
  101. package/script/src/utils/gif_decoder.js +91 -65
  102. package/script/src/utils/gif_encoder.d.ts +3 -1
  103. package/script/src/utils/gif_encoder.js +4 -2
  104. package/script/src/utils/image_processing.d.ts +31 -0
  105. package/script/src/utils/image_processing.js +236 -70
  106. package/script/src/utils/jpeg_decoder.d.ts +17 -4
  107. package/script/src/utils/jpeg_decoder.js +448 -83
  108. package/script/src/utils/jpeg_encoder.d.ts +15 -1
  109. package/script/src/utils/jpeg_encoder.js +263 -24
  110. package/script/src/utils/resize.js +51 -20
  111. package/script/src/utils/tiff_deflate.d.ts +18 -0
  112. package/script/src/utils/tiff_deflate.js +31 -0
  113. package/script/src/utils/tiff_packbits.d.ts +24 -0
  114. package/script/src/utils/tiff_packbits.js +94 -0
  115. package/script/src/utils/webp_decoder.d.ts +3 -1
  116. package/script/src/utils/webp_decoder.js +144 -63
  117. package/script/src/utils/webp_encoder.js +5 -11
@@ -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.1",
4
4
  "description": "A pure JavaScript, dependency-free, cross-runtime image processing library for Deno, Node.js, and Bun.",
5
5
  "keywords": [
6
6
  "image",
package/script/mod.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * @example
9
9
  * ```ts
10
- * import { Image } from "@cross/image";
10
+ * import { Image } from "jsr:@cross/image";
11
11
  *
12
12
  * // Decode an image
13
13
  * const data = await Deno.readFile("input.png");
@@ -26,7 +26,7 @@
26
26
  *
27
27
  * @example
28
28
  * ```ts
29
- * import { Image } from "@cross/image";
29
+ * import { Image } from "jsr:@cross/image";
30
30
  *
31
31
  * // Create a blank canvas
32
32
  * const canvas = Image.create(400, 300, 255, 255, 255);
@@ -43,13 +43,13 @@
43
43
  * ```
44
44
  */
45
45
  export { Image } from "./src/image.js";
46
- export type { ASCIIOptions, FrameMetadata, ImageData, ImageFormat, ImageFrame, ImageMetadata, MultiFrameImageData, ResizeOptions, WebPEncodeOptions, } from "./src/types.js";
46
+ export type { APNGEncoderOptions, ASCIIEncoderOptions, AVIFEncoderOptions, FrameMetadata, GIFEncoderOptions, HEICEncoderOptions, ImageData, ImageDecoderOptions, ImageFormat, ImageFrame, ImageMetadata, JPEGEncoderOptions, MultiFrameImageData, PNGEncoderOptions, ResizeOptions, TIFFEncoderOptions, WebPEncoderOptions, } from "./src/types.js";
47
47
  export { PNGFormat } from "./src/formats/png.js";
48
48
  export { APNGFormat } from "./src/formats/apng.js";
49
49
  export { JPEGFormat } from "./src/formats/jpeg.js";
50
50
  export { WebPFormat } from "./src/formats/webp.js";
51
51
  export { GIFFormat } from "./src/formats/gif.js";
52
- export { type TIFFEncodeOptions, TIFFFormat } from "./src/formats/tiff.js";
52
+ export { TIFFFormat } from "./src/formats/tiff.js";
53
53
  export { BMPFormat } from "./src/formats/bmp.js";
54
54
  export { ICOFormat } from "./src/formats/ico.js";
55
55
  export { DNGFormat } from "./src/formats/dng.js";
@@ -59,4 +59,6 @@ export { PPMFormat } from "./src/formats/ppm.js";
59
59
  export { ASCIIFormat } from "./src/formats/ascii.js";
60
60
  export { HEICFormat } from "./src/formats/heic.js";
61
61
  export { AVIFFormat } from "./src/formats/avif.js";
62
+ export { decodeBase64, encodeBase64, parseDataUrl, toDataUrl } from "./src/utils/base64.js";
63
+ export { cmykToRgb, cmykToRgba, rgbaToCmyk, rgbToCmyk } from "./src/utils/image_processing.js";
62
64
  //# sourceMappingURL=mod.d.ts.map
package/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);
@@ -44,7 +44,7 @@
44
44
  * ```
45
45
  */
46
46
  Object.defineProperty(exports, "__esModule", { value: true });
47
- exports.AVIFFormat = exports.HEICFormat = exports.ASCIIFormat = exports.PPMFormat = exports.PCXFormat = exports.PAMFormat = exports.DNGFormat = exports.ICOFormat = exports.BMPFormat = exports.TIFFFormat = exports.GIFFormat = exports.WebPFormat = exports.JPEGFormat = exports.APNGFormat = exports.PNGFormat = exports.Image = void 0;
47
+ exports.rgbToCmyk = exports.rgbaToCmyk = exports.cmykToRgba = exports.cmykToRgb = exports.toDataUrl = exports.parseDataUrl = exports.encodeBase64 = exports.decodeBase64 = exports.AVIFFormat = exports.HEICFormat = exports.ASCIIFormat = exports.PPMFormat = exports.PCXFormat = exports.PAMFormat = exports.DNGFormat = exports.ICOFormat = exports.BMPFormat = exports.TIFFFormat = exports.GIFFormat = exports.WebPFormat = exports.JPEGFormat = exports.APNGFormat = exports.PNGFormat = exports.Image = void 0;
48
48
  var image_js_1 = require("./src/image.js");
49
49
  Object.defineProperty(exports, "Image", { enumerable: true, get: function () { return image_js_1.Image; } });
50
50
  var png_js_1 = require("./src/formats/png.js");
@@ -77,3 +77,13 @@ var heic_js_1 = require("./src/formats/heic.js");
77
77
  Object.defineProperty(exports, "HEICFormat", { enumerable: true, get: function () { return heic_js_1.HEICFormat; } });
78
78
  var avif_js_1 = require("./src/formats/avif.js");
79
79
  Object.defineProperty(exports, "AVIFFormat", { enumerable: true, get: function () { return avif_js_1.AVIFFormat; } });
80
+ var base64_js_1 = require("./src/utils/base64.js");
81
+ Object.defineProperty(exports, "decodeBase64", { enumerable: true, get: function () { return base64_js_1.decodeBase64; } });
82
+ Object.defineProperty(exports, "encodeBase64", { enumerable: true, get: function () { return base64_js_1.encodeBase64; } });
83
+ Object.defineProperty(exports, "parseDataUrl", { enumerable: true, get: function () { return base64_js_1.parseDataUrl; } });
84
+ Object.defineProperty(exports, "toDataUrl", { enumerable: true, get: function () { return base64_js_1.toDataUrl; } });
85
+ var image_processing_js_1 = require("./src/utils/image_processing.js");
86
+ Object.defineProperty(exports, "cmykToRgb", { enumerable: true, get: function () { return image_processing_js_1.cmykToRgb; } });
87
+ Object.defineProperty(exports, "cmykToRgba", { enumerable: true, get: function () { return image_processing_js_1.cmykToRgba; } });
88
+ Object.defineProperty(exports, "rgbaToCmyk", { enumerable: true, get: function () { return image_processing_js_1.rgbaToCmyk; } });
89
+ Object.defineProperty(exports, "rgbToCmyk", { enumerable: true, get: function () { return image_processing_js_1.rgbToCmyk; } });
@@ -1,4 +1,4 @@
1
- import type { ImageData, 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
@@ -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";
@@ -255,9 +253,10 @@ class APNGFormat extends png_base_js_1.PNGBase {
255
253
  /**
256
254
  * Encode RGBA image data to APNG format (single frame)
257
255
  * @param imageData Image data to encode
256
+ * @param options Encoding options (compressionLevel 0-9, default 6)
258
257
  * @returns Encoded APNG image bytes
259
258
  */
260
- encode(imageData) {
259
+ encode(imageData, options) {
261
260
  // For single frame, create a multi-frame with one frame
262
261
  const multiFrame = {
263
262
  width: imageData.width,
@@ -270,15 +269,21 @@ class APNGFormat extends png_base_js_1.PNGBase {
270
269
  }],
271
270
  metadata: imageData.metadata,
272
271
  };
273
- return this.encodeFrames(multiFrame);
272
+ return this.encodeFrames(multiFrame, options);
274
273
  }
275
274
  /**
276
275
  * Encode multi-frame image data to APNG format
277
276
  * @param imageData Multi-frame image data to encode
277
+ * @param options Encoding options (compressionLevel 0-9, default 6)
278
278
  * @returns Encoded APNG image bytes
279
279
  */
280
- async encodeFrames(imageData) {
280
+ async encodeFrames(imageData, options) {
281
281
  const { width, height, frames, metadata } = imageData;
282
+ const compressionLevel = options?.compressionLevel ?? 6;
283
+ // Validate compression level
284
+ if (compressionLevel < 0 || compressionLevel > 9) {
285
+ throw new Error("Compression level must be between 0 and 9");
286
+ }
282
287
  if (frames.length === 0) {
283
288
  throw new Error("No frames to encode");
284
289
  }
@@ -336,7 +341,7 @@ class APNGFormat extends png_base_js_1.PNGBase {
336
341
  fctl[25] = 0; // blend_op: APNG_BLEND_OP_SOURCE
337
342
  chunks.push(this.createChunk("fcTL", fctl));
338
343
  // Filter and compress frame data
339
- const filtered = this.filterData(frame.data, frame.width, frame.height);
344
+ const filtered = this.filterData(frame.data, frame.width, frame.height, compressionLevel);
340
345
  const compressed = await this.deflate(filtered);
341
346
  if (i === 0) {
342
347
  // First frame uses IDAT
@@ -1,4 +1,4 @@
1
- import type { ASCIIOptions, ImageData, ImageFormat, ImageMetadata } from "../types.js";
1
+ import type { ASCIIEncoderOptions, ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata } from "../types.js";
2
2
  /**
3
3
  * ASCII format handler
4
4
  * Converts images to ASCII art text representation
@@ -22,14 +22,14 @@ export declare class ASCIIFormat implements ImageFormat {
22
22
  * @param data Raw ASCII art data
23
23
  * @returns Decoded image data with grayscale RGBA pixels
24
24
  */
25
- decode(data: Uint8Array): Promise<ImageData>;
25
+ decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
26
26
  /**
27
27
  * Encode RGBA image data to ASCII art
28
28
  * @param imageData Image data to encode
29
29
  * @param options Optional ASCII encoding options
30
30
  * @returns Encoded ASCII art as UTF-8 bytes
31
31
  */
32
- encode(imageData: ImageData, options?: ASCIIOptions): Promise<Uint8Array>;
32
+ encode(imageData: ImageData, options?: ASCIIEncoderOptions): Promise<Uint8Array>;
33
33
  /**
34
34
  * Parse options from the options line
35
35
  */
@@ -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 { 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