cross-image 0.2.4 → 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.
Files changed (105) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +507 -333
  3. package/esm/mod.d.ts +4 -4
  4. package/esm/mod.js +2 -2
  5. package/esm/src/formats/apng.d.ts +5 -5
  6. package/esm/src/formats/apng.js +7 -9
  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 +7 -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 +4 -4
  16. package/esm/src/formats/gif.js +14 -10
  17. package/esm/src/formats/heic.d.ts +3 -3
  18. package/esm/src/formats/heic.js +7 -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 +3 -3
  28. package/esm/src/formats/png.js +2 -2
  29. package/esm/src/formats/png_base.js +2 -5
  30. package/esm/src/formats/ppm.d.ts +3 -3
  31. package/esm/src/formats/ppm.js +2 -2
  32. package/esm/src/formats/tiff.d.ts +7 -18
  33. package/esm/src/formats/tiff.js +86 -21
  34. package/esm/src/formats/webp.d.ts +3 -3
  35. package/esm/src/formats/webp.js +11 -8
  36. package/esm/src/image.d.ts +11 -3
  37. package/esm/src/image.js +37 -21
  38. package/esm/src/types.d.ts +56 -4
  39. package/esm/src/utils/gif_decoder.d.ts +4 -1
  40. package/esm/src/utils/gif_decoder.js +91 -65
  41. package/esm/src/utils/image_processing.js +144 -70
  42. package/esm/src/utils/jpeg_decoder.d.ts +17 -4
  43. package/esm/src/utils/jpeg_decoder.js +448 -83
  44. package/esm/src/utils/jpeg_encoder.d.ts +15 -1
  45. package/esm/src/utils/jpeg_encoder.js +263 -24
  46. package/esm/src/utils/resize.js +51 -20
  47. package/esm/src/utils/tiff_deflate.d.ts +18 -0
  48. package/esm/src/utils/tiff_deflate.js +27 -0
  49. package/esm/src/utils/tiff_packbits.d.ts +24 -0
  50. package/esm/src/utils/tiff_packbits.js +90 -0
  51. package/esm/src/utils/webp_decoder.d.ts +3 -1
  52. package/esm/src/utils/webp_decoder.js +144 -63
  53. package/esm/src/utils/webp_encoder.js +5 -11
  54. package/package.json +1 -1
  55. package/script/mod.d.ts +4 -4
  56. package/script/mod.js +2 -2
  57. package/script/src/formats/apng.d.ts +5 -5
  58. package/script/src/formats/apng.js +7 -9
  59. package/script/src/formats/ascii.d.ts +3 -3
  60. package/script/src/formats/ascii.js +1 -1
  61. package/script/src/formats/avif.d.ts +3 -3
  62. package/script/src/formats/avif.js +7 -7
  63. package/script/src/formats/bmp.d.ts +3 -3
  64. package/script/src/formats/bmp.js +2 -2
  65. package/script/src/formats/dng.d.ts +1 -1
  66. package/script/src/formats/dng.js +1 -1
  67. package/script/src/formats/gif.d.ts +4 -4
  68. package/script/src/formats/gif.js +14 -10
  69. package/script/src/formats/heic.d.ts +3 -3
  70. package/script/src/formats/heic.js +7 -7
  71. package/script/src/formats/ico.d.ts +3 -3
  72. package/script/src/formats/ico.js +4 -4
  73. package/script/src/formats/jpeg.d.ts +3 -3
  74. package/script/src/formats/jpeg.js +23 -11
  75. package/script/src/formats/pam.d.ts +3 -3
  76. package/script/src/formats/pam.js +2 -2
  77. package/script/src/formats/pcx.d.ts +3 -3
  78. package/script/src/formats/pcx.js +2 -2
  79. package/script/src/formats/png.d.ts +3 -3
  80. package/script/src/formats/png.js +2 -2
  81. package/script/src/formats/png_base.js +2 -5
  82. package/script/src/formats/ppm.d.ts +3 -3
  83. package/script/src/formats/ppm.js +2 -2
  84. package/script/src/formats/tiff.d.ts +7 -18
  85. package/script/src/formats/tiff.js +86 -21
  86. package/script/src/formats/webp.d.ts +3 -3
  87. package/script/src/formats/webp.js +11 -8
  88. package/script/src/image.d.ts +11 -3
  89. package/script/src/image.js +36 -20
  90. package/script/src/types.d.ts +56 -4
  91. package/script/src/utils/gif_decoder.d.ts +4 -1
  92. package/script/src/utils/gif_decoder.js +91 -65
  93. package/script/src/utils/image_processing.js +144 -70
  94. package/script/src/utils/jpeg_decoder.d.ts +17 -4
  95. package/script/src/utils/jpeg_decoder.js +448 -83
  96. package/script/src/utils/jpeg_encoder.d.ts +15 -1
  97. package/script/src/utils/jpeg_encoder.js +263 -24
  98. package/script/src/utils/resize.js +51 -20
  99. package/script/src/utils/tiff_deflate.d.ts +18 -0
  100. package/script/src/utils/tiff_deflate.js +31 -0
  101. package/script/src/utils/tiff_packbits.d.ts +24 -0
  102. package/script/src/utils/tiff_packbits.js +94 -0
  103. package/script/src/utils/webp_decoder.d.ts +3 -1
  104. package/script/src/utils/webp_decoder.js +144 -63
  105. 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(quality?: number);
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(quality = 85) {
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 Array(256).fill(0);
762
- const sizes = new Array(256).fill(0);
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
- // SOF0 (Start of Frame - Baseline DCT)
786
- this.writeSOF0(output, width, height);
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
- // SOS (Start of Scan)
790
- this.writeSOS(output);
791
- // Encode scan data
792
- const scanData = this.encodeScan(width, height, rgba);
793
- for (let i = 0; i < scanData.length; i++) {
794
- output.push(scanData[i]);
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 Array(64).fill(0);
902
- const cbBlock = new Array(64).fill(0);
903
- const crBlock = new Array(64).fill(0);
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 Array(64);
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 Array(64);
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
- const absValue = Math.abs(value);
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 = value < 0 ? value + (1 << size) - 1 : value;
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
- const absCoef = Math.abs(coef);
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 = coef < 0 ? coef + (1 << size) - 1 : coef;
1246
+ const magnitude = clampedCoef < 0 ? clampedCoef + (1 << size) - 1 : clampedCoef;
1008
1247
  bitWriter.writeBits(magnitude, size);
1009
1248
  zeroCount = 0;
1010
1249
  }
@@ -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 srcY = y * yRatio;
12
- const x1 = Math.floor(srcX);
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 dy = srcY - y1;
18
- for (let c = 0; c < 4; c++) {
19
- const p1 = src[(y1 * srcWidth + x1) * 4 + c];
20
- const p2 = src[(y1 * srcWidth + x2) * 4 + c];
21
- const p3 = src[(y2 * srcWidth + x1) * 4 + c];
22
- const p4 = src[(y2 * srcWidth + x2) * 4 + c];
23
- const v1 = p1 * (1 - dx) + p2 * dx;
24
- const v2 = p3 * (1 - dx) + p4 * dx;
25
- const v = v1 * (1 - dy) + v2 * dy;
26
- dst[(y * dstWidth + x) * 4 + c] = Math.round(v);
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 = Math.floor(x * xRatio);
42
- const srcY = Math.floor(y * yRatio);
43
- const srcIdx = (srcY * srcWidth + srcX) * 4;
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
+ }