cross-image 0.4.1 → 0.4.3

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.
@@ -1253,4 +1253,271 @@ export class JPEGEncoder {
1253
1253
  bitWriter.writeBits(huffTable.codes[0x00], huffTable.sizes[0x00]);
1254
1254
  }
1255
1255
  }
1256
+ /**
1257
+ * Encode JPEG from pre-quantized DCT coefficients
1258
+ * Skips DCT and quantization - uses provided coefficients directly
1259
+ * Useful for steganography where coefficients are modified and re-encoded
1260
+ * @param coeffs JPEG quantized coefficients
1261
+ * @param _options Optional encoding options (currently unused)
1262
+ * @returns Encoded JPEG bytes
1263
+ */
1264
+ encodeFromCoefficients(coeffs, _options) {
1265
+ const output = [];
1266
+ const { width, height, components, quantizationTables, isProgressive } = coeffs;
1267
+ // SOI (Start of Image)
1268
+ output.push(0xff, 0xd8);
1269
+ // APP0 (JFIF marker) - use default 72 DPI
1270
+ this.writeAPP0(output, 72, 72);
1271
+ // DQT (Define Quantization Tables) - use original tables from coefficients
1272
+ this.writeDQTFromCoeffs(output, quantizationTables);
1273
+ // SOF (Start of Frame)
1274
+ if (isProgressive) {
1275
+ this.writeSOF2FromCoeffs(output, width, height, components);
1276
+ }
1277
+ else {
1278
+ this.writeSOF0FromCoeffs(output, width, height, components);
1279
+ }
1280
+ // DHT (Define Huffman Tables)
1281
+ this.writeDHT(output);
1282
+ if (isProgressive) {
1283
+ // Progressive encoding: encode from coefficients
1284
+ this.encodeProgressiveFromCoeffs(output, coeffs);
1285
+ }
1286
+ else {
1287
+ // Baseline encoding: single scan
1288
+ this.writeSOS(output);
1289
+ // Encode scan data from coefficients
1290
+ const scanData = this.encodeScanFromCoeffs(coeffs);
1291
+ for (let i = 0; i < scanData.length; i++) {
1292
+ output.push(scanData[i]);
1293
+ }
1294
+ }
1295
+ // EOI (End of Image)
1296
+ output.push(0xff, 0xd9);
1297
+ return new Uint8Array(output);
1298
+ }
1299
+ writeDQTFromCoeffs(output, quantizationTables) {
1300
+ // Write each quantization table
1301
+ for (let tableId = 0; tableId < quantizationTables.length; tableId++) {
1302
+ const table = quantizationTables[tableId];
1303
+ if (!table)
1304
+ continue;
1305
+ output.push(0xff, 0xdb); // DQT marker
1306
+ output.push(0x00, 0x43); // Length (67 bytes)
1307
+ output.push(tableId); // Table ID, 8-bit precision
1308
+ // Write table values in zigzag order
1309
+ for (let i = 0; i < 64; i++) {
1310
+ output.push(table[ZIGZAG[i]] ?? 1);
1311
+ }
1312
+ }
1313
+ }
1314
+ writeSOF0FromCoeffs(output, width, height, components) {
1315
+ const numComponents = components.length;
1316
+ const length = 8 + numComponents * 3;
1317
+ output.push(0xff, 0xc0); // SOF0 marker
1318
+ output.push((length >> 8) & 0xff, length & 0xff);
1319
+ output.push(0x08); // Precision (8 bits)
1320
+ output.push((height >> 8) & 0xff, height & 0xff);
1321
+ output.push((width >> 8) & 0xff, width & 0xff);
1322
+ output.push(numComponents);
1323
+ for (const comp of components) {
1324
+ output.push(comp.id);
1325
+ output.push((comp.h << 4) | comp.v);
1326
+ output.push(comp.qTable);
1327
+ }
1328
+ }
1329
+ writeSOF2FromCoeffs(output, width, height, components) {
1330
+ const numComponents = components.length;
1331
+ const length = 8 + numComponents * 3;
1332
+ output.push(0xff, 0xc2); // SOF2 marker (Progressive DCT)
1333
+ output.push((length >> 8) & 0xff, length & 0xff);
1334
+ output.push(0x08); // Precision (8 bits)
1335
+ output.push((height >> 8) & 0xff, height & 0xff);
1336
+ output.push((width >> 8) & 0xff, width & 0xff);
1337
+ output.push(numComponents);
1338
+ for (const comp of components) {
1339
+ output.push(comp.id);
1340
+ output.push((comp.h << 4) | comp.v);
1341
+ output.push(comp.qTable);
1342
+ }
1343
+ }
1344
+ encodeScanFromCoeffs(coeffs) {
1345
+ const bitWriter = new BitWriter();
1346
+ const { components } = coeffs;
1347
+ // DC predictors for each component
1348
+ const dcPreds = new Map();
1349
+ for (const comp of components) {
1350
+ dcPreds.set(comp.id, 0);
1351
+ }
1352
+ // Encode MCUs in raster order
1353
+ // For non-subsampled images (1:1:1), each MCU contains one block per component
1354
+ const mcuHeight = coeffs.mcuHeight;
1355
+ const mcuWidth = coeffs.mcuWidth;
1356
+ for (let mcuY = 0; mcuY < mcuHeight; mcuY++) {
1357
+ for (let mcuX = 0; mcuX < mcuWidth; mcuX++) {
1358
+ // Encode each component's blocks in this MCU
1359
+ for (const comp of components) {
1360
+ // Handle subsampling: component may have multiple blocks per MCU
1361
+ for (let v = 0; v < comp.v; v++) {
1362
+ for (let h = 0; h < comp.h; h++) {
1363
+ const blockY = mcuY * comp.v + v;
1364
+ const blockX = mcuX * comp.h + h;
1365
+ if (blockY < comp.blocks.length &&
1366
+ blockX < comp.blocks[0].length) {
1367
+ const block = comp.blocks[blockY][blockX];
1368
+ // Get the block in zigzag order for encoding
1369
+ const zigzagBlock = new Int32Array(64);
1370
+ for (let i = 0; i < 64; i++) {
1371
+ zigzagBlock[i] = block[ZIGZAG[i]];
1372
+ }
1373
+ // Encode DC coefficient
1374
+ const prevDC = dcPreds.get(comp.id) ?? 0;
1375
+ const dcHuffman = comp.id === 1
1376
+ ? this.dcLuminanceHuffman
1377
+ : this.dcChrominanceHuffman;
1378
+ const acHuffman = comp.id === 1
1379
+ ? this.acLuminanceHuffman
1380
+ : this.acChrominanceHuffman;
1381
+ // DC coefficient is at index 0 of the block (already in zigzag order from decoder)
1382
+ const dc = block[0];
1383
+ const dcDiff = dc - prevDC;
1384
+ this.encodeDC(dcDiff, dcHuffman, bitWriter);
1385
+ dcPreds.set(comp.id, dc);
1386
+ // Encode AC coefficients (indices 1-63 in zigzag order)
1387
+ this.encodeACFromCoeffs(block, acHuffman, bitWriter);
1388
+ }
1389
+ }
1390
+ }
1391
+ }
1392
+ }
1393
+ }
1394
+ bitWriter.flush();
1395
+ return Array.from(bitWriter.getBytes());
1396
+ }
1397
+ encodeACFromCoeffs(block, huffTable, bitWriter) {
1398
+ let zeroCount = 0;
1399
+ // Block is stored with DC at index 0, and AC at indices corresponding to zigzag positions
1400
+ // We need to iterate in zigzag order for AC coefficients (indices 1-63)
1401
+ for (let i = 1; i < 64; i++) {
1402
+ const coef = block[ZIGZAG[i]];
1403
+ if (coef === 0) {
1404
+ zeroCount++;
1405
+ }
1406
+ else {
1407
+ // Write any pending zero runs
1408
+ while (zeroCount >= 16) {
1409
+ bitWriter.writeBits(huffTable.codes[0xf0], huffTable.sizes[0xf0]);
1410
+ zeroCount -= 16;
1411
+ }
1412
+ // Clamp coefficient
1413
+ const maxAC = 1023;
1414
+ const clampedCoef = Math.max(-maxAC, Math.min(maxAC, coef));
1415
+ const absCoef = Math.abs(clampedCoef);
1416
+ const size = Math.floor(Math.log2(absCoef)) + 1;
1417
+ const symbol = (zeroCount << 4) | size;
1418
+ bitWriter.writeBits(huffTable.codes[symbol], huffTable.sizes[symbol]);
1419
+ const magnitude = clampedCoef < 0 ? clampedCoef + (1 << size) - 1 : clampedCoef;
1420
+ bitWriter.writeBits(magnitude, size);
1421
+ zeroCount = 0;
1422
+ }
1423
+ }
1424
+ // Write EOB if there are trailing zeros
1425
+ if (zeroCount > 0) {
1426
+ bitWriter.writeBits(huffTable.codes[0x00], huffTable.sizes[0x00]);
1427
+ }
1428
+ }
1429
+ encodeProgressiveFromCoeffs(output, coeffs) {
1430
+ const { components } = coeffs;
1431
+ // Pre-extract blocks for each component (already quantized)
1432
+ const componentBlocks = new Map();
1433
+ for (const comp of components) {
1434
+ const blocks = [];
1435
+ for (const row of comp.blocks) {
1436
+ for (const block of row) {
1437
+ blocks.push(block);
1438
+ }
1439
+ }
1440
+ componentBlocks.set(comp.id, blocks);
1441
+ }
1442
+ // Scan 1: DC-only (interleaved across all components)
1443
+ this.writeProgressiveSOS(output, components.map((c) => c.id), 0, 0, 0, 0);
1444
+ const dcScanData = this.encodeProgressiveDCScanFromCoeffs(componentBlocks, components);
1445
+ for (let i = 0; i < dcScanData.length; i++) {
1446
+ output.push(dcScanData[i]);
1447
+ }
1448
+ // Scan 2+: AC coefficients (one scan per component)
1449
+ for (const comp of components) {
1450
+ this.writeProgressiveSOS(output, [comp.id], 1, 63, 0, 0);
1451
+ const blocks = componentBlocks.get(comp.id) ?? [];
1452
+ const acHuffman = comp.id === 1 ? this.acLuminanceHuffman : this.acChrominanceHuffman;
1453
+ const acScanData = this.encodeProgressiveACScanFromCoeffs(blocks, acHuffman);
1454
+ for (let i = 0; i < acScanData.length; i++) {
1455
+ output.push(acScanData[i]);
1456
+ }
1457
+ }
1458
+ }
1459
+ encodeProgressiveDCScanFromCoeffs(componentBlocks, components) {
1460
+ const bitWriter = new BitWriter();
1461
+ const dcPreds = new Map();
1462
+ for (const comp of components) {
1463
+ dcPreds.set(comp.id, 0);
1464
+ }
1465
+ // Get number of blocks (should be same for all components in interleaved scan)
1466
+ const numBlocks = componentBlocks.get(components[0].id)?.length ?? 0;
1467
+ for (let i = 0; i < numBlocks; i++) {
1468
+ for (const comp of components) {
1469
+ const blocks = componentBlocks.get(comp.id);
1470
+ if (!blocks || i >= blocks.length)
1471
+ continue;
1472
+ const block = blocks[i];
1473
+ const dc = block[0]; // DC coefficient at index 0
1474
+ const prevDC = dcPreds.get(comp.id) ?? 0;
1475
+ const dcHuffman = comp.id === 1 ? this.dcLuminanceHuffman : this.dcChrominanceHuffman;
1476
+ this.encodeOnlyDC(dc, prevDC, dcHuffman, bitWriter);
1477
+ dcPreds.set(comp.id, dc);
1478
+ }
1479
+ }
1480
+ bitWriter.flush();
1481
+ return Array.from(bitWriter.getBytes());
1482
+ }
1483
+ encodeProgressiveACScanFromCoeffs(blocks, acHuffman) {
1484
+ const bitWriter = new BitWriter();
1485
+ for (const block of blocks) {
1486
+ this.encodeOnlyACFromCoeffs(block, acHuffman, bitWriter);
1487
+ }
1488
+ bitWriter.flush();
1489
+ return Array.from(bitWriter.getBytes());
1490
+ }
1491
+ encodeOnlyACFromCoeffs(block, acTable, bitWriter) {
1492
+ let zeroCount = 0;
1493
+ // Iterate AC coefficients in zigzag order (indices 1-63)
1494
+ for (let i = 1; i < 64; i++) {
1495
+ const coef = block[ZIGZAG[i]];
1496
+ const clampedCoef = Math.max(-1023, Math.min(1023, coef));
1497
+ if (clampedCoef === 0) {
1498
+ zeroCount++;
1499
+ if (zeroCount === 16) {
1500
+ bitWriter.writeBits(acTable.codes[0xf0], acTable.sizes[0xf0]);
1501
+ zeroCount = 0;
1502
+ }
1503
+ }
1504
+ else {
1505
+ while (zeroCount >= 16) {
1506
+ bitWriter.writeBits(acTable.codes[0xf0], acTable.sizes[0xf0]);
1507
+ zeroCount -= 16;
1508
+ }
1509
+ const absCoef = Math.abs(clampedCoef);
1510
+ const size = Math.floor(Math.log2(absCoef)) + 1;
1511
+ const symbol = (zeroCount << 4) | size;
1512
+ bitWriter.writeBits(acTable.codes[symbol], acTable.sizes[symbol]);
1513
+ const magnitude = clampedCoef < 0 ? clampedCoef + (1 << size) - 1 : clampedCoef;
1514
+ bitWriter.writeBits(magnitude, size);
1515
+ zeroCount = 0;
1516
+ }
1517
+ }
1518
+ // Write EOB if there are trailing zeros
1519
+ if (zeroCount > 0) {
1520
+ bitWriter.writeBits(acTable.codes[0x00], acTable.sizes[0x00]);
1521
+ }
1522
+ }
1256
1523
  }
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * Deflate compression/decompression for TIFF
3
- * Uses native JavaScript CompressionStream/DecompressionStream APIs
4
3
  * Compression code: 8 (Adobe-style Deflate)
5
4
  */
6
5
  /**
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * Deflate compression/decompression for TIFF
3
- * Uses native JavaScript CompressionStream/DecompressionStream APIs
4
3
  * Compression code: 8 (Adobe-style Deflate)
5
4
  */
6
5
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cross-image",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "A pure JavaScript, dependency-free, cross-runtime image processing library for Deno, Node.js, and Bun.",
5
5
  "keywords": [
6
6
  "image",
package/script/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";
@@ -48,6 +48,7 @@ export declare class GIFFormat implements ImageFormat {
48
48
  * Encode multi-frame image data to animated GIF
49
49
  */
50
50
  encodeFrames(imageData: MultiFrameImageData, options?: GIFEncoderOptions): Promise<Uint8Array>;
51
+ private mapDisposalMethodToNumber;
51
52
  private mapDisposalMethod;
52
53
  private decodeUsingRuntime;
53
54
  private readDataSubBlocks;
@@ -246,12 +246,29 @@ class GIFFormat {
246
246
  }
247
247
  const encoder = new gif_encoder_js_1.GIFEncoder(imageData.width, imageData.height);
248
248
  for (const frame of imageData.frames) {
249
- // Get delay from metadata (default to 100ms if not set)
250
249
  const delay = frame.frameMetadata?.delay ?? 100;
251
- encoder.addFrame(frame.data, delay);
250
+ encoder.addFrame(frame.data, delay, {
251
+ left: frame.frameMetadata?.left ?? 0,
252
+ top: frame.frameMetadata?.top ?? 0,
253
+ width: frame.width,
254
+ height: frame.height,
255
+ disposal: this.mapDisposalMethodToNumber(frame.frameMetadata?.disposal),
256
+ });
252
257
  }
253
258
  return Promise.resolve(encoder.encode(options));
254
259
  }
260
+ mapDisposalMethodToNumber(disposal) {
261
+ switch (disposal) {
262
+ case "none":
263
+ return 1;
264
+ case "background":
265
+ return 2;
266
+ case "previous":
267
+ return 3;
268
+ default:
269
+ return 0;
270
+ }
271
+ }
255
272
  mapDisposalMethod(disposal) {
256
273
  switch (disposal) {
257
274
  case 0:
@@ -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
@@ -1108,6 +1108,65 @@ class JPEGFormat {
1108
1108
  "dpiY",
1109
1109
  ];
1110
1110
  }
1111
+ /**
1112
+ * Extract quantized DCT coefficients from JPEG data
1113
+ * These coefficients can be modified for steganography and re-encoded
1114
+ * @param data Raw JPEG data
1115
+ * @param options Decoder options
1116
+ * @returns JPEGQuantizedCoefficients or undefined if extraction fails
1117
+ */
1118
+ async extractCoefficients(data, options) {
1119
+ if (!this.canDecode(data)) {
1120
+ return undefined;
1121
+ }
1122
+ try {
1123
+ // Force pure-JS decoding since runtime decoders don't expose coefficients
1124
+ const { JPEGDecoder } = await Promise.resolve().then(() => __importStar(require("../utils/jpeg_decoder.js")));
1125
+ const decoder = new JPEGDecoder(data, {
1126
+ tolerantDecoding: options?.tolerantDecoding ?? true,
1127
+ onWarning: options?.onWarning,
1128
+ extractCoefficients: true,
1129
+ });
1130
+ // Decode to extract coefficients
1131
+ decoder.decode();
1132
+ // Get the quantized coefficients
1133
+ return decoder.getQuantizedCoefficients();
1134
+ }
1135
+ catch (error) {
1136
+ if (options?.onWarning) {
1137
+ options.onWarning(`Failed to extract JPEG coefficients: ${error}`, error);
1138
+ }
1139
+ return undefined;
1140
+ }
1141
+ }
1142
+ /**
1143
+ * Type guard to check if coefficient data is JPEG format
1144
+ */
1145
+ isJPEGCoefficients(coeffs) {
1146
+ return ("format" in coeffs &&
1147
+ coeffs.format === "jpeg" &&
1148
+ "components" in coeffs &&
1149
+ "quantizationTables" in coeffs &&
1150
+ "isProgressive" in coeffs);
1151
+ }
1152
+ /**
1153
+ * Encode JPEG from quantized DCT coefficients
1154
+ * Useful for steganography - modify coefficients and re-encode
1155
+ * @param coeffs JPEG quantized coefficients
1156
+ * @param options Encoding options
1157
+ * @returns Encoded JPEG bytes
1158
+ */
1159
+ async encodeFromCoefficients(coeffs, options) {
1160
+ if (!this.isJPEGCoefficients(coeffs)) {
1161
+ throw new Error("Invalid coefficient format for JPEG");
1162
+ }
1163
+ const { JPEGEncoder } = await Promise.resolve().then(() => __importStar(require("../utils/jpeg_encoder.js")));
1164
+ const encoder = new JPEGEncoder({
1165
+ quality: options?.quality,
1166
+ progressive: options?.progressive ?? coeffs.isProgressive,
1167
+ });
1168
+ return encoder.encodeFromCoefficients(coeffs, options);
1169
+ }
1111
1170
  /**
1112
1171
  * Extract metadata from JPEG data without fully decoding the pixel data
1113
1172
  * 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.
@@ -233,6 +233,74 @@ class Image {
233
233
  }
234
234
  return undefined;
235
235
  }
236
+ /**
237
+ * Extract coefficients from encoded image data
238
+ * For JPEG, this returns quantized DCT coefficients that can be modified for steganography
239
+ * and re-encoded using encodeFromCoefficients()
240
+ * @param data Raw image data
241
+ * @param format Optional format hint (e.g., "jpeg")
242
+ * @param options Optional decoder options
243
+ * @returns Format-specific coefficient structure or undefined if not supported
244
+ *
245
+ * @example
246
+ * ```ts
247
+ * // Extract JPEG coefficients for steganography
248
+ * const coeffs = await Image.extractCoefficients(jpegData, "jpeg");
249
+ * if (coeffs) {
250
+ * // Modify coefficients for steganography...
251
+ * const modified = await Image.encodeFromCoefficients(coeffs, "jpeg");
252
+ * }
253
+ * ```
254
+ */
255
+ static async extractCoefficients(data, format, options) {
256
+ // Try specified format first
257
+ if (format) {
258
+ const handler = Image.formats.find((f) => f.name === format);
259
+ if (handler && handler.canDecode(data) && handler.extractCoefficients) {
260
+ return await handler.extractCoefficients(data, options);
261
+ }
262
+ }
263
+ // Auto-detect format
264
+ for (const handler of Image.formats) {
265
+ if (handler.canDecode(data) && handler.extractCoefficients) {
266
+ return await handler.extractCoefficients(data, options);
267
+ }
268
+ }
269
+ return undefined;
270
+ }
271
+ /**
272
+ * Encode image from coefficients
273
+ * For JPEG, accepts quantized DCT coefficients and produces a valid JPEG file
274
+ * Useful for steganography where coefficients are extracted, modified, and re-encoded
275
+ * @param coeffs Format-specific coefficient structure
276
+ * @param format Optional format hint (auto-detected from coeffs.format if available)
277
+ * @param options Optional format-specific encoding options
278
+ * @returns Encoded image bytes
279
+ *
280
+ * @example
281
+ * ```ts
282
+ * // Re-encode modified JPEG coefficients
283
+ * const coeffs = await Image.extractCoefficients(jpegData, "jpeg");
284
+ * // Modify coefficients...
285
+ * const encoded = await Image.encodeFromCoefficients(coeffs, "jpeg");
286
+ * ```
287
+ */
288
+ static async encodeFromCoefficients(coeffs, format, options) {
289
+ // Detect format from coefficient structure or use provided format
290
+ const detectedFormat = format ??
291
+ coeffs.format;
292
+ if (!detectedFormat) {
293
+ throw new Error("Format must be specified or present in coefficient data");
294
+ }
295
+ const handler = Image.formats.find((f) => f.name === detectedFormat);
296
+ if (!handler) {
297
+ throw new Error(`Unknown format: ${detectedFormat}`);
298
+ }
299
+ if (!handler.encodeFromCoefficients) {
300
+ throw new Error(`Format ${detectedFormat} does not support encoding from coefficients`);
301
+ }
302
+ return await handler.encodeFromCoefficients(coeffs, options);
303
+ }
236
304
  /**
237
305
  * Read an image from bytes
238
306
  * @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