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
@@ -24,6 +24,23 @@ exports.rotate180 = rotate180;
24
24
  exports.rotate270 = rotate270;
25
25
  exports.flipHorizontal = flipHorizontal;
26
26
  exports.flipVertical = flipVertical;
27
+ exports.rgbToCmyk = rgbToCmyk;
28
+ exports.cmykToRgb = cmykToRgb;
29
+ exports.rgbaToCmyk = rgbaToCmyk;
30
+ exports.cmykToRgba = cmykToRgba;
31
+ /**
32
+ * Detect system endianness
33
+ * Returns true if little-endian (most common), false if big-endian
34
+ */
35
+ function isLittleEndian() {
36
+ const buffer = new ArrayBuffer(4);
37
+ const uint32View = new Uint32Array(buffer);
38
+ const uint8View = new Uint8Array(buffer);
39
+ uint32View[0] = 0x01020304;
40
+ return uint8View[0] === 0x04;
41
+ }
42
+ // Cache the endianness check result
43
+ const IS_LITTLE_ENDIAN = isLittleEndian();
27
44
  /**
28
45
  * Composite one image on top of another at a specified position
29
46
  * @param base Base image data (RGBA)
@@ -48,17 +65,20 @@ function composite(base, baseWidth, baseHeight, overlay, overlayWidth, overlayHe
48
65
  const endY = Math.min(baseHeight, y + overlayHeight);
49
66
  // Iterate over the overlapping region
50
67
  for (let py = startY; py < endY; py++) {
68
+ const baseRowOffset = py * baseWidth * 4;
69
+ const overlayRowOffset = (py - y) * overlayWidth * 4;
51
70
  for (let px = startX; px < endX; px++) {
52
- // Calculate indices
53
- const baseIdx = (py * baseWidth + px) * 4;
54
- const overlayX = px - x;
55
- const overlayY = py - y;
56
- const overlayIdx = (overlayY * overlayWidth + overlayX) * 4;
71
+ // Calculate indices with pre-computed offsets
72
+ const baseIdx = baseRowOffset + px * 4;
73
+ const overlayIdx = overlayRowOffset + (px - x) * 4;
57
74
  // Get overlay pixel with opacity
58
75
  const overlayR = overlay[overlayIdx];
59
76
  const overlayG = overlay[overlayIdx + 1];
60
77
  const overlayB = overlay[overlayIdx + 2];
61
78
  const overlayA = (overlay[overlayIdx + 3] / 255) * finalOpacity;
79
+ // Skip if overlay is fully transparent
80
+ if (overlayA === 0)
81
+ continue;
62
82
  // Get base pixel
63
83
  const baseR = result[baseIdx];
64
84
  const baseG = result[baseIdx + 1];
@@ -66,11 +86,14 @@ function composite(base, baseWidth, baseHeight, overlay, overlayWidth, overlayHe
66
86
  const baseA = result[baseIdx + 3] / 255;
67
87
  // Alpha compositing using "over" operation
68
88
  const outA = overlayA + baseA * (1 - overlayA);
69
- if (outA > 0) {
70
- result[baseIdx] = Math.round((overlayR * overlayA + baseR * baseA * (1 - overlayA)) / outA);
71
- result[baseIdx + 1] = Math.round((overlayG * overlayA + baseG * baseA * (1 - overlayA)) / outA);
72
- result[baseIdx + 2] = Math.round((overlayB * overlayA + baseB * baseA * (1 - overlayA)) / outA);
73
- result[baseIdx + 3] = Math.round(outA * 255);
89
+ if (outA > 0.001) {
90
+ const invOverlayA = 1 - overlayA;
91
+ const baseWeight = baseA * invOverlayA;
92
+ const invOutA = 1 / outA;
93
+ result[baseIdx] = ((overlayR * overlayA + baseR * baseWeight) * invOutA + 0.5) | 0;
94
+ result[baseIdx + 1] = ((overlayG * overlayA + baseG * baseWeight) * invOutA + 0.5) | 0;
95
+ result[baseIdx + 2] = ((overlayB * overlayA + baseB * baseWeight) * invOutA + 0.5) | 0;
96
+ result[baseIdx + 3] = (outA * 255 + 0.5) | 0;
74
97
  }
75
98
  }
76
99
  }
@@ -84,11 +107,23 @@ function composite(base, baseWidth, baseHeight, overlay, overlayWidth, overlayHe
84
107
  */
85
108
  function adjustBrightness(data, amount) {
86
109
  const result = new Uint8Array(data.length);
87
- const adjust = Math.max(-1, Math.min(1, amount)) * 255;
110
+ const clampedAmount = Math.max(-1, Math.min(1, amount));
111
+ const adjust = clampedAmount * 255;
112
+ // Pre-compute lookup table for clamping
113
+ // Range: -255 to 511 (data value 0-255 + adjust -255 to 255), offset by 255 for zero-based index
114
+ const LUT_SIZE = 767;
115
+ const LUT_OFFSET = 255;
116
+ const lut = new Uint8Array(LUT_SIZE);
117
+ for (let i = 0; i < LUT_SIZE; i++) {
118
+ const value = i - LUT_OFFSET;
119
+ lut[i] = value < 0 ? 0 : (value > 255 ? 255 : value);
120
+ }
121
+ // Use bitwise OR for fast rounding (equivalent to Math.round for positive numbers)
122
+ const adjustInt = (adjust + 0.5) | 0;
88
123
  for (let i = 0; i < data.length; i += 4) {
89
- result[i] = Math.max(0, Math.min(255, data[i] + adjust)); // R
90
- result[i + 1] = Math.max(0, Math.min(255, data[i + 1] + adjust)); // G
91
- result[i + 2] = Math.max(0, Math.min(255, data[i + 2] + adjust)); // B
124
+ result[i] = lut[data[i] + adjustInt + LUT_OFFSET]; // R
125
+ result[i + 1] = lut[data[i + 1] + adjustInt + LUT_OFFSET]; // G
126
+ result[i + 2] = lut[data[i + 2] + adjustInt + LUT_OFFSET]; // B
92
127
  result[i + 3] = data[i + 3]; // A
93
128
  }
94
129
  return result;
@@ -104,10 +139,16 @@ function adjustContrast(data, amount) {
104
139
  const contrast = Math.max(-1, Math.min(1, amount));
105
140
  const factor = (259 * (contrast * 255 + 255)) /
106
141
  (255 * (259 - contrast * 255));
142
+ // Pre-compute lookup table for all possible pixel values
143
+ const lut = new Uint8Array(256);
144
+ for (let i = 0; i < 256; i++) {
145
+ const val = factor * (i - 128) + 128;
146
+ lut[i] = val < 0 ? 0 : (val > 255 ? 255 : Math.round(val));
147
+ }
107
148
  for (let i = 0; i < data.length; i += 4) {
108
- result[i] = Math.max(0, Math.min(255, factor * (data[i] - 128) + 128)); // R
109
- result[i + 1] = Math.max(0, Math.min(255, factor * (data[i + 1] - 128) + 128)); // G
110
- result[i + 2] = Math.max(0, Math.min(255, factor * (data[i + 2] - 128) + 128)); // B
149
+ result[i] = lut[data[i]]; // R
150
+ result[i + 1] = lut[data[i + 1]]; // G
151
+ result[i + 2] = lut[data[i + 2]]; // B
111
152
  result[i + 3] = data[i + 3]; // A
112
153
  }
113
154
  return result;
@@ -122,10 +163,16 @@ function adjustExposure(data, amount) {
122
163
  const result = new Uint8Array(data.length);
123
164
  const stops = Math.max(-3, Math.min(3, amount));
124
165
  const multiplier = Math.pow(2, stops);
166
+ // Pre-compute lookup table for all possible pixel values
167
+ const lut = new Uint8Array(256);
168
+ for (let i = 0; i < 256; i++) {
169
+ const val = i * multiplier;
170
+ lut[i] = val > 255 ? 255 : (val + 0.5) | 0;
171
+ }
125
172
  for (let i = 0; i < data.length; i += 4) {
126
- result[i] = Math.max(0, Math.min(255, data[i] * multiplier)); // R
127
- result[i + 1] = Math.max(0, Math.min(255, data[i + 1] * multiplier)); // G
128
- result[i + 2] = Math.max(0, Math.min(255, data[i + 2] * multiplier)); // B
173
+ result[i] = lut[data[i]]; // R
174
+ result[i + 1] = lut[data[i + 1]]; // G
175
+ result[i + 2] = lut[data[i + 2]]; // B
129
176
  result[i + 3] = data[i + 3]; // A
130
177
  }
131
178
  return result;
@@ -332,15 +379,12 @@ function crop(data, width, height, x, y, cropWidth, cropHeight) {
332
379
  const actualWidth = endX - startX;
333
380
  const actualHeight = endY - startY;
334
381
  const result = new Uint8Array(actualWidth * actualHeight * 4);
382
+ const rowBytes = actualWidth * 4;
383
+ // Copy entire rows at once for better performance
335
384
  for (let py = 0; py < actualHeight; py++) {
336
- for (let px = 0; px < actualWidth; px++) {
337
- const srcIdx = ((startY + py) * width + (startX + px)) * 4;
338
- const dstIdx = (py * actualWidth + px) * 4;
339
- result[dstIdx] = data[srcIdx];
340
- result[dstIdx + 1] = data[srcIdx + 1];
341
- result[dstIdx + 2] = data[srcIdx + 2];
342
- result[dstIdx + 3] = data[srcIdx + 3];
343
- }
385
+ const srcOffset = ((startY + py) * width + startX) * 4;
386
+ const dstOffset = py * rowBytes;
387
+ result.set(data.subarray(srcOffset, srcOffset + rowBytes), dstOffset);
344
388
  }
345
389
  return { data: result, width: actualWidth, height: actualHeight };
346
390
  }
@@ -419,46 +463,52 @@ function generateGaussianKernel(radius, sigma) {
419
463
  function gaussianBlur(data, width, height, radius = 1, sigma) {
420
464
  const clampedRadius = Math.max(1, Math.floor(radius));
421
465
  const kernel = generateGaussianKernel(clampedRadius, sigma);
466
+ const widthMinus1 = width - 1;
467
+ const heightMinus1 = height - 1;
422
468
  // Apply horizontal pass
423
469
  const temp = new Uint8Array(data.length);
424
470
  for (let y = 0; y < height; y++) {
471
+ const rowOffset = y * width * 4;
425
472
  for (let x = 0; x < width; x++) {
426
473
  let r = 0, g = 0, b = 0, a = 0;
427
474
  for (let kx = -clampedRadius; kx <= clampedRadius; kx++) {
428
- const px = Math.max(0, Math.min(width - 1, x + kx));
429
- const idx = (y * width + px) * 4;
475
+ const px = x + kx;
476
+ const clampedPx = px < 0 ? 0 : (px > widthMinus1 ? widthMinus1 : px);
477
+ const idx = rowOffset + clampedPx * 4;
430
478
  const weight = kernel[kx + clampedRadius];
431
479
  r += data[idx] * weight;
432
480
  g += data[idx + 1] * weight;
433
481
  b += data[idx + 2] * weight;
434
482
  a += data[idx + 3] * weight;
435
483
  }
436
- const outIdx = (y * width + x) * 4;
437
- temp[outIdx] = Math.round(r);
438
- temp[outIdx + 1] = Math.round(g);
439
- temp[outIdx + 2] = Math.round(b);
440
- temp[outIdx + 3] = Math.round(a);
484
+ const outIdx = rowOffset + x * 4;
485
+ temp[outIdx] = (r + 0.5) | 0;
486
+ temp[outIdx + 1] = (g + 0.5) | 0;
487
+ temp[outIdx + 2] = (b + 0.5) | 0;
488
+ temp[outIdx + 3] = (a + 0.5) | 0;
441
489
  }
442
490
  }
443
491
  // Apply vertical pass
444
492
  const result = new Uint8Array(data.length);
445
493
  for (let y = 0; y < height; y++) {
494
+ const rowOffset = y * width * 4;
446
495
  for (let x = 0; x < width; x++) {
447
496
  let r = 0, g = 0, b = 0, a = 0;
448
497
  for (let ky = -clampedRadius; ky <= clampedRadius; ky++) {
449
- const py = Math.max(0, Math.min(height - 1, y + ky));
450
- const idx = (py * width + x) * 4;
498
+ const py = y + ky;
499
+ const clampedPy = py < 0 ? 0 : (py > heightMinus1 ? heightMinus1 : py);
500
+ const idx = clampedPy * width * 4 + x * 4;
451
501
  const weight = kernel[ky + clampedRadius];
452
502
  r += temp[idx] * weight;
453
503
  g += temp[idx + 1] * weight;
454
504
  b += temp[idx + 2] * weight;
455
505
  a += temp[idx + 3] * weight;
456
506
  }
457
- const outIdx = (y * width + x) * 4;
458
- result[outIdx] = Math.round(r);
459
- result[outIdx + 1] = Math.round(g);
460
- result[outIdx + 2] = Math.round(b);
461
- result[outIdx + 3] = Math.round(a);
507
+ const outIdx = rowOffset + x * 4;
508
+ result[outIdx] = (r + 0.5) | 0;
509
+ result[outIdx + 1] = (g + 0.5) | 0;
510
+ result[outIdx + 2] = (b + 0.5) | 0;
511
+ result[outIdx + 3] = (a + 0.5) | 0;
462
512
  }
463
513
  }
464
514
  return result;
@@ -608,16 +658,30 @@ function rotate90(data, width, height) {
608
658
  */
609
659
  function rotate180(data, width, height) {
610
660
  const result = new Uint8Array(data.length);
611
- for (let y = 0; y < height; y++) {
612
- for (let x = 0; x < width; x++) {
613
- const srcIdx = (y * width + x) * 4;
614
- const dstX = width - 1 - x;
615
- const dstY = height - 1 - y;
616
- const dstIdx = (dstY * width + dstX) * 4;
617
- result[dstIdx] = data[srcIdx];
618
- result[dstIdx + 1] = data[srcIdx + 1];
619
- result[dstIdx + 2] = data[srcIdx + 2];
620
- result[dstIdx + 3] = data[srcIdx + 3];
661
+ // Only use Uint32Array optimization on little-endian systems to avoid byte order issues
662
+ if (IS_LITTLE_ENDIAN) {
663
+ // Use Uint32Array view for faster 4-byte (pixel) copying
664
+ // Note: Uint8Array buffers are guaranteed to be aligned for any TypedArray view
665
+ const src32 = new Uint32Array(data.buffer, data.byteOffset, width * height);
666
+ const dst32 = new Uint32Array(result.buffer, result.byteOffset, width * height);
667
+ const totalPixels = width * height;
668
+ for (let i = 0; i < totalPixels; i++) {
669
+ dst32[totalPixels - 1 - i] = src32[i];
670
+ }
671
+ }
672
+ else {
673
+ // Fallback for big-endian systems - byte-by-byte copying
674
+ for (let y = 0; y < height; y++) {
675
+ for (let x = 0; x < width; x++) {
676
+ const srcIdx = (y * width + x) * 4;
677
+ const dstX = width - 1 - x;
678
+ const dstY = height - 1 - y;
679
+ const dstIdx = (dstY * width + dstX) * 4;
680
+ result[dstIdx] = data[srcIdx];
681
+ result[dstIdx + 1] = data[srcIdx + 1];
682
+ result[dstIdx + 2] = data[srcIdx + 2];
683
+ result[dstIdx + 3] = data[srcIdx + 3];
684
+ }
621
685
  }
622
686
  }
623
687
  return result;
@@ -656,15 +720,33 @@ function rotate270(data, width, height) {
656
720
  */
657
721
  function flipHorizontal(data, width, height) {
658
722
  const result = new Uint8Array(data.length);
659
- for (let y = 0; y < height; y++) {
660
- for (let x = 0; x < width; x++) {
661
- const srcIdx = (y * width + x) * 4;
662
- const dstX = width - 1 - x;
663
- const dstIdx = (y * width + dstX) * 4;
664
- result[dstIdx] = data[srcIdx];
665
- result[dstIdx + 1] = data[srcIdx + 1];
666
- result[dstIdx + 2] = data[srcIdx + 2];
667
- result[dstIdx + 3] = data[srcIdx + 3];
723
+ // Only use Uint32Array optimization on little-endian systems to avoid byte order issues
724
+ if (IS_LITTLE_ENDIAN) {
725
+ // Use Uint32Array view for faster 4-byte (pixel) copying
726
+ // Note: Uint8Array buffers are guaranteed to be aligned for any TypedArray view
727
+ const src32 = new Uint32Array(data.buffer, data.byteOffset, width * height);
728
+ const dst32 = new Uint32Array(result.buffer, result.byteOffset, width * height);
729
+ for (let y = 0; y < height; y++) {
730
+ const rowStart = y * width;
731
+ for (let x = 0; x < width; x++) {
732
+ const srcIdx = rowStart + x;
733
+ const dstIdx = rowStart + (width - 1 - x);
734
+ dst32[dstIdx] = src32[srcIdx];
735
+ }
736
+ }
737
+ }
738
+ else {
739
+ // Fallback for big-endian systems - byte-by-byte copying
740
+ for (let y = 0; y < height; y++) {
741
+ for (let x = 0; x < width; x++) {
742
+ const srcIdx = (y * width + x) * 4;
743
+ const dstX = width - 1 - x;
744
+ const dstIdx = (y * width + dstX) * 4;
745
+ result[dstIdx] = data[srcIdx];
746
+ result[dstIdx + 1] = data[srcIdx + 1];
747
+ result[dstIdx + 2] = data[srcIdx + 2];
748
+ result[dstIdx + 3] = data[srcIdx + 3];
749
+ }
668
750
  }
669
751
  }
670
752
  return result;
@@ -678,16 +760,100 @@ function flipHorizontal(data, width, height) {
678
760
  */
679
761
  function flipVertical(data, width, height) {
680
762
  const result = new Uint8Array(data.length);
763
+ const rowBytes = width * 4;
764
+ // Copy entire rows at once for better performance
681
765
  for (let y = 0; y < height; y++) {
682
- for (let x = 0; x < width; x++) {
683
- const srcIdx = (y * width + x) * 4;
684
- const dstY = height - 1 - y;
685
- const dstIdx = (dstY * width + x) * 4;
686
- result[dstIdx] = data[srcIdx];
687
- result[dstIdx + 1] = data[srcIdx + 1];
688
- result[dstIdx + 2] = data[srcIdx + 2];
689
- result[dstIdx + 3] = data[srcIdx + 3];
690
- }
766
+ const srcOffset = y * rowBytes;
767
+ const dstOffset = (height - 1 - y) * rowBytes;
768
+ result.set(data.subarray(srcOffset, srcOffset + rowBytes), dstOffset);
769
+ }
770
+ return result;
771
+ }
772
+ /**
773
+ * Convert RGB color to CMYK color space
774
+ * @param r Red component (0-255)
775
+ * @param g Green component (0-255)
776
+ * @param b Blue component (0-255)
777
+ * @returns CMYK values: [c (0-1), m (0-1), y (0-1), k (0-1)]
778
+ */
779
+ function rgbToCmyk(r, g, b) {
780
+ // Normalize RGB values to 0-1 range
781
+ const rNorm = r / 255;
782
+ const gNorm = g / 255;
783
+ const bNorm = b / 255;
784
+ // Calculate K (key/black)
785
+ const k = 1 - Math.max(rNorm, gNorm, bNorm);
786
+ // If K is 1 (pure black), CMY are undefined but conventionally set to 0
787
+ if (k === 1) {
788
+ return [0, 0, 0, 1];
789
+ }
790
+ // Calculate CMY components
791
+ const c = (1 - rNorm - k) / (1 - k);
792
+ const m = (1 - gNorm - k) / (1 - k);
793
+ const y = (1 - bNorm - k) / (1 - k);
794
+ return [c, m, y, k];
795
+ }
796
+ /**
797
+ * Convert CMYK color to RGB color space
798
+ * @param c Cyan component (0-1)
799
+ * @param m Magenta component (0-1)
800
+ * @param y Yellow component (0-1)
801
+ * @param k Key/Black component (0-1)
802
+ * @returns RGB values: [r (0-255), g (0-255), b (0-255)]
803
+ */
804
+ function cmykToRgb(c, m, y, k) {
805
+ // Convert CMYK to RGB
806
+ const r = 255 * (1 - c) * (1 - k);
807
+ const g = 255 * (1 - m) * (1 - k);
808
+ const b = 255 * (1 - y) * (1 - k);
809
+ return [
810
+ Math.round(Math.max(0, Math.min(255, r))),
811
+ Math.round(Math.max(0, Math.min(255, g))),
812
+ Math.round(Math.max(0, Math.min(255, b))),
813
+ ];
814
+ }
815
+ /**
816
+ * Convert RGBA image data to CMYK representation
817
+ * Returns a Float32Array with 4 values per pixel (C, M, Y, K) in 0-1 range
818
+ * @param data Image data (RGBA)
819
+ * @returns CMYK image data as Float32Array (4 values per pixel)
820
+ */
821
+ function rgbaToCmyk(data) {
822
+ const pixelCount = data.length / 4;
823
+ const result = new Float32Array(pixelCount * 4);
824
+ for (let i = 0; i < data.length; i += 4) {
825
+ const r = data[i];
826
+ const g = data[i + 1];
827
+ const b = data[i + 2];
828
+ const [c, m, y, k] = rgbToCmyk(r, g, b);
829
+ const outIdx = (i / 4) * 4;
830
+ result[outIdx] = c;
831
+ result[outIdx + 1] = m;
832
+ result[outIdx + 2] = y;
833
+ result[outIdx + 3] = k;
834
+ }
835
+ return result;
836
+ }
837
+ /**
838
+ * Convert CMYK image data to RGBA representation
839
+ * @param cmykData CMYK image data (4 values per pixel in 0-1 range)
840
+ * @param alpha Optional alpha value for all pixels (0-255, default: 255)
841
+ * @returns RGBA image data
842
+ */
843
+ function cmykToRgba(cmykData, alpha = 255) {
844
+ const pixelCount = cmykData.length / 4;
845
+ const result = new Uint8Array(pixelCount * 4);
846
+ for (let i = 0; i < cmykData.length; i += 4) {
847
+ const c = cmykData[i];
848
+ const m = cmykData[i + 1];
849
+ const y = cmykData[i + 2];
850
+ const k = cmykData[i + 3];
851
+ const [r, g, b] = cmykToRgb(c, m, y, k);
852
+ const outIdx = i;
853
+ result[outIdx] = r;
854
+ result[outIdx + 1] = g;
855
+ result[outIdx + 2] = b;
856
+ result[outIdx + 3] = alpha;
691
857
  }
692
858
  return result;
693
859
  }
@@ -1,10 +1,14 @@
1
1
  /**
2
- * Basic baseline JPEG decoder implementation
3
- * Supports baseline DCT JPEG images (the most common format)
2
+ * JPEG decoder implementation supporting both baseline and progressive DCT
4
3
  *
5
- * This is a simplified implementation that handles common JPEG files.
4
+ * Supports:
5
+ * - Baseline DCT (SOF0) - Sequential encoding
6
+ * - Progressive DCT (SOF2) - Multi-scan encoding with spectral selection and successive approximation
7
+ *
8
+ * This is a pure JavaScript implementation that handles common JPEG files.
6
9
  * For complex or non-standard JPEGs, the ImageDecoder API fallback is preferred.
7
10
  */
11
+ import type { ImageDecoderOptions } from "../types.js";
8
12
  export declare class JPEGDecoder {
9
13
  private data;
10
14
  private pos;
@@ -17,7 +21,15 @@ export declare class JPEGDecoder {
17
21
  private restartInterval;
18
22
  private bitBuffer;
19
23
  private bitCount;
20
- constructor(data: Uint8Array);
24
+ private options;
25
+ private isProgressive;
26
+ private spectralStart;
27
+ private spectralEnd;
28
+ private successiveHigh;
29
+ private successiveLow;
30
+ private scanComponentIds;
31
+ private eobRun;
32
+ constructor(data: Uint8Array, settings?: ImageDecoderOptions);
21
33
  decode(): Uint8Array;
22
34
  private readMarker;
23
35
  private readUint16;
@@ -33,6 +45,7 @@ export declare class JPEGDecoder {
33
45
  private decodeHuffman;
34
46
  private readBit;
35
47
  private receiveBits;
48
+ private receiveUnsignedBits;
36
49
  private idct;
37
50
  private convertToRGB;
38
51
  }