cross-image 0.4.1 → 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.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @cross/image
2
2
 
3
- A pure JavaScript, dependency-free, cross-runtime image processing library for Deno, Node.js, and
4
- Bun. Decode, encode, manipulate, and process images in multiple formats including PNG, JPEG, WebP,
5
- GIF, and more—all without native dependencies.
3
+ A pure JavaScript, dependency-free, cross-runtime image processing library for Deno, Node.js, Bun
4
+ and browsers. Decode, encode, manipulate, and process images in multiple formats including PNG,
5
+ JPEG, WebP, GIF, and more—all without native dependencies.
6
6
 
7
7
  📚 **[Full Documentation](https://cross-image.56k.guru/)**
8
8
 
@@ -10,7 +10,7 @@ GIF, and more—all without native dependencies.
10
10
 
11
11
  - 🚀 **Pure JavaScript** - No native dependencies
12
12
  - 🔌 **Pluggable formats** - Easy to extend with custom formats
13
- - 📦 **Cross-runtime** - Works on Deno, Node.js (18+), and Bun
13
+ - 📦 **Cross-runtime** - Works on Deno, Node.js (18+), Bun and Browsers.
14
14
  - 🎨 **Multiple formats** - PNG, APNG, JPEG, WebP, GIF, TIFF, BMP, ICO, DNG, PAM, PPM, PCX, ASCII,
15
15
  HEIC, and AVIF support
16
16
  - ✂️ **Image manipulation** - Resize, crop, composite, and more
@@ -284,6 +284,79 @@ const compressed = await image.encode("tiff", {
284
284
  - **Full compression support**: CMYK works with all TIFF compression methods
285
285
  - **Industry standard**: TIFF is the preferred format for CMYK images in print production
286
286
 
287
+ ## JPEG Coefficient Extraction
288
+
289
+ The library provides advanced APIs for extracting and encoding JPEG quantized DCT coefficients,
290
+ enabling coefficient-domain steganography and other frequency-domain processing techniques.
291
+
292
+ ### Extracting Coefficients
293
+
294
+ ```typescript
295
+ import { Image } from "jsr:@cross/image";
296
+
297
+ const data = await Deno.readFile("photo.jpg");
298
+
299
+ // Extract quantized DCT coefficients
300
+ const coefficients = await Image.extractCoefficients(data, "jpeg");
301
+
302
+ if (coefficients) {
303
+ console.log(`Image: ${coefficients.width}x${coefficients.height}`);
304
+ console.log(`Progressive: ${coefficients.isProgressive}`);
305
+ console.log(`Components: ${coefficients.components.length}`);
306
+
307
+ // Access coefficient blocks (Y, Cb, Cr components)
308
+ for (const comp of coefficients.components) {
309
+ console.log(`Component ${comp.id}: ${comp.blocks.length} block rows`);
310
+ }
311
+ }
312
+ ```
313
+
314
+ ### Modifying and Re-encoding Coefficients
315
+
316
+ ```typescript
317
+ import { Image } from "jsr:@cross/image";
318
+
319
+ const data = await Deno.readFile("input.jpg");
320
+ const coefficients = await Image.extractCoefficients(data, "jpeg");
321
+
322
+ if (coefficients) {
323
+ // Modify coefficients (e.g., for steganography)
324
+ for (const comp of coefficients.components) {
325
+ for (const row of comp.blocks) {
326
+ for (const block of row) {
327
+ // Modify AC coefficients (indices 1-63)
328
+ // DC coefficient is at index 0
329
+ // block is an Int32Array of 64 quantized DCT values in zigzag order
330
+ }
331
+ }
332
+ }
333
+
334
+ // Re-encode to JPEG
335
+ const encoded = await Image.encodeFromCoefficients(coefficients, "jpeg");
336
+ await Deno.writeFile("output.jpg", encoded);
337
+ }
338
+ ```
339
+
340
+ ### Coefficient Structure
341
+
342
+ The `JPEGQuantizedCoefficients` type provides:
343
+
344
+ - `width`, `height` - Image dimensions
345
+ - `isProgressive` - Whether the source was progressive JPEG
346
+ - `components` - Array of Y, Cb, Cr (or grayscale) component data
347
+ - `quantizationTables` - The quantization tables used
348
+ - `mcuWidth`, `mcuHeight` - MCU block dimensions
349
+
350
+ Each component contains `blocks[][]` where each block is an `Int32Array` of 64 quantized DCT
351
+ coefficients in zigzag order.
352
+
353
+ **Use Cases:**
354
+
355
+ - DCT-domain steganography (survives JPEG re-compression)
356
+ - Coefficient analysis and visualization
357
+ - Custom frequency-domain filtering
358
+ - Forensic analysis of JPEG compression artifacts
359
+
287
360
  ## Base64 / Data URLs
288
361
 
289
362
  The library includes small utilities for working with base64 and `data:` URLs.
package/esm/mod.d.ts CHANGED
@@ -43,7 +43,7 @@
43
43
  * ```
44
44
  */
45
45
  export { Image } from "./src/image.js";
46
- export type { APNGEncoderOptions, ASCIIEncoderOptions, AVIFEncoderOptions, FrameMetadata, GIFEncoderOptions, HEICEncoderOptions, ImageData, ImageDecoderOptions, ImageFormat, ImageFrame, ImageMetadata, JPEGEncoderOptions, MultiFrameImageData, PNGEncoderOptions, ResizeOptions, TIFFEncoderOptions, WebPEncoderOptions, } from "./src/types.js";
46
+ export type { APNGEncoderOptions, ASCIIEncoderOptions, AVIFEncoderOptions, CoefficientData, FrameMetadata, GIFEncoderOptions, HEICEncoderOptions, ImageData, ImageDecoderOptions, ImageFormat, ImageFrame, ImageMetadata, JPEGComponentCoefficients, JPEGEncoderOptions, JPEGQuantizedCoefficients, MultiFrameImageData, PNGEncoderOptions, ResizeOptions, TIFFEncoderOptions, WebPEncoderOptions, } from "./src/types.js";
47
47
  export { PNGFormat } from "./src/formats/png.js";
48
48
  export { APNGFormat } from "./src/formats/apng.js";
49
49
  export { JPEGFormat } from "./src/formats/jpeg.js";
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, JPEGEncoderOptions } from "../types.js";
1
+ import type { CoefficientData, ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, JPEGEncoderOptions, JPEGQuantizedCoefficients } from "../types.js";
2
2
  /**
3
3
  * JPEG format handler
4
4
  * Implements a basic JPEG decoder and encoder
@@ -42,6 +42,26 @@ export declare class JPEGFormat implements ImageFormat {
42
42
  * Get the list of metadata fields supported by JPEG format
43
43
  */
44
44
  getSupportedMetadata(): Array<keyof ImageMetadata>;
45
+ /**
46
+ * Extract quantized DCT coefficients from JPEG data
47
+ * These coefficients can be modified for steganography and re-encoded
48
+ * @param data Raw JPEG data
49
+ * @param options Decoder options
50
+ * @returns JPEGQuantizedCoefficients or undefined if extraction fails
51
+ */
52
+ extractCoefficients(data: Uint8Array, options?: ImageDecoderOptions): Promise<JPEGQuantizedCoefficients | undefined>;
53
+ /**
54
+ * Type guard to check if coefficient data is JPEG format
55
+ */
56
+ private isJPEGCoefficients;
57
+ /**
58
+ * Encode JPEG from quantized DCT coefficients
59
+ * Useful for steganography - modify coefficients and re-encode
60
+ * @param coeffs JPEG quantized coefficients
61
+ * @param options Encoding options
62
+ * @returns Encoded JPEG bytes
63
+ */
64
+ encodeFromCoefficients(coeffs: CoefficientData, options?: JPEGEncoderOptions): Promise<Uint8Array>;
45
65
  /**
46
66
  * Extract metadata from JPEG data without fully decoding the pixel data
47
67
  * This quickly parses JFIF and EXIF markers to extract metadata
@@ -1072,6 +1072,65 @@ export class JPEGFormat {
1072
1072
  "dpiY",
1073
1073
  ];
1074
1074
  }
1075
+ /**
1076
+ * Extract quantized DCT coefficients from JPEG data
1077
+ * These coefficients can be modified for steganography and re-encoded
1078
+ * @param data Raw JPEG data
1079
+ * @param options Decoder options
1080
+ * @returns JPEGQuantizedCoefficients or undefined if extraction fails
1081
+ */
1082
+ async extractCoefficients(data, options) {
1083
+ if (!this.canDecode(data)) {
1084
+ return undefined;
1085
+ }
1086
+ try {
1087
+ // Force pure-JS decoding since runtime decoders don't expose coefficients
1088
+ const { JPEGDecoder } = await import("../utils/jpeg_decoder.js");
1089
+ const decoder = new JPEGDecoder(data, {
1090
+ tolerantDecoding: options?.tolerantDecoding ?? true,
1091
+ onWarning: options?.onWarning,
1092
+ extractCoefficients: true,
1093
+ });
1094
+ // Decode to extract coefficients
1095
+ decoder.decode();
1096
+ // Get the quantized coefficients
1097
+ return decoder.getQuantizedCoefficients();
1098
+ }
1099
+ catch (error) {
1100
+ if (options?.onWarning) {
1101
+ options.onWarning(`Failed to extract JPEG coefficients: ${error}`, error);
1102
+ }
1103
+ return undefined;
1104
+ }
1105
+ }
1106
+ /**
1107
+ * Type guard to check if coefficient data is JPEG format
1108
+ */
1109
+ isJPEGCoefficients(coeffs) {
1110
+ return ("format" in coeffs &&
1111
+ coeffs.format === "jpeg" &&
1112
+ "components" in coeffs &&
1113
+ "quantizationTables" in coeffs &&
1114
+ "isProgressive" in coeffs);
1115
+ }
1116
+ /**
1117
+ * Encode JPEG from quantized DCT coefficients
1118
+ * Useful for steganography - modify coefficients and re-encode
1119
+ * @param coeffs JPEG quantized coefficients
1120
+ * @param options Encoding options
1121
+ * @returns Encoded JPEG bytes
1122
+ */
1123
+ async encodeFromCoefficients(coeffs, options) {
1124
+ if (!this.isJPEGCoefficients(coeffs)) {
1125
+ throw new Error("Invalid coefficient format for JPEG");
1126
+ }
1127
+ const { JPEGEncoder } = await import("../utils/jpeg_encoder.js");
1128
+ const encoder = new JPEGEncoder({
1129
+ quality: options?.quality,
1130
+ progressive: options?.progressive ?? coeffs.isProgressive,
1131
+ });
1132
+ return encoder.encodeFromCoefficients(coeffs, options);
1133
+ }
1075
1134
  /**
1076
1135
  * Extract metadata from JPEG data without fully decoding the pixel data
1077
1136
  * This quickly parses JFIF and EXIF markers to extract metadata
@@ -1,4 +1,4 @@
1
- import type { ImageDecoderOptions, ImageFormat, ImageMetadata, MultiFrameImageData, ResizeOptions } from "./types.js";
1
+ import type { CoefficientData, ImageDecoderOptions, ImageFormat, ImageMetadata, MultiFrameImageData, ResizeOptions } from "./types.js";
2
2
  /**
3
3
  * Main Image class for reading, manipulating, and saving images
4
4
  */
@@ -98,6 +98,44 @@ export declare class Image {
98
98
  * @returns Metadata extracted from the image, or undefined if extraction fails or format is unsupported
99
99
  */
100
100
  static extractMetadata(data: Uint8Array, format?: string): Promise<ImageMetadata | undefined>;
101
+ /**
102
+ * Extract coefficients from encoded image data
103
+ * For JPEG, this returns quantized DCT coefficients that can be modified for steganography
104
+ * and re-encoded using encodeFromCoefficients()
105
+ * @param data Raw image data
106
+ * @param format Optional format hint (e.g., "jpeg")
107
+ * @param options Optional decoder options
108
+ * @returns Format-specific coefficient structure or undefined if not supported
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * // Extract JPEG coefficients for steganography
113
+ * const coeffs = await Image.extractCoefficients(jpegData, "jpeg");
114
+ * if (coeffs) {
115
+ * // Modify coefficients for steganography...
116
+ * const modified = await Image.encodeFromCoefficients(coeffs, "jpeg");
117
+ * }
118
+ * ```
119
+ */
120
+ static extractCoefficients(data: Uint8Array, format?: string, options?: ImageDecoderOptions): Promise<CoefficientData | undefined>;
121
+ /**
122
+ * Encode image from coefficients
123
+ * For JPEG, accepts quantized DCT coefficients and produces a valid JPEG file
124
+ * Useful for steganography where coefficients are extracted, modified, and re-encoded
125
+ * @param coeffs Format-specific coefficient structure
126
+ * @param format Optional format hint (auto-detected from coeffs.format if available)
127
+ * @param options Optional format-specific encoding options
128
+ * @returns Encoded image bytes
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * // Re-encode modified JPEG coefficients
133
+ * const coeffs = await Image.extractCoefficients(jpegData, "jpeg");
134
+ * // Modify coefficients...
135
+ * const encoded = await Image.encodeFromCoefficients(coeffs, "jpeg");
136
+ * ```
137
+ */
138
+ static encodeFromCoefficients(coeffs: CoefficientData, format?: string, options?: unknown): Promise<Uint8Array>;
101
139
  /**
102
140
  * Read an image from bytes
103
141
  * @deprecated Use `decode()` instead. This method will be removed in a future version.
package/esm/src/image.js CHANGED
@@ -230,6 +230,74 @@ export class Image {
230
230
  }
231
231
  return undefined;
232
232
  }
233
+ /**
234
+ * Extract coefficients from encoded image data
235
+ * For JPEG, this returns quantized DCT coefficients that can be modified for steganography
236
+ * and re-encoded using encodeFromCoefficients()
237
+ * @param data Raw image data
238
+ * @param format Optional format hint (e.g., "jpeg")
239
+ * @param options Optional decoder options
240
+ * @returns Format-specific coefficient structure or undefined if not supported
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * // Extract JPEG coefficients for steganography
245
+ * const coeffs = await Image.extractCoefficients(jpegData, "jpeg");
246
+ * if (coeffs) {
247
+ * // Modify coefficients for steganography...
248
+ * const modified = await Image.encodeFromCoefficients(coeffs, "jpeg");
249
+ * }
250
+ * ```
251
+ */
252
+ static async extractCoefficients(data, format, options) {
253
+ // Try specified format first
254
+ if (format) {
255
+ const handler = Image.formats.find((f) => f.name === format);
256
+ if (handler && handler.canDecode(data) && handler.extractCoefficients) {
257
+ return await handler.extractCoefficients(data, options);
258
+ }
259
+ }
260
+ // Auto-detect format
261
+ for (const handler of Image.formats) {
262
+ if (handler.canDecode(data) && handler.extractCoefficients) {
263
+ return await handler.extractCoefficients(data, options);
264
+ }
265
+ }
266
+ return undefined;
267
+ }
268
+ /**
269
+ * Encode image from coefficients
270
+ * For JPEG, accepts quantized DCT coefficients and produces a valid JPEG file
271
+ * Useful for steganography where coefficients are extracted, modified, and re-encoded
272
+ * @param coeffs Format-specific coefficient structure
273
+ * @param format Optional format hint (auto-detected from coeffs.format if available)
274
+ * @param options Optional format-specific encoding options
275
+ * @returns Encoded image bytes
276
+ *
277
+ * @example
278
+ * ```ts
279
+ * // Re-encode modified JPEG coefficients
280
+ * const coeffs = await Image.extractCoefficients(jpegData, "jpeg");
281
+ * // Modify coefficients...
282
+ * const encoded = await Image.encodeFromCoefficients(coeffs, "jpeg");
283
+ * ```
284
+ */
285
+ static async encodeFromCoefficients(coeffs, format, options) {
286
+ // Detect format from coefficient structure or use provided format
287
+ const detectedFormat = format ??
288
+ coeffs.format;
289
+ if (!detectedFormat) {
290
+ throw new Error("Format must be specified or present in coefficient data");
291
+ }
292
+ const handler = Image.formats.find((f) => f.name === detectedFormat);
293
+ if (!handler) {
294
+ throw new Error(`Unknown format: ${detectedFormat}`);
295
+ }
296
+ if (!handler.encodeFromCoefficients) {
297
+ throw new Error(`Format ${detectedFormat} does not support encoding from coefficients`);
298
+ }
299
+ return await handler.encodeFromCoefficients(coeffs, options);
300
+ }
233
301
  /**
234
302
  * Read an image from bytes
235
303
  * @deprecated Use `decode()` instead. This method will be removed in a future version.
@@ -1,3 +1,49 @@
1
+ /**
2
+ * JPEG quantized DCT coefficients for steganography and advanced processing
3
+ * Contains the frequency-domain representation of the image
4
+ */
5
+ export interface JPEGQuantizedCoefficients {
6
+ /** Format identifier */
7
+ format: "jpeg";
8
+ /** Image width in pixels */
9
+ width: number;
10
+ /** Image height in pixels */
11
+ height: number;
12
+ /** Whether the JPEG is progressive */
13
+ isProgressive: boolean;
14
+ /** Component data (Y, Cb, Cr for color images) */
15
+ components: JPEGComponentCoefficients[];
16
+ /** Quantization tables used (indexed by table ID) */
17
+ quantizationTables: (Uint8Array | number[])[];
18
+ /** MCU width (number of 8x8 blocks horizontally) */
19
+ mcuWidth: number;
20
+ /** MCU height (number of 8x8 blocks vertically) */
21
+ mcuHeight: number;
22
+ }
23
+ /**
24
+ * Coefficients for a single JPEG component (Y, Cb, or Cr)
25
+ */
26
+ export interface JPEGComponentCoefficients {
27
+ /** Component ID (1=Y, 2=Cb, 3=Cr typically) */
28
+ id: number;
29
+ /** Horizontal sampling factor */
30
+ h: number;
31
+ /** Vertical sampling factor */
32
+ v: number;
33
+ /** Quantization table index */
34
+ qTable: number;
35
+ /**
36
+ * Quantized DCT coefficient blocks
37
+ * blocks[blockRow][blockCol] contains a 64-element array in zigzag order
38
+ * Coefficients are quantized (divided by quantization table values)
39
+ */
40
+ blocks: Int32Array[][];
41
+ }
42
+ /**
43
+ * Union type for coefficient data from different formats
44
+ * Currently only JPEG is supported, but this allows for future extension
45
+ */
46
+ export type CoefficientData = JPEGQuantizedCoefficients;
1
47
  /**
2
48
  * Image metadata
3
49
  */
@@ -339,5 +385,22 @@ export interface ImageFormat {
339
385
  * @returns Metadata extracted from the image, or undefined if extraction fails
340
386
  */
341
387
  extractMetadata?(data: Uint8Array): Promise<ImageMetadata | undefined>;
388
+ /**
389
+ * Extract coefficients from encoded image data (optional)
390
+ * For JPEG, this returns quantized DCT coefficients
391
+ * Useful for steganography and advanced image processing
392
+ * @param data Raw image data
393
+ * @param options Decoder options
394
+ * @returns Format-specific coefficient structure or undefined if not supported
395
+ */
396
+ extractCoefficients?(data: Uint8Array, options?: ImageDecoderOptions): Promise<CoefficientData | undefined>;
397
+ /**
398
+ * Encode image from coefficients (optional)
399
+ * For JPEG, accepts quantized DCT coefficients and produces a valid JPEG
400
+ * @param coeffs Format-specific coefficient structure
401
+ * @param options Format-specific encoding options
402
+ * @returns Encoded image bytes
403
+ */
404
+ encodeFromCoefficients?(coeffs: CoefficientData, options?: unknown): Promise<Uint8Array>;
342
405
  }
343
406
  //# sourceMappingURL=types.d.ts.map
@@ -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
@@ -211,10 +211,18 @@ export class JPEGDecoder {
211
211
  writable: true,
212
212
  value: 0
213
213
  }); // Remaining blocks to skip due to EOBn
214
+ // Storage for quantized coefficients (when extractCoefficients is true)
215
+ Object.defineProperty(this, "quantizedCoefficients", {
216
+ enumerable: true,
217
+ configurable: true,
218
+ writable: true,
219
+ value: null
220
+ });
214
221
  this.data = data;
215
222
  this.options = {
216
223
  tolerantDecoding: settings.tolerantDecoding ?? true,
217
224
  onWarning: settings.onWarning,
225
+ extractCoefficients: settings.extractCoefficients ?? false,
218
226
  };
219
227
  }
220
228
  decode() {
@@ -276,7 +284,8 @@ export class JPEGDecoder {
276
284
  // For progressive JPEGs, perform IDCT on all blocks after all scans are complete
277
285
  // This ensures that frequency-domain coefficients from multiple scans are properly
278
286
  // accumulated before transformation to spatial domain
279
- if (this.isProgressive) {
287
+ // Skip IDCT when extracting coefficients - we want the quantized DCT values
288
+ if (this.isProgressive && !this.options.extractCoefficients) {
280
289
  for (const component of this.components) {
281
290
  if (component.blocks) {
282
291
  for (const row of component.blocks) {
@@ -287,9 +296,63 @@ export class JPEGDecoder {
287
296
  }
288
297
  }
289
298
  }
299
+ // If extracting coefficients, store them before converting to RGB
300
+ if (this.options.extractCoefficients) {
301
+ this.storeQuantizedCoefficients();
302
+ }
290
303
  // Convert YCbCr to RGB
291
304
  return this.convertToRGB();
292
305
  }
306
+ /**
307
+ * Get the quantized DCT coefficients after decoding
308
+ * Only available if extractCoefficients option was set to true
309
+ * @returns JPEGQuantizedCoefficients or undefined if not available
310
+ */
311
+ getQuantizedCoefficients() {
312
+ return this.quantizedCoefficients ?? undefined;
313
+ }
314
+ /**
315
+ * Store quantized coefficients in the output structure
316
+ * Called after decoding when extractCoefficients is true
317
+ */
318
+ storeQuantizedCoefficients() {
319
+ // Calculate MCU dimensions
320
+ const maxH = Math.max(...this.components.map((c) => c.h));
321
+ const maxV = Math.max(...this.components.map((c) => c.v));
322
+ const mcuWidth = Math.ceil(this.width / (8 * maxH));
323
+ const mcuHeight = Math.ceil(this.height / (8 * maxV));
324
+ // Build component coefficients
325
+ const componentCoeffs = this.components.map((comp) => ({
326
+ id: comp.id,
327
+ h: comp.h,
328
+ v: comp.v,
329
+ qTable: comp.qTable,
330
+ blocks: comp.blocks.map((row) => row.map((block) => {
331
+ // Convert to Int32Array if not already
332
+ if (block instanceof Int32Array) {
333
+ return block;
334
+ }
335
+ return new Int32Array(block);
336
+ })),
337
+ }));
338
+ // Copy quantization tables
339
+ const qTables = this.qTables.map((table) => {
340
+ if (table instanceof Uint8Array) {
341
+ return new Uint8Array(table);
342
+ }
343
+ return new Uint8Array(table);
344
+ });
345
+ this.quantizedCoefficients = {
346
+ format: "jpeg",
347
+ width: this.width,
348
+ height: this.height,
349
+ isProgressive: this.isProgressive,
350
+ components: componentCoeffs,
351
+ quantizationTables: qTables,
352
+ mcuWidth,
353
+ mcuHeight,
354
+ };
355
+ }
293
356
  readMarker() {
294
357
  while (this.pos < this.data.length && this.data[this.pos] !== 0xFF) {
295
358
  this.pos++;
@@ -590,7 +653,13 @@ export class JPEGDecoder {
590
653
  component.pred += dcDiff;
591
654
  // For successive approximation, shift the coefficient left by Al bits
592
655
  const coeff = component.pred << this.successiveLow;
593
- block[0] = coeff * this.qTables[component.qTable][0];
656
+ // When extracting coefficients, store quantized value without dequantization
657
+ if (this.options.extractCoefficients) {
658
+ block[0] = coeff;
659
+ }
660
+ else {
661
+ block[0] = coeff * this.qTables[component.qTable][0];
662
+ }
594
663
  }
595
664
  else {
596
665
  // DC refinement scan: add a refinement bit
@@ -598,7 +667,12 @@ export class JPEGDecoder {
598
667
  if (bit) {
599
668
  // Add the refinement bit at position Al
600
669
  const refinement = 1 << this.successiveLow;
601
- block[0] += refinement * this.qTables[component.qTable][0];
670
+ if (this.options.extractCoefficients) {
671
+ block[0] += refinement;
672
+ }
673
+ else {
674
+ block[0] += refinement * this.qTables[component.qTable][0];
675
+ }
602
676
  }
603
677
  }
604
678
  }
@@ -645,8 +719,14 @@ export class JPEGDecoder {
645
719
  break;
646
720
  // For successive approximation, shift the coefficient left by Al bits
647
721
  const coeff = this.receiveBits(s) << this.successiveLow;
648
- block[ZIGZAG[k]] = coeff *
649
- this.qTables[component.qTable][ZIGZAG[k]];
722
+ // When extracting coefficients, store quantized value without dequantization
723
+ if (this.options.extractCoefficients) {
724
+ block[ZIGZAG[k]] = coeff;
725
+ }
726
+ else {
727
+ block[ZIGZAG[k]] = coeff *
728
+ this.qTables[component.qTable][ZIGZAG[k]];
729
+ }
650
730
  k++;
651
731
  }
652
732
  }
@@ -693,7 +773,10 @@ export class JPEGDecoder {
693
773
  if (current !== 0) {
694
774
  const bit = this.readBit();
695
775
  if (bit) {
696
- const refinement = (1 << this.successiveLow) * qTable[z];
776
+ // When extracting coefficients, don't dequantize
777
+ const refinement = this.options.extractCoefficients
778
+ ? (1 << this.successiveLow)
779
+ : (1 << this.successiveLow) * qTable[z];
697
780
  block[z] += direction * refinement;
698
781
  }
699
782
  }
@@ -708,13 +791,17 @@ export class JPEGDecoder {
708
791
  if (current !== 0) {
709
792
  const bit = this.readBit();
710
793
  if (bit) {
711
- const refinement = (1 << this.successiveLow) * qTable[z];
794
+ // When extracting coefficients, don't dequantize
795
+ const refinement = this.options.extractCoefficients
796
+ ? (1 << this.successiveLow)
797
+ : (1 << this.successiveLow) * qTable[z];
712
798
  block[z] += direction * refinement;
713
799
  }
714
800
  }
715
801
  else {
716
802
  const newCoeff = successiveACNextValue << this.successiveLow;
717
- block[z] = newCoeff * qTable[z];
803
+ // When extracting coefficients, don't dequantize
804
+ block[z] = this.options.extractCoefficients ? newCoeff : newCoeff * qTable[z];
718
805
  successiveACState = 0;
719
806
  }
720
807
  break;
@@ -722,7 +809,10 @@ export class JPEGDecoder {
722
809
  if (current !== 0) {
723
810
  const bit = this.readBit();
724
811
  if (bit) {
725
- const refinement = (1 << this.successiveLow) * qTable[z];
812
+ // When extracting coefficients, don't dequantize
813
+ const refinement = this.options.extractCoefficients
814
+ ? (1 << this.successiveLow)
815
+ : (1 << this.successiveLow) * qTable[z];
726
816
  block[z] += direction * refinement;
727
817
  }
728
818
  }
@@ -741,7 +831,8 @@ export class JPEGDecoder {
741
831
  // Perform IDCT only for baseline JPEGs
742
832
  // For progressive JPEGs, IDCT is deferred until all scans are complete
743
833
  // to preserve frequency-domain coefficients for accumulation across scans
744
- if (!this.isProgressive) {
834
+ // Skip IDCT when extracting coefficients - we want the quantized DCT values
835
+ if (!this.isProgressive && !this.options.extractCoefficients) {
745
836
  this.idct(block);
746
837
  }
747
838
  }
@@ -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