cross-image 0.2.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +615 -333
- package/esm/mod.d.ts +6 -4
- package/esm/mod.js +4 -2
- package/esm/src/formats/apng.d.ts +7 -5
- package/esm/src/formats/apng.js +15 -10
- package/esm/src/formats/ascii.d.ts +3 -3
- package/esm/src/formats/ascii.js +1 -1
- package/esm/src/formats/avif.d.ts +3 -3
- package/esm/src/formats/avif.js +17 -7
- package/esm/src/formats/bmp.d.ts +3 -3
- package/esm/src/formats/bmp.js +2 -2
- package/esm/src/formats/dng.d.ts +1 -1
- package/esm/src/formats/dng.js +1 -1
- package/esm/src/formats/gif.d.ts +5 -5
- package/esm/src/formats/gif.js +17 -13
- package/esm/src/formats/heic.d.ts +3 -3
- package/esm/src/formats/heic.js +17 -7
- package/esm/src/formats/ico.d.ts +3 -3
- package/esm/src/formats/ico.js +4 -4
- package/esm/src/formats/jpeg.d.ts +3 -3
- package/esm/src/formats/jpeg.js +23 -11
- package/esm/src/formats/pam.d.ts +3 -3
- package/esm/src/formats/pam.js +2 -2
- package/esm/src/formats/pcx.d.ts +3 -3
- package/esm/src/formats/pcx.js +2 -2
- package/esm/src/formats/png.d.ts +4 -3
- package/esm/src/formats/png.js +9 -3
- package/esm/src/formats/png_base.d.ts +42 -1
- package/esm/src/formats/png_base.js +200 -10
- package/esm/src/formats/ppm.d.ts +3 -3
- package/esm/src/formats/ppm.js +2 -2
- package/esm/src/formats/tiff.d.ts +7 -18
- package/esm/src/formats/tiff.js +162 -27
- package/esm/src/formats/webp.d.ts +3 -3
- package/esm/src/formats/webp.js +11 -8
- package/esm/src/image.d.ts +26 -3
- package/esm/src/image.js +66 -22
- package/esm/src/types.d.ts +122 -4
- package/esm/src/utils/base64.d.ts +32 -0
- package/esm/src/utils/base64.js +173 -0
- package/esm/src/utils/gif_decoder.d.ts +4 -1
- package/esm/src/utils/gif_decoder.js +91 -65
- package/esm/src/utils/gif_encoder.d.ts +3 -1
- package/esm/src/utils/gif_encoder.js +4 -2
- package/esm/src/utils/image_processing.d.ts +31 -0
- package/esm/src/utils/image_processing.js +232 -70
- package/esm/src/utils/jpeg_decoder.d.ts +17 -4
- package/esm/src/utils/jpeg_decoder.js +448 -83
- package/esm/src/utils/jpeg_encoder.d.ts +15 -1
- package/esm/src/utils/jpeg_encoder.js +263 -24
- package/esm/src/utils/resize.js +51 -20
- package/esm/src/utils/tiff_deflate.d.ts +18 -0
- package/esm/src/utils/tiff_deflate.js +27 -0
- package/esm/src/utils/tiff_packbits.d.ts +24 -0
- package/esm/src/utils/tiff_packbits.js +90 -0
- package/esm/src/utils/webp_decoder.d.ts +3 -1
- package/esm/src/utils/webp_decoder.js +144 -63
- package/esm/src/utils/webp_encoder.js +5 -11
- package/package.json +1 -1
- package/script/mod.d.ts +6 -4
- package/script/mod.js +13 -3
- package/script/src/formats/apng.d.ts +7 -5
- package/script/src/formats/apng.js +15 -10
- package/script/src/formats/ascii.d.ts +3 -3
- package/script/src/formats/ascii.js +1 -1
- package/script/src/formats/avif.d.ts +3 -3
- package/script/src/formats/avif.js +17 -7
- package/script/src/formats/bmp.d.ts +3 -3
- package/script/src/formats/bmp.js +2 -2
- package/script/src/formats/dng.d.ts +1 -1
- package/script/src/formats/dng.js +1 -1
- package/script/src/formats/gif.d.ts +5 -5
- package/script/src/formats/gif.js +17 -13
- package/script/src/formats/heic.d.ts +3 -3
- package/script/src/formats/heic.js +17 -7
- package/script/src/formats/ico.d.ts +3 -3
- package/script/src/formats/ico.js +4 -4
- package/script/src/formats/jpeg.d.ts +3 -3
- package/script/src/formats/jpeg.js +23 -11
- package/script/src/formats/pam.d.ts +3 -3
- package/script/src/formats/pam.js +2 -2
- package/script/src/formats/pcx.d.ts +3 -3
- package/script/src/formats/pcx.js +2 -2
- package/script/src/formats/png.d.ts +4 -3
- package/script/src/formats/png.js +9 -3
- package/script/src/formats/png_base.d.ts +42 -1
- package/script/src/formats/png_base.js +200 -10
- package/script/src/formats/ppm.d.ts +3 -3
- package/script/src/formats/ppm.js +2 -2
- package/script/src/formats/tiff.d.ts +7 -18
- package/script/src/formats/tiff.js +162 -27
- package/script/src/formats/webp.d.ts +3 -3
- package/script/src/formats/webp.js +11 -8
- package/script/src/image.d.ts +26 -3
- package/script/src/image.js +64 -20
- package/script/src/types.d.ts +122 -4
- package/script/src/utils/base64.d.ts +32 -0
- package/script/src/utils/base64.js +179 -0
- package/script/src/utils/gif_decoder.d.ts +4 -1
- package/script/src/utils/gif_decoder.js +91 -65
- package/script/src/utils/gif_encoder.d.ts +3 -1
- package/script/src/utils/gif_encoder.js +4 -2
- package/script/src/utils/image_processing.d.ts +31 -0
- package/script/src/utils/image_processing.js +236 -70
- package/script/src/utils/jpeg_decoder.d.ts +17 -4
- package/script/src/utils/jpeg_decoder.js +448 -83
- package/script/src/utils/jpeg_encoder.d.ts +15 -1
- package/script/src/utils/jpeg_encoder.js +263 -24
- package/script/src/utils/resize.js +51 -20
- package/script/src/utils/tiff_deflate.d.ts +18 -0
- package/script/src/utils/tiff_deflate.js +31 -0
- package/script/src/utils/tiff_packbits.d.ts +24 -0
- package/script/src/utils/tiff_packbits.js +94 -0
- package/script/src/utils/webp_decoder.d.ts +3 -1
- package/script/src/utils/webp_decoder.js +144 -63
- package/script/src/utils/webp_encoder.js +5 -11
|
@@ -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 =
|
|
54
|
-
const
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
result[baseIdx
|
|
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
|
|
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] =
|
|
90
|
-
result[i + 1] =
|
|
91
|
-
result[i + 2] =
|
|
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] =
|
|
109
|
-
result[i + 1] =
|
|
110
|
-
result[i + 2] =
|
|
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] =
|
|
127
|
-
result[i + 1] =
|
|
128
|
-
result[i + 2] =
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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 =
|
|
429
|
-
const
|
|
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 =
|
|
437
|
-
temp[outIdx] =
|
|
438
|
-
temp[outIdx + 1] =
|
|
439
|
-
temp[outIdx + 2] =
|
|
440
|
-
temp[outIdx + 3] =
|
|
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 =
|
|
450
|
-
const
|
|
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 =
|
|
458
|
-
result[outIdx] =
|
|
459
|
-
result[outIdx + 1] =
|
|
460
|
-
result[outIdx + 2] =
|
|
461
|
-
result[outIdx + 3] =
|
|
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
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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
|
-
*
|
|
3
|
-
* Supports baseline DCT JPEG images (the most common format)
|
|
2
|
+
* JPEG decoder implementation supporting both baseline and progressive DCT
|
|
4
3
|
*
|
|
5
|
-
*
|
|
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
|
-
|
|
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
|
}
|