cross-image 0.2.3 → 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.
- package/LICENSE +21 -21
- package/README.md +292 -74
- package/esm/mod.d.ts +6 -4
- package/esm/mod.js +4 -2
- package/esm/src/formats/apng.d.ts +17 -5
- package/esm/src/formats/apng.js +104 -9
- package/esm/src/formats/ascii.d.ts +13 -3
- package/esm/src/formats/ascii.js +25 -1
- package/esm/src/formats/avif.d.ts +96 -0
- package/esm/src/formats/avif.js +607 -0
- package/esm/src/formats/bmp.d.ts +13 -3
- package/esm/src/formats/bmp.js +75 -2
- package/esm/src/formats/dng.d.ts +14 -2
- package/esm/src/formats/dng.js +27 -5
- package/esm/src/formats/gif.d.ts +18 -5
- package/esm/src/formats/gif.js +160 -14
- package/esm/src/formats/heic.d.ts +96 -0
- package/esm/src/formats/heic.js +608 -0
- package/esm/src/formats/ico.d.ts +13 -3
- package/esm/src/formats/ico.js +32 -4
- package/esm/src/formats/jpeg.d.ts +10 -3
- package/esm/src/formats/jpeg.js +99 -11
- package/esm/src/formats/pam.d.ts +13 -3
- package/esm/src/formats/pam.js +68 -2
- package/esm/src/formats/pcx.d.ts +13 -3
- package/esm/src/formats/pcx.js +47 -2
- package/esm/src/formats/png.d.ts +15 -3
- package/esm/src/formats/png.js +89 -2
- package/esm/src/formats/png_base.js +2 -5
- package/esm/src/formats/ppm.d.ts +13 -3
- package/esm/src/formats/ppm.js +36 -2
- package/esm/src/formats/tiff.d.ts +14 -18
- package/esm/src/formats/tiff.js +219 -20
- package/esm/src/formats/webp.d.ts +10 -3
- package/esm/src/formats/webp.js +103 -8
- package/esm/src/image.d.ts +20 -3
- package/esm/src/image.js +65 -21
- package/esm/src/types.d.ts +74 -4
- package/esm/src/utils/gif_decoder.d.ts +4 -1
- package/esm/src/utils/gif_decoder.js +91 -65
- package/esm/src/utils/image_processing.js +144 -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 +18 -1
- package/script/mod.d.ts +6 -4
- package/script/mod.js +7 -3
- package/script/src/formats/apng.d.ts +17 -5
- package/script/src/formats/apng.js +104 -9
- package/script/src/formats/ascii.d.ts +13 -3
- package/script/src/formats/ascii.js +25 -1
- package/script/src/formats/avif.d.ts +96 -0
- package/script/src/formats/avif.js +611 -0
- package/script/src/formats/bmp.d.ts +13 -3
- package/script/src/formats/bmp.js +75 -2
- package/script/src/formats/dng.d.ts +14 -2
- package/script/src/formats/dng.js +27 -5
- package/script/src/formats/gif.d.ts +18 -5
- package/script/src/formats/gif.js +160 -14
- package/script/src/formats/heic.d.ts +96 -0
- package/script/src/formats/heic.js +612 -0
- package/script/src/formats/ico.d.ts +13 -3
- package/script/src/formats/ico.js +32 -4
- package/script/src/formats/jpeg.d.ts +10 -3
- package/script/src/formats/jpeg.js +99 -11
- package/script/src/formats/pam.d.ts +13 -3
- package/script/src/formats/pam.js +68 -2
- package/script/src/formats/pcx.d.ts +13 -3
- package/script/src/formats/pcx.js +47 -2
- package/script/src/formats/png.d.ts +15 -3
- package/script/src/formats/png.js +89 -2
- package/script/src/formats/png_base.js +2 -5
- package/script/src/formats/ppm.d.ts +13 -3
- package/script/src/formats/ppm.js +36 -2
- package/script/src/formats/tiff.d.ts +14 -18
- package/script/src/formats/tiff.js +219 -20
- package/script/src/formats/webp.d.ts +10 -3
- package/script/src/formats/webp.js +103 -8
- package/script/src/image.d.ts +20 -3
- package/script/src/image.js +64 -20
- package/script/src/types.d.ts +74 -4
- package/script/src/utils/gif_decoder.d.ts +4 -1
- package/script/src/utils/gif_decoder.js +91 -65
- package/script/src/utils/image_processing.js +144 -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
|
@@ -5,15 +5,20 @@
|
|
|
5
5
|
* This is a simplified implementation focusing on correctness over performance.
|
|
6
6
|
* For production use with better quality/size, the OffscreenCanvas API is preferred.
|
|
7
7
|
*/
|
|
8
|
+
export interface JPEGEncoderOptions {
|
|
9
|
+
quality?: number;
|
|
10
|
+
progressive?: boolean;
|
|
11
|
+
}
|
|
8
12
|
export declare class JPEGEncoder {
|
|
9
13
|
private quality;
|
|
14
|
+
private progressive;
|
|
10
15
|
private luminanceQuantTable;
|
|
11
16
|
private chrominanceQuantTable;
|
|
12
17
|
private dcLuminanceHuffman;
|
|
13
18
|
private acLuminanceHuffman;
|
|
14
19
|
private dcChrominanceHuffman;
|
|
15
20
|
private acChrominanceHuffman;
|
|
16
|
-
constructor(
|
|
21
|
+
constructor(options?: JPEGEncoderOptions);
|
|
17
22
|
private initQuantizationTables;
|
|
18
23
|
private initHuffmanTables;
|
|
19
24
|
private buildHuffmanTable;
|
|
@@ -21,9 +26,18 @@ export declare class JPEGEncoder {
|
|
|
21
26
|
private writeAPP0;
|
|
22
27
|
private writeDQT;
|
|
23
28
|
private writeSOF0;
|
|
29
|
+
private writeSOF2;
|
|
24
30
|
private writeDHT;
|
|
25
31
|
private writeHuffmanTable;
|
|
26
32
|
private writeSOS;
|
|
33
|
+
private writeProgressiveSOS;
|
|
34
|
+
private encodeProgressive;
|
|
35
|
+
private dctAndQuantize;
|
|
36
|
+
private performDCT2D;
|
|
37
|
+
private encodeProgressiveDCScan;
|
|
38
|
+
private encodeProgressiveACScanSingle;
|
|
39
|
+
private encodeOnlyDC;
|
|
40
|
+
private encodeOnlyAC;
|
|
27
41
|
private encodeScan;
|
|
28
42
|
private encodeBlock;
|
|
29
43
|
private forwardDCT;
|
|
@@ -694,13 +694,19 @@ class BitWriter {
|
|
|
694
694
|
}
|
|
695
695
|
}
|
|
696
696
|
class JPEGEncoder {
|
|
697
|
-
constructor(
|
|
697
|
+
constructor(options = {}) {
|
|
698
698
|
Object.defineProperty(this, "quality", {
|
|
699
699
|
enumerable: true,
|
|
700
700
|
configurable: true,
|
|
701
701
|
writable: true,
|
|
702
702
|
value: void 0
|
|
703
703
|
});
|
|
704
|
+
Object.defineProperty(this, "progressive", {
|
|
705
|
+
enumerable: true,
|
|
706
|
+
configurable: true,
|
|
707
|
+
writable: true,
|
|
708
|
+
value: void 0
|
|
709
|
+
});
|
|
704
710
|
Object.defineProperty(this, "luminanceQuantTable", {
|
|
705
711
|
enumerable: true,
|
|
706
712
|
configurable: true,
|
|
@@ -737,14 +743,13 @@ class JPEGEncoder {
|
|
|
737
743
|
writable: true,
|
|
738
744
|
value: void 0
|
|
739
745
|
});
|
|
740
|
-
this.quality = Math.max(1, Math.min(100, quality));
|
|
746
|
+
this.quality = Math.max(1, Math.min(100, options.quality ?? 85));
|
|
747
|
+
this.progressive = options.progressive ?? false;
|
|
741
748
|
this.initQuantizationTables();
|
|
742
749
|
this.initHuffmanTables();
|
|
743
750
|
}
|
|
744
751
|
initQuantizationTables() {
|
|
745
|
-
const scaleFactor = this.quality < 50
|
|
746
|
-
? 5000 / this.quality
|
|
747
|
-
: 200 - this.quality * 2;
|
|
752
|
+
const scaleFactor = this.quality < 50 ? 5000 / this.quality : 200 - this.quality * 2;
|
|
748
753
|
for (let i = 0; i < 64; i++) {
|
|
749
754
|
let lumVal = Math.floor((STANDARD_LUMINANCE_QUANT_TABLE[i] * scaleFactor + 50) / 100);
|
|
750
755
|
let chromVal = Math.floor((STANDARD_CHROMINANCE_QUANT_TABLE[i] * scaleFactor + 50) / 100);
|
|
@@ -761,8 +766,8 @@ class JPEGEncoder {
|
|
|
761
766
|
this.acChrominanceHuffman = this.buildHuffmanTable(STD_AC_CHROMINANCE_NRCODES, STD_AC_CHROMINANCE_VALUES);
|
|
762
767
|
}
|
|
763
768
|
buildHuffmanTable(nrcodes, values) {
|
|
764
|
-
const codes = new
|
|
765
|
-
const sizes = new
|
|
769
|
+
const codes = new Uint32Array(256);
|
|
770
|
+
const sizes = new Uint8Array(256);
|
|
766
771
|
let code = 0;
|
|
767
772
|
let valueIndex = 0;
|
|
768
773
|
for (let length = 1; length <= 16; length++) {
|
|
@@ -785,16 +790,28 @@ class JPEGEncoder {
|
|
|
785
790
|
this.writeAPP0(output, dpiX, dpiY);
|
|
786
791
|
// DQT (Define Quantization Tables)
|
|
787
792
|
this.writeDQT(output);
|
|
788
|
-
//
|
|
789
|
-
this.
|
|
793
|
+
// SOF (Start of Frame - either baseline or progressive)
|
|
794
|
+
if (this.progressive) {
|
|
795
|
+
this.writeSOF2(output, width, height);
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
this.writeSOF0(output, width, height);
|
|
799
|
+
}
|
|
790
800
|
// DHT (Define Huffman Tables)
|
|
791
801
|
this.writeDHT(output);
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
802
|
+
if (this.progressive) {
|
|
803
|
+
// Progressive encoding: multiple scans
|
|
804
|
+
this.encodeProgressive(output, width, height, rgba);
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
// Baseline encoding: single scan
|
|
808
|
+
// SOS (Start of Scan)
|
|
809
|
+
this.writeSOS(output);
|
|
810
|
+
// Encode scan data
|
|
811
|
+
const scanData = this.encodeScan(width, height, rgba);
|
|
812
|
+
for (let i = 0; i < scanData.length; i++) {
|
|
813
|
+
output.push(scanData[i]);
|
|
814
|
+
}
|
|
798
815
|
}
|
|
799
816
|
// EOI (End of Image)
|
|
800
817
|
output.push(0xff, 0xd9);
|
|
@@ -846,6 +863,26 @@ class JPEGEncoder {
|
|
|
846
863
|
output.push(0x11); // Sampling factors (1x1)
|
|
847
864
|
output.push(0x01); // Quantization table 1
|
|
848
865
|
}
|
|
866
|
+
writeSOF2(output, width, height) {
|
|
867
|
+
output.push(0xff, 0xc2); // SOF2 marker (Progressive DCT)
|
|
868
|
+
output.push(0x00, 0x11); // Length (17 bytes)
|
|
869
|
+
output.push(0x08); // Precision (8 bits)
|
|
870
|
+
output.push((height >> 8) & 0xff, height & 0xff); // Height
|
|
871
|
+
output.push((width >> 8) & 0xff, width & 0xff); // Width
|
|
872
|
+
output.push(0x03); // Number of components (3 = YCbCr)
|
|
873
|
+
// Y component
|
|
874
|
+
output.push(0x01); // Component ID
|
|
875
|
+
output.push(0x11); // Sampling factors (1x1)
|
|
876
|
+
output.push(0x00); // Quantization table 0
|
|
877
|
+
// Cb component
|
|
878
|
+
output.push(0x02); // Component ID
|
|
879
|
+
output.push(0x11); // Sampling factors (1x1)
|
|
880
|
+
output.push(0x01); // Quantization table 1
|
|
881
|
+
// Cr component
|
|
882
|
+
output.push(0x03); // Component ID
|
|
883
|
+
output.push(0x11); // Sampling factors (1x1)
|
|
884
|
+
output.push(0x01); // Quantization table 1
|
|
885
|
+
}
|
|
849
886
|
writeDHT(output) {
|
|
850
887
|
// DC Luminance
|
|
851
888
|
this.writeHuffmanTable(output, 0x00, STD_DC_LUMINANCE_NRCODES, STD_DC_LUMINANCE_VALUES);
|
|
@@ -893,6 +930,200 @@ class JPEGEncoder {
|
|
|
893
930
|
output.push(0x3f); // End of spectral selection
|
|
894
931
|
output.push(0x00); // Successive approximation
|
|
895
932
|
}
|
|
933
|
+
writeProgressiveSOS(output, componentIds, spectralStart, spectralEnd, successiveHigh, successiveLow) {
|
|
934
|
+
output.push(0xff, 0xda); // SOS marker
|
|
935
|
+
// Length depends on number of components
|
|
936
|
+
const length = 6 + componentIds.length * 2;
|
|
937
|
+
output.push((length >> 8) & 0xff, length & 0xff);
|
|
938
|
+
output.push(componentIds.length); // Number of components
|
|
939
|
+
for (const id of componentIds) {
|
|
940
|
+
output.push(id); // Component ID
|
|
941
|
+
if (id === 1) {
|
|
942
|
+
// Y component uses table 0
|
|
943
|
+
output.push(0x00); // DC table 0, AC table 0
|
|
944
|
+
}
|
|
945
|
+
else {
|
|
946
|
+
// Cb/Cr components use table 1
|
|
947
|
+
output.push(0x11); // DC table 1, AC table 1
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
output.push(spectralStart); // Start of spectral selection (Ss)
|
|
951
|
+
output.push(spectralEnd); // End of spectral selection (Se)
|
|
952
|
+
output.push((successiveHigh << 4) | successiveLow); // Successive approximation (Ah, Al)
|
|
953
|
+
}
|
|
954
|
+
encodeProgressive(output, width, height, rgba) {
|
|
955
|
+
// Simplified progressive encoding:
|
|
956
|
+
// Scan 1: Interleaved DC coefficients for all components (Ss=0, Se=0)
|
|
957
|
+
// Scans 2-4: Non-interleaved AC coefficients (Ss=1, Se=63) per component.
|
|
958
|
+
//
|
|
959
|
+
// Note: Some JPEG readers are picky about interleaved AC scans in
|
|
960
|
+
// progressive images, so we emit AC scans as single-component for broader
|
|
961
|
+
// compatibility.
|
|
962
|
+
// For a more sophisticated progressive JPEG, we would use multiple scans
|
|
963
|
+
// with different spectral selections and successive approximation values.
|
|
964
|
+
// This basic implementation provides progressive structure while keeping
|
|
965
|
+
// complexity manageable.
|
|
966
|
+
// Pre-process all blocks (DCT + quantization)
|
|
967
|
+
const mcuWidth = Math.ceil(width / 8);
|
|
968
|
+
const mcuHeight = Math.ceil(height / 8);
|
|
969
|
+
const yBlocks = [];
|
|
970
|
+
const cbBlocks = [];
|
|
971
|
+
const crBlocks = [];
|
|
972
|
+
for (let mcuY = 0; mcuY < mcuHeight; mcuY++) {
|
|
973
|
+
for (let mcuX = 0; mcuX < mcuWidth; mcuX++) {
|
|
974
|
+
const yBlock = new Float32Array(64);
|
|
975
|
+
const cbBlock = new Float32Array(64);
|
|
976
|
+
const crBlock = new Float32Array(64);
|
|
977
|
+
// Extract 8x8 block and convert RGB to YCbCr
|
|
978
|
+
for (let y = 0; y < 8; y++) {
|
|
979
|
+
for (let x = 0; x < 8; x++) {
|
|
980
|
+
const px = mcuX * 8 + x;
|
|
981
|
+
const py = mcuY * 8 + y;
|
|
982
|
+
if (px < width && py < height) {
|
|
983
|
+
const offset = (py * width + px) * 4;
|
|
984
|
+
const r = rgba[offset];
|
|
985
|
+
const g = rgba[offset + 1];
|
|
986
|
+
const b = rgba[offset + 2];
|
|
987
|
+
// RGB to YCbCr conversion
|
|
988
|
+
const yVal = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
989
|
+
const cbVal = -0.168736 * r - 0.331264 * g + 0.5 * b + 128;
|
|
990
|
+
const crVal = 0.5 * r - 0.418688 * g - 0.081312 * b + 128;
|
|
991
|
+
yBlock[y * 8 + x] = yVal - 128; // Level shift
|
|
992
|
+
cbBlock[y * 8 + x] = cbVal - 128;
|
|
993
|
+
crBlock[y * 8 + x] = crVal - 128;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
// DCT and quantize
|
|
998
|
+
yBlocks.push(this.dctAndQuantize(yBlock, this.luminanceQuantTable));
|
|
999
|
+
cbBlocks.push(this.dctAndQuantize(cbBlock, this.chrominanceQuantTable));
|
|
1000
|
+
crBlocks.push(this.dctAndQuantize(crBlock, this.chrominanceQuantTable));
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
// Scan 1: DC-only (interleaved across all components)
|
|
1004
|
+
this.writeProgressiveSOS(output, [1, 2, 3], 0, 0, 0, 0);
|
|
1005
|
+
const dcScanData = this.encodeProgressiveDCScan(yBlocks, cbBlocks, crBlocks);
|
|
1006
|
+
for (let i = 0; i < dcScanData.length; i++) {
|
|
1007
|
+
output.push(dcScanData[i]);
|
|
1008
|
+
}
|
|
1009
|
+
// Scan 2: AC coefficients (Y only)
|
|
1010
|
+
this.writeProgressiveSOS(output, [1], 1, 63, 0, 0);
|
|
1011
|
+
const yAcScanData = this.encodeProgressiveACScanSingle(yBlocks, this.acLuminanceHuffman);
|
|
1012
|
+
for (let i = 0; i < yAcScanData.length; i++) {
|
|
1013
|
+
output.push(yAcScanData[i]);
|
|
1014
|
+
}
|
|
1015
|
+
// Scan 3: AC coefficients (Cb only)
|
|
1016
|
+
this.writeProgressiveSOS(output, [2], 1, 63, 0, 0);
|
|
1017
|
+
const cbAcScanData = this.encodeProgressiveACScanSingle(cbBlocks, this.acChrominanceHuffman);
|
|
1018
|
+
for (let i = 0; i < cbAcScanData.length; i++) {
|
|
1019
|
+
output.push(cbAcScanData[i]);
|
|
1020
|
+
}
|
|
1021
|
+
// Scan 4: AC coefficients (Cr only)
|
|
1022
|
+
this.writeProgressiveSOS(output, [3], 1, 63, 0, 0);
|
|
1023
|
+
const crAcScanData = this.encodeProgressiveACScanSingle(crBlocks, this.acChrominanceHuffman);
|
|
1024
|
+
for (let i = 0; i < crAcScanData.length; i++) {
|
|
1025
|
+
output.push(crAcScanData[i]);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
dctAndQuantize(block, quantTable) {
|
|
1029
|
+
// Perform DCT
|
|
1030
|
+
const dct = this.performDCT2D(block);
|
|
1031
|
+
// Quantize
|
|
1032
|
+
const quantized = new Int32Array(64);
|
|
1033
|
+
for (let i = 0; i < 64; i++) {
|
|
1034
|
+
quantized[i] = Math.round(dct[i] / quantTable[i]);
|
|
1035
|
+
}
|
|
1036
|
+
return quantized;
|
|
1037
|
+
}
|
|
1038
|
+
performDCT2D(block) {
|
|
1039
|
+
const output = new Float32Array(64);
|
|
1040
|
+
for (let v = 0; v < 8; v++) {
|
|
1041
|
+
for (let u = 0; u < 8; u++) {
|
|
1042
|
+
let sum = 0;
|
|
1043
|
+
for (let y = 0; y < 8; y++) {
|
|
1044
|
+
for (let x = 0; x < 8; x++) {
|
|
1045
|
+
const cu = u === 0 ? 1 / Math.sqrt(2) : 1;
|
|
1046
|
+
const cv = v === 0 ? 1 / Math.sqrt(2) : 1;
|
|
1047
|
+
sum += block[y * 8 + x] *
|
|
1048
|
+
Math.cos((2 * x + 1) * u * Math.PI / 16) *
|
|
1049
|
+
Math.cos((2 * y + 1) * v * Math.PI / 16) *
|
|
1050
|
+
cu * cv;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
output[v * 8 + u] = sum / 4;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
return output;
|
|
1057
|
+
}
|
|
1058
|
+
encodeProgressiveDCScan(yBlocks, cbBlocks, crBlocks) {
|
|
1059
|
+
// Encode only DC coefficients for progressive JPEG
|
|
1060
|
+
const bitWriter = new BitWriter();
|
|
1061
|
+
let dcY = 0, dcCb = 0, dcCr = 0;
|
|
1062
|
+
for (let i = 0; i < yBlocks.length; i++) {
|
|
1063
|
+
// Encode only DC coefficients
|
|
1064
|
+
dcY = this.encodeOnlyDC(yBlocks[i][0], dcY, this.dcLuminanceHuffman, bitWriter);
|
|
1065
|
+
dcCb = this.encodeOnlyDC(cbBlocks[i][0], dcCb, this.dcChrominanceHuffman, bitWriter);
|
|
1066
|
+
dcCr = this.encodeOnlyDC(crBlocks[i][0], dcCr, this.dcChrominanceHuffman, bitWriter);
|
|
1067
|
+
}
|
|
1068
|
+
bitWriter.flush();
|
|
1069
|
+
return Array.from(bitWriter.getBytes());
|
|
1070
|
+
}
|
|
1071
|
+
encodeProgressiveACScanSingle(blocks, acTable) {
|
|
1072
|
+
// Encode only AC coefficients for progressive JPEG (single component)
|
|
1073
|
+
const bitWriter = new BitWriter();
|
|
1074
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
1075
|
+
this.encodeOnlyAC(blocks[i], acTable, bitWriter);
|
|
1076
|
+
}
|
|
1077
|
+
bitWriter.flush();
|
|
1078
|
+
return Array.from(bitWriter.getBytes());
|
|
1079
|
+
}
|
|
1080
|
+
encodeOnlyDC(dc, prevDC, dcTable, bitWriter) {
|
|
1081
|
+
const dcDiff = dc - prevDC;
|
|
1082
|
+
const clampedDiff = Math.max(-2047, Math.min(2047, dcDiff));
|
|
1083
|
+
const absDiff = Math.abs(clampedDiff);
|
|
1084
|
+
let size = 0;
|
|
1085
|
+
if (absDiff > 0) {
|
|
1086
|
+
size = Math.floor(Math.log2(absDiff)) + 1;
|
|
1087
|
+
}
|
|
1088
|
+
bitWriter.writeBits(dcTable.codes[size], dcTable.sizes[size]);
|
|
1089
|
+
if (size > 0) {
|
|
1090
|
+
const magnitude = clampedDiff < 0 ? clampedDiff + (1 << size) - 1 : clampedDiff;
|
|
1091
|
+
bitWriter.writeBits(magnitude, size);
|
|
1092
|
+
}
|
|
1093
|
+
return dc;
|
|
1094
|
+
}
|
|
1095
|
+
encodeOnlyAC(quantized, acTable, bitWriter) {
|
|
1096
|
+
let zeroCount = 0;
|
|
1097
|
+
// Start from index 1 (skip DC coefficient)
|
|
1098
|
+
for (let i = 1; i < 64; i++) {
|
|
1099
|
+
const coef = quantized[ZIGZAG[i]];
|
|
1100
|
+
const clampedCoef = Math.max(-1023, Math.min(1023, coef));
|
|
1101
|
+
if (clampedCoef === 0) {
|
|
1102
|
+
zeroCount++;
|
|
1103
|
+
if (zeroCount === 16) {
|
|
1104
|
+
bitWriter.writeBits(acTable.codes[0xf0], acTable.sizes[0xf0]);
|
|
1105
|
+
zeroCount = 0;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
else {
|
|
1109
|
+
while (zeroCount >= 16) {
|
|
1110
|
+
bitWriter.writeBits(acTable.codes[0xf0], acTable.sizes[0xf0]);
|
|
1111
|
+
zeroCount -= 16;
|
|
1112
|
+
}
|
|
1113
|
+
const absCoef = Math.abs(clampedCoef);
|
|
1114
|
+
const size = Math.floor(Math.log2(absCoef)) + 1;
|
|
1115
|
+
const symbol = (zeroCount << 4) | size;
|
|
1116
|
+
bitWriter.writeBits(acTable.codes[symbol], acTable.sizes[symbol]);
|
|
1117
|
+
const magnitude = clampedCoef < 0 ? clampedCoef + (1 << size) - 1 : clampedCoef;
|
|
1118
|
+
bitWriter.writeBits(magnitude, size);
|
|
1119
|
+
zeroCount = 0;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
// Write EOB if there are trailing zeros
|
|
1123
|
+
if (zeroCount > 0) {
|
|
1124
|
+
bitWriter.writeBits(acTable.codes[0x00], acTable.sizes[0x00]);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
896
1127
|
encodeScan(width, height, rgba) {
|
|
897
1128
|
const bitWriter = new BitWriter();
|
|
898
1129
|
// Convert RGBA to YCbCr and encode MCUs
|
|
@@ -901,9 +1132,9 @@ class JPEGEncoder {
|
|
|
901
1132
|
const mcuHeight = Math.ceil(height / 8);
|
|
902
1133
|
for (let mcuY = 0; mcuY < mcuHeight; mcuY++) {
|
|
903
1134
|
for (let mcuX = 0; mcuX < mcuWidth; mcuX++) {
|
|
904
|
-
const yBlock = new
|
|
905
|
-
const cbBlock = new
|
|
906
|
-
const crBlock = new
|
|
1135
|
+
const yBlock = new Float32Array(64);
|
|
1136
|
+
const cbBlock = new Float32Array(64);
|
|
1137
|
+
const crBlock = new Float32Array(64);
|
|
907
1138
|
// Extract 8x8 block and convert RGB to YCbCr
|
|
908
1139
|
for (let y = 0; y < 8; y++) {
|
|
909
1140
|
for (let x = 0; x < 8; x++) {
|
|
@@ -937,7 +1168,7 @@ class JPEGEncoder {
|
|
|
937
1168
|
// Apply DCT
|
|
938
1169
|
this.forwardDCT(block);
|
|
939
1170
|
// Quantize and reorder to zigzag
|
|
940
|
-
const quantized = new
|
|
1171
|
+
const quantized = new Int32Array(64);
|
|
941
1172
|
for (let i = 0; i < 64; i++) {
|
|
942
1173
|
const zigzagIndex = ZIGZAG[i];
|
|
943
1174
|
quantized[i] = Math.round(block[zigzagIndex] / quantTable[zigzagIndex]);
|
|
@@ -951,7 +1182,7 @@ class JPEGEncoder {
|
|
|
951
1182
|
}
|
|
952
1183
|
forwardDCT(block) {
|
|
953
1184
|
// Simplified 2D DCT
|
|
954
|
-
const temp = new
|
|
1185
|
+
const temp = new Float32Array(64);
|
|
955
1186
|
// 1D DCT on rows
|
|
956
1187
|
for (let i = 0; i < 8; i++) {
|
|
957
1188
|
const offset = i * 8;
|
|
@@ -977,7 +1208,11 @@ class JPEGEncoder {
|
|
|
977
1208
|
}
|
|
978
1209
|
}
|
|
979
1210
|
encodeDC(value, huffTable, bitWriter) {
|
|
980
|
-
|
|
1211
|
+
// Clamp DC coefficient to the range supported by standard Huffman tables
|
|
1212
|
+
// Standard DC tables support sizes 0-11, which means values in range [-2047, 2047]
|
|
1213
|
+
const maxDC = 2047;
|
|
1214
|
+
const clampedValue = Math.max(-maxDC, Math.min(maxDC, value));
|
|
1215
|
+
const absValue = Math.abs(clampedValue);
|
|
981
1216
|
let size = 0;
|
|
982
1217
|
if (absValue > 0) {
|
|
983
1218
|
size = Math.floor(Math.log2(absValue)) + 1;
|
|
@@ -986,7 +1221,7 @@ class JPEGEncoder {
|
|
|
986
1221
|
bitWriter.writeBits(huffTable.codes[size], huffTable.sizes[size]);
|
|
987
1222
|
// Write magnitude
|
|
988
1223
|
if (size > 0) {
|
|
989
|
-
const magnitude =
|
|
1224
|
+
const magnitude = clampedValue < 0 ? clampedValue + (1 << size) - 1 : clampedValue;
|
|
990
1225
|
bitWriter.writeBits(magnitude, size);
|
|
991
1226
|
}
|
|
992
1227
|
}
|
|
@@ -1003,11 +1238,15 @@ class JPEGEncoder {
|
|
|
1003
1238
|
bitWriter.writeBits(huffTable.codes[0xf0], huffTable.sizes[0xf0]);
|
|
1004
1239
|
zeroCount -= 16;
|
|
1005
1240
|
}
|
|
1006
|
-
|
|
1241
|
+
// Clamp AC coefficient to the range supported by standard Huffman tables
|
|
1242
|
+
// Standard AC tables support sizes 1-10, which means values in range [-1023, 1023]
|
|
1243
|
+
const maxAC = 1023;
|
|
1244
|
+
const clampedCoef = Math.max(-maxAC, Math.min(maxAC, coef));
|
|
1245
|
+
const absCoef = Math.abs(clampedCoef);
|
|
1007
1246
|
const size = Math.floor(Math.log2(absCoef)) + 1;
|
|
1008
1247
|
const symbol = (zeroCount << 4) | size;
|
|
1009
1248
|
bitWriter.writeBits(huffTable.codes[symbol], huffTable.sizes[symbol]);
|
|
1010
|
-
const magnitude =
|
|
1249
|
+
const magnitude = clampedCoef < 0 ? clampedCoef + (1 << size) - 1 : clampedCoef;
|
|
1011
1250
|
bitWriter.writeBits(magnitude, size);
|
|
1012
1251
|
zeroCount = 0;
|
|
1013
1252
|
}
|
|
@@ -10,26 +10,55 @@ function resizeBilinear(src, srcWidth, srcHeight, dstWidth, dstHeight) {
|
|
|
10
10
|
const dst = new Uint8Array(dstWidth * dstHeight * 4);
|
|
11
11
|
const xRatio = srcWidth / dstWidth;
|
|
12
12
|
const yRatio = srcHeight / dstHeight;
|
|
13
|
+
const srcWidthMinus1 = srcWidth - 1;
|
|
14
|
+
const srcHeightMinus1 = srcHeight - 1;
|
|
13
15
|
for (let y = 0; y < dstHeight; y++) {
|
|
16
|
+
const srcY = y * yRatio;
|
|
17
|
+
const y1 = srcY | 0; // Fast floor
|
|
18
|
+
const y2 = y1 < srcHeightMinus1 ? y1 + 1 : srcHeightMinus1;
|
|
19
|
+
const dy = srcY - y1;
|
|
20
|
+
const dyInv = 1 - dy;
|
|
21
|
+
const dstRowOffset = y * dstWidth * 4;
|
|
14
22
|
for (let x = 0; x < dstWidth; x++) {
|
|
15
23
|
const srcX = x * xRatio;
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const y1 = Math.floor(srcY);
|
|
19
|
-
const x2 = Math.min(x1 + 1, srcWidth - 1);
|
|
20
|
-
const y2 = Math.min(y1 + 1, srcHeight - 1);
|
|
24
|
+
const x1 = srcX | 0; // Fast floor
|
|
25
|
+
const x2 = x1 < srcWidthMinus1 ? x1 + 1 : srcWidthMinus1;
|
|
21
26
|
const dx = srcX - x1;
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
const dxInv = 1 - dx;
|
|
28
|
+
const idx1 = (y1 * srcWidth + x1) * 4;
|
|
29
|
+
const idx2 = (y1 * srcWidth + x2) * 4;
|
|
30
|
+
const idx3 = (y2 * srcWidth + x1) * 4;
|
|
31
|
+
const idx4 = (y2 * srcWidth + x2) * 4;
|
|
32
|
+
const dstIdx = dstRowOffset + x * 4;
|
|
33
|
+
// Unroll channel loop for better performance
|
|
34
|
+
const p1_r = src[idx1];
|
|
35
|
+
const p2_r = src[idx2];
|
|
36
|
+
const p3_r = src[idx3];
|
|
37
|
+
const p4_r = src[idx4];
|
|
38
|
+
const v1_r = p1_r * dxInv + p2_r * dx;
|
|
39
|
+
const v2_r = p3_r * dxInv + p4_r * dx;
|
|
40
|
+
dst[dstIdx] = (v1_r * dyInv + v2_r * dy + 0.5) | 0;
|
|
41
|
+
const p1_g = src[idx1 + 1];
|
|
42
|
+
const p2_g = src[idx2 + 1];
|
|
43
|
+
const p3_g = src[idx3 + 1];
|
|
44
|
+
const p4_g = src[idx4 + 1];
|
|
45
|
+
const v1_g = p1_g * dxInv + p2_g * dx;
|
|
46
|
+
const v2_g = p3_g * dxInv + p4_g * dx;
|
|
47
|
+
dst[dstIdx + 1] = (v1_g * dyInv + v2_g * dy + 0.5) | 0;
|
|
48
|
+
const p1_b = src[idx1 + 2];
|
|
49
|
+
const p2_b = src[idx2 + 2];
|
|
50
|
+
const p3_b = src[idx3 + 2];
|
|
51
|
+
const p4_b = src[idx4 + 2];
|
|
52
|
+
const v1_b = p1_b * dxInv + p2_b * dx;
|
|
53
|
+
const v2_b = p3_b * dxInv + p4_b * dx;
|
|
54
|
+
dst[dstIdx + 2] = (v1_b * dyInv + v2_b * dy + 0.5) | 0;
|
|
55
|
+
const p1_a = src[idx1 + 3];
|
|
56
|
+
const p2_a = src[idx2 + 3];
|
|
57
|
+
const p3_a = src[idx3 + 3];
|
|
58
|
+
const p4_a = src[idx4 + 3];
|
|
59
|
+
const v1_a = p1_a * dxInv + p2_a * dx;
|
|
60
|
+
const v2_a = p3_a * dxInv + p4_a * dx;
|
|
61
|
+
dst[dstIdx + 3] = (v1_a * dyInv + v2_a * dy + 0.5) | 0;
|
|
33
62
|
}
|
|
34
63
|
}
|
|
35
64
|
return dst;
|
|
@@ -42,11 +71,13 @@ function resizeNearest(src, srcWidth, srcHeight, dstWidth, dstHeight) {
|
|
|
42
71
|
const xRatio = srcWidth / dstWidth;
|
|
43
72
|
const yRatio = srcHeight / dstHeight;
|
|
44
73
|
for (let y = 0; y < dstHeight; y++) {
|
|
74
|
+
const srcY = (y * yRatio) | 0; // Use bitwise OR for fast floor
|
|
75
|
+
const srcRowOffset = srcY * srcWidth;
|
|
76
|
+
const dstRowOffset = y * dstWidth;
|
|
45
77
|
for (let x = 0; x < dstWidth; x++) {
|
|
46
|
-
const srcX =
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const dstIdx = (y * dstWidth + x) * 4;
|
|
78
|
+
const srcX = (x * xRatio) | 0; // Use bitwise OR for fast floor
|
|
79
|
+
const srcIdx = (srcRowOffset + srcX) * 4;
|
|
80
|
+
const dstIdx = (dstRowOffset + x) * 4;
|
|
50
81
|
dst[dstIdx] = src[srcIdx];
|
|
51
82
|
dst[dstIdx + 1] = src[srcIdx + 1];
|
|
52
83
|
dst[dstIdx + 2] = src[srcIdx + 2];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deflate compression/decompression for TIFF
|
|
3
|
+
* Uses native JavaScript CompressionStream/DecompressionStream APIs
|
|
4
|
+
* Compression code: 8 (Adobe-style Deflate)
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Compress data using Deflate
|
|
8
|
+
* @param data Uncompressed data
|
|
9
|
+
* @returns Compressed data
|
|
10
|
+
*/
|
|
11
|
+
export declare function deflateCompress(data: Uint8Array): Promise<Uint8Array>;
|
|
12
|
+
/**
|
|
13
|
+
* Decompress Deflate data
|
|
14
|
+
* @param data Compressed data
|
|
15
|
+
* @returns Decompressed data
|
|
16
|
+
*/
|
|
17
|
+
export declare function deflateDecompress(data: Uint8Array): Promise<Uint8Array>;
|
|
18
|
+
//# sourceMappingURL=tiff_deflate.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Deflate compression/decompression for TIFF
|
|
4
|
+
* Uses native JavaScript CompressionStream/DecompressionStream APIs
|
|
5
|
+
* Compression code: 8 (Adobe-style Deflate)
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.deflateCompress = deflateCompress;
|
|
9
|
+
exports.deflateDecompress = deflateDecompress;
|
|
10
|
+
/**
|
|
11
|
+
* Compress data using Deflate
|
|
12
|
+
* @param data Uncompressed data
|
|
13
|
+
* @returns Compressed data
|
|
14
|
+
*/
|
|
15
|
+
async function deflateCompress(data) {
|
|
16
|
+
const stream = new Response(data).body
|
|
17
|
+
.pipeThrough(new CompressionStream("deflate"));
|
|
18
|
+
const compressed = await new Response(stream).arrayBuffer();
|
|
19
|
+
return new Uint8Array(compressed);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Decompress Deflate data
|
|
23
|
+
* @param data Compressed data
|
|
24
|
+
* @returns Decompressed data
|
|
25
|
+
*/
|
|
26
|
+
async function deflateDecompress(data) {
|
|
27
|
+
const stream = new Response(data).body
|
|
28
|
+
.pipeThrough(new DecompressionStream("deflate"));
|
|
29
|
+
const decompressed = await new Response(stream).arrayBuffer();
|
|
30
|
+
return new Uint8Array(decompressed);
|
|
31
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PackBits compression/decompression for TIFF
|
|
3
|
+
* PackBits is a simple run-length encoding (RLE) scheme used in TIFF images
|
|
4
|
+
* Compression code: 32773
|
|
5
|
+
*
|
|
6
|
+
* Format:
|
|
7
|
+
* - Header byte n:
|
|
8
|
+
* - If n >= 0 and n <= 127: Copy the next n+1 bytes literally
|
|
9
|
+
* - If n >= -127 and n <= -1: Repeat the next byte -n+1 times
|
|
10
|
+
* - If n = -128: No operation (skip)
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Compress data using PackBits RLE
|
|
14
|
+
* @param data Uncompressed data
|
|
15
|
+
* @returns Compressed data
|
|
16
|
+
*/
|
|
17
|
+
export declare function packBitsCompress(data: Uint8Array): Uint8Array;
|
|
18
|
+
/**
|
|
19
|
+
* Decompress PackBits RLE data
|
|
20
|
+
* @param data Compressed data
|
|
21
|
+
* @returns Decompressed data
|
|
22
|
+
*/
|
|
23
|
+
export declare function packBitsDecompress(data: Uint8Array): Uint8Array;
|
|
24
|
+
//# sourceMappingURL=tiff_packbits.d.ts.map
|