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 +77 -4
- package/esm/mod.d.ts +1 -1
- package/esm/src/formats/jpeg.d.ts +21 -1
- package/esm/src/formats/jpeg.js +59 -0
- package/esm/src/image.d.ts +39 -1
- package/esm/src/image.js +68 -0
- package/esm/src/types.d.ts +63 -0
- package/esm/src/utils/jpeg_decoder.d.ts +25 -2
- package/esm/src/utils/jpeg_decoder.js +101 -10
- package/esm/src/utils/jpeg_encoder.d.ts +19 -0
- package/esm/src/utils/jpeg_encoder.js +267 -0
- package/package.json +1 -1
- package/script/mod.d.ts +1 -1
- package/script/src/formats/jpeg.d.ts +21 -1
- package/script/src/formats/jpeg.js +59 -0
- package/script/src/image.d.ts +39 -1
- package/script/src/image.js +68 -0
- package/script/src/types.d.ts +63 -0
- package/script/src/utils/jpeg_decoder.d.ts +25 -2
- package/script/src/utils/jpeg_decoder.js +101 -10
- package/script/src/utils/jpeg_encoder.d.ts +19 -0
- package/script/src/utils/jpeg_encoder.js +267 -0
|
@@ -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
|
}
|
package/package.json
CHANGED
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";
|
|
@@ -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
|
package/script/src/image.d.ts
CHANGED
|
@@ -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/script/src/image.js
CHANGED
|
@@ -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.
|
package/script/src/types.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|