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