cross-image 0.4.0 → 0.4.2

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 (62) hide show
  1. package/README.md +186 -5
  2. package/esm/mod.d.ts +3 -1
  3. package/esm/mod.js +2 -0
  4. package/esm/src/formats/apng.d.ts +5 -3
  5. package/esm/src/formats/apng.js +11 -4
  6. package/esm/src/formats/avif.d.ts +2 -2
  7. package/esm/src/formats/avif.js +11 -1
  8. package/esm/src/formats/gif.d.ts +3 -3
  9. package/esm/src/formats/gif.js +4 -4
  10. package/esm/src/formats/heic.d.ts +2 -2
  11. package/esm/src/formats/heic.js +11 -1
  12. package/esm/src/formats/jpeg.d.ts +21 -1
  13. package/esm/src/formats/jpeg.js +59 -0
  14. package/esm/src/formats/png.d.ts +3 -2
  15. package/esm/src/formats/png.js +8 -2
  16. package/esm/src/formats/png_base.d.ts +42 -1
  17. package/esm/src/formats/png_base.js +198 -5
  18. package/esm/src/formats/tiff.js +76 -6
  19. package/esm/src/image.d.ts +54 -1
  20. package/esm/src/image.js +97 -1
  21. package/esm/src/types.d.ts +129 -0
  22. package/esm/src/utils/base64.d.ts +32 -0
  23. package/esm/src/utils/base64.js +173 -0
  24. package/esm/src/utils/gif_encoder.d.ts +3 -1
  25. package/esm/src/utils/gif_encoder.js +4 -2
  26. package/esm/src/utils/image_processing.d.ts +31 -0
  27. package/esm/src/utils/image_processing.js +88 -0
  28. package/esm/src/utils/jpeg_decoder.d.ts +25 -2
  29. package/esm/src/utils/jpeg_decoder.js +101 -10
  30. package/esm/src/utils/jpeg_encoder.d.ts +19 -0
  31. package/esm/src/utils/jpeg_encoder.js +267 -0
  32. package/package.json +1 -1
  33. package/script/mod.d.ts +3 -1
  34. package/script/mod.js +11 -1
  35. package/script/src/formats/apng.d.ts +5 -3
  36. package/script/src/formats/apng.js +11 -4
  37. package/script/src/formats/avif.d.ts +2 -2
  38. package/script/src/formats/avif.js +11 -1
  39. package/script/src/formats/gif.d.ts +3 -3
  40. package/script/src/formats/gif.js +4 -4
  41. package/script/src/formats/heic.d.ts +2 -2
  42. package/script/src/formats/heic.js +11 -1
  43. package/script/src/formats/jpeg.d.ts +21 -1
  44. package/script/src/formats/jpeg.js +59 -0
  45. package/script/src/formats/png.d.ts +3 -2
  46. package/script/src/formats/png.js +8 -2
  47. package/script/src/formats/png_base.d.ts +42 -1
  48. package/script/src/formats/png_base.js +198 -5
  49. package/script/src/formats/tiff.js +76 -6
  50. package/script/src/image.d.ts +54 -1
  51. package/script/src/image.js +96 -0
  52. package/script/src/types.d.ts +129 -0
  53. package/script/src/utils/base64.d.ts +32 -0
  54. package/script/src/utils/base64.js +179 -0
  55. package/script/src/utils/gif_encoder.d.ts +3 -1
  56. package/script/src/utils/gif_encoder.js +4 -2
  57. package/script/src/utils/image_processing.d.ts +31 -0
  58. package/script/src/utils/image_processing.js +92 -0
  59. package/script/src/utils/jpeg_decoder.d.ts +25 -2
  60. package/script/src/utils/jpeg_decoder.js +101 -10
  61. package/script/src/utils/jpeg_encoder.d.ts +19 -0
  62. package/script/src/utils/jpeg_encoder.js +267 -0
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ /**
3
+ * Base64 utilities.
4
+ *
5
+ * Designed to work across Deno, Node.js, and Bun without external dependencies.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.encodeBase64 = encodeBase64;
9
+ exports.decodeBase64 = decodeBase64;
10
+ exports.toDataUrl = toDataUrl;
11
+ exports.parseDataUrl = parseDataUrl;
12
+ const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
13
+ const DECODE_TABLE = (() => {
14
+ const table = new Uint8Array(256);
15
+ table.fill(0xff);
16
+ for (let i = 0; i < BASE64_ALPHABET.length; i++) {
17
+ table[BASE64_ALPHABET.charCodeAt(i)] = i;
18
+ }
19
+ return table;
20
+ })();
21
+ function stripWhitespace(input) {
22
+ // Avoid regex to keep this fast and allocation-light.
23
+ let out = "";
24
+ for (let i = 0; i < input.length; i++) {
25
+ const code = input.charCodeAt(i);
26
+ // Space, tab, CR, LF
27
+ if (code === 0x20 || code === 0x09 || code === 0x0d || code === 0x0a)
28
+ continue;
29
+ out += input[i];
30
+ }
31
+ return out;
32
+ }
33
+ function normalizeBase64(input) {
34
+ // Accept URL-safe alphabet in decode paths.
35
+ let s = stripWhitespace(input.trim());
36
+ if (s.length === 0)
37
+ return s;
38
+ // Map base64url to base64.
39
+ if (s.includes("-") || s.includes("_")) {
40
+ s = s.replaceAll("-", "+").replaceAll("_", "/");
41
+ }
42
+ const remainder = s.length % 4;
43
+ if (remainder === 1) {
44
+ throw new Error("Invalid base64: length must not be 1 (mod 4)");
45
+ }
46
+ if (remainder === 2)
47
+ s += "==";
48
+ if (remainder === 3)
49
+ s += "=";
50
+ return s;
51
+ }
52
+ /**
53
+ * Encode bytes into a standard Base64 string.
54
+ */
55
+ function encodeBase64(bytes) {
56
+ if (bytes.length === 0)
57
+ return "";
58
+ const outLen = Math.ceil(bytes.length / 3) * 4;
59
+ const out = new Array(outLen);
60
+ let i = 0;
61
+ let o = 0;
62
+ for (; i + 2 < bytes.length; i += 3) {
63
+ const n = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
64
+ out[o++] = BASE64_ALPHABET[(n >> 18) & 63];
65
+ out[o++] = BASE64_ALPHABET[(n >> 12) & 63];
66
+ out[o++] = BASE64_ALPHABET[(n >> 6) & 63];
67
+ out[o++] = BASE64_ALPHABET[n & 63];
68
+ }
69
+ const remaining = bytes.length - i;
70
+ if (remaining === 1) {
71
+ const n = bytes[i] << 16;
72
+ out[o++] = BASE64_ALPHABET[(n >> 18) & 63];
73
+ out[o++] = BASE64_ALPHABET[(n >> 12) & 63];
74
+ out[o++] = "=";
75
+ out[o++] = "=";
76
+ }
77
+ else if (remaining === 2) {
78
+ const n = (bytes[i] << 16) | (bytes[i + 1] << 8);
79
+ out[o++] = BASE64_ALPHABET[(n >> 18) & 63];
80
+ out[o++] = BASE64_ALPHABET[(n >> 12) & 63];
81
+ out[o++] = BASE64_ALPHABET[(n >> 6) & 63];
82
+ out[o++] = "=";
83
+ }
84
+ return out.join("");
85
+ }
86
+ /**
87
+ * Decode a Base64 (or Base64URL) string into bytes.
88
+ *
89
+ * - Whitespace is ignored.
90
+ * - Missing padding is tolerated.
91
+ * - `-`/`_` are accepted as URL-safe variants.
92
+ */
93
+ function decodeBase64(base64) {
94
+ const s = normalizeBase64(base64);
95
+ if (s.length === 0)
96
+ return new Uint8Array(0);
97
+ let padding = 0;
98
+ if (s.endsWith("=="))
99
+ padding = 2;
100
+ else if (s.endsWith("="))
101
+ padding = 1;
102
+ const quadCount = s.length / 4;
103
+ const outLen = quadCount * 3 - padding;
104
+ const out = new Uint8Array(outLen);
105
+ let o = 0;
106
+ for (let i = 0; i < s.length; i += 4) {
107
+ const c0 = s.charCodeAt(i);
108
+ const c1 = s.charCodeAt(i + 1);
109
+ const c2 = s.charCodeAt(i + 2);
110
+ const c3 = s.charCodeAt(i + 3);
111
+ const v0 = DECODE_TABLE[c0];
112
+ const v1 = DECODE_TABLE[c1];
113
+ if (v0 === 0xff || v1 === 0xff) {
114
+ throw new Error("Invalid base64: invalid character");
115
+ }
116
+ const isPad2 = c2 === 0x3d; // '='
117
+ const isPad3 = c3 === 0x3d;
118
+ const v2 = isPad2 ? 0 : DECODE_TABLE[c2];
119
+ const v3 = isPad3 ? 0 : DECODE_TABLE[c3];
120
+ if ((!isPad2 && v2 === 0xff) || (!isPad3 && v3 === 0xff)) {
121
+ throw new Error("Invalid base64: invalid character");
122
+ }
123
+ const n = (v0 << 18) | (v1 << 12) | (v2 << 6) | v3;
124
+ out[o++] = (n >> 16) & 0xff;
125
+ if (!isPad2) {
126
+ if (o >= out.length)
127
+ break;
128
+ out[o++] = (n >> 8) & 0xff;
129
+ }
130
+ if (!isPad3) {
131
+ if (o >= out.length)
132
+ break;
133
+ out[o++] = n & 0xff;
134
+ }
135
+ if (isPad2 || isPad3) {
136
+ // Padding is only valid in the final quartet.
137
+ if (i + 4 !== s.length) {
138
+ throw new Error("Invalid base64: padding can only appear at the end");
139
+ }
140
+ // If third char is padding, fourth must also be padding.
141
+ if (isPad2 && !isPad3) {
142
+ throw new Error("Invalid base64: invalid padding");
143
+ }
144
+ }
145
+ }
146
+ return out;
147
+ }
148
+ /**
149
+ * Create a base64 data URL.
150
+ */
151
+ function toDataUrl(mime, bytes) {
152
+ if (!mime)
153
+ throw new Error("mime is required");
154
+ return `data:${mime};base64,${encodeBase64(bytes)}`;
155
+ }
156
+ /**
157
+ * Parse a base64 data URL.
158
+ *
159
+ * Supports `data:<mime>;base64,<payload>`.
160
+ */
161
+ function parseDataUrl(url) {
162
+ if (!url.startsWith("data:")) {
163
+ throw new Error("Invalid data URL: must start with 'data:'");
164
+ }
165
+ const commaIndex = url.indexOf(",");
166
+ if (commaIndex === -1) {
167
+ throw new Error("Invalid data URL: missing ',' separator");
168
+ }
169
+ const meta = url.slice(5, commaIndex);
170
+ const payload = url.slice(commaIndex + 1);
171
+ // We only support base64 payloads.
172
+ const metaParts = meta.split(";");
173
+ const mime = metaParts[0] || "application/octet-stream";
174
+ const isBase64 = metaParts.some((p) => p.toLowerCase() === "base64");
175
+ if (!isBase64) {
176
+ throw new Error("Invalid data URL: only base64 payloads are supported");
177
+ }
178
+ return { mime, bytes: decodeBase64(payload) };
179
+ }
@@ -25,6 +25,8 @@ export declare class GIFEncoder {
25
25
  private quantize;
26
26
  private nextPowerOf2;
27
27
  private getBitsPerColor;
28
- encode(): Uint8Array;
28
+ encode(options?: {
29
+ loop?: number;
30
+ }): Uint8Array;
29
31
  }
30
32
  //# sourceMappingURL=gif_encoder.d.ts.map
@@ -168,11 +168,13 @@ class GIFEncoder {
168
168
  }
169
169
  return Math.max(2, bits);
170
170
  }
171
- encode() {
171
+ encode(options) {
172
172
  if (this.frames.length === 0) {
173
173
  throw new Error("No frames to encode");
174
174
  }
175
175
  const output = [];
176
+ // Get loop count from options (default to 0 = infinite)
177
+ const loopCount = options?.loop ?? 0;
176
178
  // Quantize first frame for Global Color Table
177
179
  const firstFrame = this.frames[0];
178
180
  const { palette: globalPalette, indexed: firstIndexed } = this.quantize(firstFrame.data);
@@ -209,7 +211,7 @@ class GIFEncoder {
209
211
  this.writeString(output, "NETSCAPE2.0");
210
212
  output.push(3); // Sub-block Size
211
213
  output.push(1); // Loop Indicator (1 = loop)
212
- this.writeUint16LE(output, 0); // Loop Count (0 = infinite)
214
+ this.writeUint16LE(output, loopCount); // Loop Count (0 = infinite, 1+ = specific count)
213
215
  output.push(0); // Block Terminator
214
216
  }
215
217
  // Encode frames
@@ -186,4 +186,35 @@ export declare function flipHorizontal(data: Uint8Array, width: number, height:
186
186
  * @returns Flipped image data
187
187
  */
188
188
  export declare function flipVertical(data: Uint8Array, width: number, height: number): Uint8Array;
189
+ /**
190
+ * Convert RGB color to CMYK color space
191
+ * @param r Red component (0-255)
192
+ * @param g Green component (0-255)
193
+ * @param b Blue component (0-255)
194
+ * @returns CMYK values: [c (0-1), m (0-1), y (0-1), k (0-1)]
195
+ */
196
+ export declare function rgbToCmyk(r: number, g: number, b: number): [number, number, number, number];
197
+ /**
198
+ * Convert CMYK color to RGB color space
199
+ * @param c Cyan component (0-1)
200
+ * @param m Magenta component (0-1)
201
+ * @param y Yellow component (0-1)
202
+ * @param k Key/Black component (0-1)
203
+ * @returns RGB values: [r (0-255), g (0-255), b (0-255)]
204
+ */
205
+ export declare function cmykToRgb(c: number, m: number, y: number, k: number): [number, number, number];
206
+ /**
207
+ * Convert RGBA image data to CMYK representation
208
+ * Returns a Float32Array with 4 values per pixel (C, M, Y, K) in 0-1 range
209
+ * @param data Image data (RGBA)
210
+ * @returns CMYK image data as Float32Array (4 values per pixel)
211
+ */
212
+ export declare function rgbaToCmyk(data: Uint8Array): Float32Array;
213
+ /**
214
+ * Convert CMYK image data to RGBA representation
215
+ * @param cmykData CMYK image data (4 values per pixel in 0-1 range)
216
+ * @param alpha Optional alpha value for all pixels (0-255, default: 255)
217
+ * @returns RGBA image data
218
+ */
219
+ export declare function cmykToRgba(cmykData: Float32Array, alpha?: number): Uint8Array;
189
220
  //# sourceMappingURL=image_processing.d.ts.map
@@ -24,6 +24,10 @@ exports.rotate180 = rotate180;
24
24
  exports.rotate270 = rotate270;
25
25
  exports.flipHorizontal = flipHorizontal;
26
26
  exports.flipVertical = flipVertical;
27
+ exports.rgbToCmyk = rgbToCmyk;
28
+ exports.cmykToRgb = cmykToRgb;
29
+ exports.rgbaToCmyk = rgbaToCmyk;
30
+ exports.cmykToRgba = cmykToRgba;
27
31
  /**
28
32
  * Detect system endianness
29
33
  * Returns true if little-endian (most common), false if big-endian
@@ -765,3 +769,91 @@ function flipVertical(data, width, height) {
765
769
  }
766
770
  return result;
767
771
  }
772
+ /**
773
+ * Convert RGB color to CMYK color space
774
+ * @param r Red component (0-255)
775
+ * @param g Green component (0-255)
776
+ * @param b Blue component (0-255)
777
+ * @returns CMYK values: [c (0-1), m (0-1), y (0-1), k (0-1)]
778
+ */
779
+ function rgbToCmyk(r, g, b) {
780
+ // Normalize RGB values to 0-1 range
781
+ const rNorm = r / 255;
782
+ const gNorm = g / 255;
783
+ const bNorm = b / 255;
784
+ // Calculate K (key/black)
785
+ const k = 1 - Math.max(rNorm, gNorm, bNorm);
786
+ // If K is 1 (pure black), CMY are undefined but conventionally set to 0
787
+ if (k === 1) {
788
+ return [0, 0, 0, 1];
789
+ }
790
+ // Calculate CMY components
791
+ const c = (1 - rNorm - k) / (1 - k);
792
+ const m = (1 - gNorm - k) / (1 - k);
793
+ const y = (1 - bNorm - k) / (1 - k);
794
+ return [c, m, y, k];
795
+ }
796
+ /**
797
+ * Convert CMYK color to RGB color space
798
+ * @param c Cyan component (0-1)
799
+ * @param m Magenta component (0-1)
800
+ * @param y Yellow component (0-1)
801
+ * @param k Key/Black component (0-1)
802
+ * @returns RGB values: [r (0-255), g (0-255), b (0-255)]
803
+ */
804
+ function cmykToRgb(c, m, y, k) {
805
+ // Convert CMYK to RGB
806
+ const r = 255 * (1 - c) * (1 - k);
807
+ const g = 255 * (1 - m) * (1 - k);
808
+ const b = 255 * (1 - y) * (1 - k);
809
+ return [
810
+ Math.round(Math.max(0, Math.min(255, r))),
811
+ Math.round(Math.max(0, Math.min(255, g))),
812
+ Math.round(Math.max(0, Math.min(255, b))),
813
+ ];
814
+ }
815
+ /**
816
+ * Convert RGBA image data to CMYK representation
817
+ * Returns a Float32Array with 4 values per pixel (C, M, Y, K) in 0-1 range
818
+ * @param data Image data (RGBA)
819
+ * @returns CMYK image data as Float32Array (4 values per pixel)
820
+ */
821
+ function rgbaToCmyk(data) {
822
+ const pixelCount = data.length / 4;
823
+ const result = new Float32Array(pixelCount * 4);
824
+ for (let i = 0; i < data.length; i += 4) {
825
+ const r = data[i];
826
+ const g = data[i + 1];
827
+ const b = data[i + 2];
828
+ const [c, m, y, k] = rgbToCmyk(r, g, b);
829
+ const outIdx = (i / 4) * 4;
830
+ result[outIdx] = c;
831
+ result[outIdx + 1] = m;
832
+ result[outIdx + 2] = y;
833
+ result[outIdx + 3] = k;
834
+ }
835
+ return result;
836
+ }
837
+ /**
838
+ * Convert CMYK image data to RGBA representation
839
+ * @param cmykData CMYK image data (4 values per pixel in 0-1 range)
840
+ * @param alpha Optional alpha value for all pixels (0-255, default: 255)
841
+ * @returns RGBA image data
842
+ */
843
+ function cmykToRgba(cmykData, alpha = 255) {
844
+ const pixelCount = cmykData.length / 4;
845
+ const result = new Uint8Array(pixelCount * 4);
846
+ for (let i = 0; i < cmykData.length; i += 4) {
847
+ const c = cmykData[i];
848
+ const m = cmykData[i + 1];
849
+ const y = cmykData[i + 2];
850
+ const k = cmykData[i + 3];
851
+ const [r, g, b] = cmykToRgb(c, m, y, k);
852
+ const outIdx = i;
853
+ result[outIdx] = r;
854
+ result[outIdx + 1] = g;
855
+ result[outIdx + 2] = b;
856
+ result[outIdx + 3] = alpha;
857
+ }
858
+ return result;
859
+ }
@@ -8,7 +8,17 @@
8
8
  * This is a pure JavaScript implementation that handles common JPEG files.
9
9
  * For complex or non-standard JPEGs, the ImageDecoder API fallback is preferred.
10
10
  */
11
- import type { ImageDecoderOptions } from "../types.js";
11
+ import type { ImageDecoderOptions, JPEGQuantizedCoefficients } from "../types.js";
12
+ /**
13
+ * Extended decoder options including coefficient extraction
14
+ */
15
+ interface JPEGDecoderOptions extends ImageDecoderOptions {
16
+ /**
17
+ * When true, stores quantized DCT coefficients for later retrieval
18
+ * via getQuantizedCoefficients(). Coefficients are stored in zigzag order.
19
+ */
20
+ extractCoefficients?: boolean;
21
+ }
12
22
  export declare class JPEGDecoder {
13
23
  private data;
14
24
  private pos;
@@ -29,8 +39,20 @@ export declare class JPEGDecoder {
29
39
  private successiveLow;
30
40
  private scanComponentIds;
31
41
  private eobRun;
32
- constructor(data: Uint8Array, settings?: ImageDecoderOptions);
42
+ private quantizedCoefficients;
43
+ constructor(data: Uint8Array, settings?: JPEGDecoderOptions);
33
44
  decode(): Uint8Array;
45
+ /**
46
+ * Get the quantized DCT coefficients after decoding
47
+ * Only available if extractCoefficients option was set to true
48
+ * @returns JPEGQuantizedCoefficients or undefined if not available
49
+ */
50
+ getQuantizedCoefficients(): JPEGQuantizedCoefficients | undefined;
51
+ /**
52
+ * Store quantized coefficients in the output structure
53
+ * Called after decoding when extractCoefficients is true
54
+ */
55
+ private storeQuantizedCoefficients;
34
56
  private readMarker;
35
57
  private readUint16;
36
58
  private skipSegment;
@@ -49,4 +71,5 @@ export declare class JPEGDecoder {
49
71
  private idct;
50
72
  private convertToRGB;
51
73
  }
74
+ export {};
52
75
  //# sourceMappingURL=jpeg_decoder.d.ts.map
@@ -214,10 +214,18 @@ class JPEGDecoder {
214
214
  writable: true,
215
215
  value: 0
216
216
  }); // Remaining blocks to skip due to EOBn
217
+ // Storage for quantized coefficients (when extractCoefficients is true)
218
+ Object.defineProperty(this, "quantizedCoefficients", {
219
+ enumerable: true,
220
+ configurable: true,
221
+ writable: true,
222
+ value: null
223
+ });
217
224
  this.data = data;
218
225
  this.options = {
219
226
  tolerantDecoding: settings.tolerantDecoding ?? true,
220
227
  onWarning: settings.onWarning,
228
+ extractCoefficients: settings.extractCoefficients ?? false,
221
229
  };
222
230
  }
223
231
  decode() {
@@ -279,7 +287,8 @@ class JPEGDecoder {
279
287
  // For progressive JPEGs, perform IDCT on all blocks after all scans are complete
280
288
  // This ensures that frequency-domain coefficients from multiple scans are properly
281
289
  // accumulated before transformation to spatial domain
282
- if (this.isProgressive) {
290
+ // Skip IDCT when extracting coefficients - we want the quantized DCT values
291
+ if (this.isProgressive && !this.options.extractCoefficients) {
283
292
  for (const component of this.components) {
284
293
  if (component.blocks) {
285
294
  for (const row of component.blocks) {
@@ -290,9 +299,63 @@ class JPEGDecoder {
290
299
  }
291
300
  }
292
301
  }
302
+ // If extracting coefficients, store them before converting to RGB
303
+ if (this.options.extractCoefficients) {
304
+ this.storeQuantizedCoefficients();
305
+ }
293
306
  // Convert YCbCr to RGB
294
307
  return this.convertToRGB();
295
308
  }
309
+ /**
310
+ * Get the quantized DCT coefficients after decoding
311
+ * Only available if extractCoefficients option was set to true
312
+ * @returns JPEGQuantizedCoefficients or undefined if not available
313
+ */
314
+ getQuantizedCoefficients() {
315
+ return this.quantizedCoefficients ?? undefined;
316
+ }
317
+ /**
318
+ * Store quantized coefficients in the output structure
319
+ * Called after decoding when extractCoefficients is true
320
+ */
321
+ storeQuantizedCoefficients() {
322
+ // Calculate MCU dimensions
323
+ const maxH = Math.max(...this.components.map((c) => c.h));
324
+ const maxV = Math.max(...this.components.map((c) => c.v));
325
+ const mcuWidth = Math.ceil(this.width / (8 * maxH));
326
+ const mcuHeight = Math.ceil(this.height / (8 * maxV));
327
+ // Build component coefficients
328
+ const componentCoeffs = this.components.map((comp) => ({
329
+ id: comp.id,
330
+ h: comp.h,
331
+ v: comp.v,
332
+ qTable: comp.qTable,
333
+ blocks: comp.blocks.map((row) => row.map((block) => {
334
+ // Convert to Int32Array if not already
335
+ if (block instanceof Int32Array) {
336
+ return block;
337
+ }
338
+ return new Int32Array(block);
339
+ })),
340
+ }));
341
+ // Copy quantization tables
342
+ const qTables = this.qTables.map((table) => {
343
+ if (table instanceof Uint8Array) {
344
+ return new Uint8Array(table);
345
+ }
346
+ return new Uint8Array(table);
347
+ });
348
+ this.quantizedCoefficients = {
349
+ format: "jpeg",
350
+ width: this.width,
351
+ height: this.height,
352
+ isProgressive: this.isProgressive,
353
+ components: componentCoeffs,
354
+ quantizationTables: qTables,
355
+ mcuWidth,
356
+ mcuHeight,
357
+ };
358
+ }
296
359
  readMarker() {
297
360
  while (this.pos < this.data.length && this.data[this.pos] !== 0xFF) {
298
361
  this.pos++;
@@ -593,7 +656,13 @@ class JPEGDecoder {
593
656
  component.pred += dcDiff;
594
657
  // For successive approximation, shift the coefficient left by Al bits
595
658
  const coeff = component.pred << this.successiveLow;
596
- block[0] = coeff * this.qTables[component.qTable][0];
659
+ // When extracting coefficients, store quantized value without dequantization
660
+ if (this.options.extractCoefficients) {
661
+ block[0] = coeff;
662
+ }
663
+ else {
664
+ block[0] = coeff * this.qTables[component.qTable][0];
665
+ }
597
666
  }
598
667
  else {
599
668
  // DC refinement scan: add a refinement bit
@@ -601,7 +670,12 @@ class JPEGDecoder {
601
670
  if (bit) {
602
671
  // Add the refinement bit at position Al
603
672
  const refinement = 1 << this.successiveLow;
604
- block[0] += refinement * this.qTables[component.qTable][0];
673
+ if (this.options.extractCoefficients) {
674
+ block[0] += refinement;
675
+ }
676
+ else {
677
+ block[0] += refinement * this.qTables[component.qTable][0];
678
+ }
605
679
  }
606
680
  }
607
681
  }
@@ -648,8 +722,14 @@ class JPEGDecoder {
648
722
  break;
649
723
  // For successive approximation, shift the coefficient left by Al bits
650
724
  const coeff = this.receiveBits(s) << this.successiveLow;
651
- block[ZIGZAG[k]] = coeff *
652
- this.qTables[component.qTable][ZIGZAG[k]];
725
+ // When extracting coefficients, store quantized value without dequantization
726
+ if (this.options.extractCoefficients) {
727
+ block[ZIGZAG[k]] = coeff;
728
+ }
729
+ else {
730
+ block[ZIGZAG[k]] = coeff *
731
+ this.qTables[component.qTable][ZIGZAG[k]];
732
+ }
653
733
  k++;
654
734
  }
655
735
  }
@@ -696,7 +776,10 @@ class JPEGDecoder {
696
776
  if (current !== 0) {
697
777
  const bit = this.readBit();
698
778
  if (bit) {
699
- const refinement = (1 << this.successiveLow) * qTable[z];
779
+ // When extracting coefficients, don't dequantize
780
+ const refinement = this.options.extractCoefficients
781
+ ? (1 << this.successiveLow)
782
+ : (1 << this.successiveLow) * qTable[z];
700
783
  block[z] += direction * refinement;
701
784
  }
702
785
  }
@@ -711,13 +794,17 @@ class JPEGDecoder {
711
794
  if (current !== 0) {
712
795
  const bit = this.readBit();
713
796
  if (bit) {
714
- const refinement = (1 << this.successiveLow) * qTable[z];
797
+ // When extracting coefficients, don't dequantize
798
+ const refinement = this.options.extractCoefficients
799
+ ? (1 << this.successiveLow)
800
+ : (1 << this.successiveLow) * qTable[z];
715
801
  block[z] += direction * refinement;
716
802
  }
717
803
  }
718
804
  else {
719
805
  const newCoeff = successiveACNextValue << this.successiveLow;
720
- block[z] = newCoeff * qTable[z];
806
+ // When extracting coefficients, don't dequantize
807
+ block[z] = this.options.extractCoefficients ? newCoeff : newCoeff * qTable[z];
721
808
  successiveACState = 0;
722
809
  }
723
810
  break;
@@ -725,7 +812,10 @@ class JPEGDecoder {
725
812
  if (current !== 0) {
726
813
  const bit = this.readBit();
727
814
  if (bit) {
728
- const refinement = (1 << this.successiveLow) * qTable[z];
815
+ // When extracting coefficients, don't dequantize
816
+ const refinement = this.options.extractCoefficients
817
+ ? (1 << this.successiveLow)
818
+ : (1 << this.successiveLow) * qTable[z];
729
819
  block[z] += direction * refinement;
730
820
  }
731
821
  }
@@ -744,7 +834,8 @@ class JPEGDecoder {
744
834
  // Perform IDCT only for baseline JPEGs
745
835
  // For progressive JPEGs, IDCT is deferred until all scans are complete
746
836
  // to preserve frequency-domain coefficients for accumulation across scans
747
- if (!this.isProgressive) {
837
+ // Skip IDCT when extracting coefficients - we want the quantized DCT values
838
+ if (!this.isProgressive && !this.options.extractCoefficients) {
748
839
  this.idct(block);
749
840
  }
750
841
  }
@@ -5,6 +5,7 @@
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
+ import type { JPEGQuantizedCoefficients } from "../types.js";
8
9
  export interface JPEGEncoderOptions {
9
10
  quality?: number;
10
11
  progressive?: boolean;
@@ -43,5 +44,23 @@ export declare class JPEGEncoder {
43
44
  private forwardDCT;
44
45
  private encodeDC;
45
46
  private encodeAC;
47
+ /**
48
+ * Encode JPEG from pre-quantized DCT coefficients
49
+ * Skips DCT and quantization - uses provided coefficients directly
50
+ * Useful for steganography where coefficients are modified and re-encoded
51
+ * @param coeffs JPEG quantized coefficients
52
+ * @param _options Optional encoding options (currently unused)
53
+ * @returns Encoded JPEG bytes
54
+ */
55
+ encodeFromCoefficients(coeffs: JPEGQuantizedCoefficients, _options?: JPEGEncoderOptions): Uint8Array;
56
+ private writeDQTFromCoeffs;
57
+ private writeSOF0FromCoeffs;
58
+ private writeSOF2FromCoeffs;
59
+ private encodeScanFromCoeffs;
60
+ private encodeACFromCoeffs;
61
+ private encodeProgressiveFromCoeffs;
62
+ private encodeProgressiveDCScanFromCoeffs;
63
+ private encodeProgressiveACScanFromCoeffs;
64
+ private encodeOnlyACFromCoeffs;
46
65
  }
47
66
  //# sourceMappingURL=jpeg_encoder.d.ts.map