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.
@@ -1256,5 +1256,272 @@ class JPEGEncoder {
1256
1256
  bitWriter.writeBits(huffTable.codes[0x00], huffTable.sizes[0x00]);
1257
1257
  }
1258
1258
  }
1259
+ /**
1260
+ * Encode JPEG from pre-quantized DCT coefficients
1261
+ * Skips DCT and quantization - uses provided coefficients directly
1262
+ * Useful for steganography where coefficients are modified and re-encoded
1263
+ * @param coeffs JPEG quantized coefficients
1264
+ * @param _options Optional encoding options (currently unused)
1265
+ * @returns Encoded JPEG bytes
1266
+ */
1267
+ encodeFromCoefficients(coeffs, _options) {
1268
+ const output = [];
1269
+ const { width, height, components, quantizationTables, isProgressive } = coeffs;
1270
+ // SOI (Start of Image)
1271
+ output.push(0xff, 0xd8);
1272
+ // APP0 (JFIF marker) - use default 72 DPI
1273
+ this.writeAPP0(output, 72, 72);
1274
+ // DQT (Define Quantization Tables) - use original tables from coefficients
1275
+ this.writeDQTFromCoeffs(output, quantizationTables);
1276
+ // SOF (Start of Frame)
1277
+ if (isProgressive) {
1278
+ this.writeSOF2FromCoeffs(output, width, height, components);
1279
+ }
1280
+ else {
1281
+ this.writeSOF0FromCoeffs(output, width, height, components);
1282
+ }
1283
+ // DHT (Define Huffman Tables)
1284
+ this.writeDHT(output);
1285
+ if (isProgressive) {
1286
+ // Progressive encoding: encode from coefficients
1287
+ this.encodeProgressiveFromCoeffs(output, coeffs);
1288
+ }
1289
+ else {
1290
+ // Baseline encoding: single scan
1291
+ this.writeSOS(output);
1292
+ // Encode scan data from coefficients
1293
+ const scanData = this.encodeScanFromCoeffs(coeffs);
1294
+ for (let i = 0; i < scanData.length; i++) {
1295
+ output.push(scanData[i]);
1296
+ }
1297
+ }
1298
+ // EOI (End of Image)
1299
+ output.push(0xff, 0xd9);
1300
+ return new Uint8Array(output);
1301
+ }
1302
+ writeDQTFromCoeffs(output, quantizationTables) {
1303
+ // Write each quantization table
1304
+ for (let tableId = 0; tableId < quantizationTables.length; tableId++) {
1305
+ const table = quantizationTables[tableId];
1306
+ if (!table)
1307
+ continue;
1308
+ output.push(0xff, 0xdb); // DQT marker
1309
+ output.push(0x00, 0x43); // Length (67 bytes)
1310
+ output.push(tableId); // Table ID, 8-bit precision
1311
+ // Write table values in zigzag order
1312
+ for (let i = 0; i < 64; i++) {
1313
+ output.push(table[ZIGZAG[i]] ?? 1);
1314
+ }
1315
+ }
1316
+ }
1317
+ writeSOF0FromCoeffs(output, width, height, components) {
1318
+ const numComponents = components.length;
1319
+ const length = 8 + numComponents * 3;
1320
+ output.push(0xff, 0xc0); // SOF0 marker
1321
+ output.push((length >> 8) & 0xff, length & 0xff);
1322
+ output.push(0x08); // Precision (8 bits)
1323
+ output.push((height >> 8) & 0xff, height & 0xff);
1324
+ output.push((width >> 8) & 0xff, width & 0xff);
1325
+ output.push(numComponents);
1326
+ for (const comp of components) {
1327
+ output.push(comp.id);
1328
+ output.push((comp.h << 4) | comp.v);
1329
+ output.push(comp.qTable);
1330
+ }
1331
+ }
1332
+ writeSOF2FromCoeffs(output, width, height, components) {
1333
+ const numComponents = components.length;
1334
+ const length = 8 + numComponents * 3;
1335
+ output.push(0xff, 0xc2); // SOF2 marker (Progressive DCT)
1336
+ output.push((length >> 8) & 0xff, length & 0xff);
1337
+ output.push(0x08); // Precision (8 bits)
1338
+ output.push((height >> 8) & 0xff, height & 0xff);
1339
+ output.push((width >> 8) & 0xff, width & 0xff);
1340
+ output.push(numComponents);
1341
+ for (const comp of components) {
1342
+ output.push(comp.id);
1343
+ output.push((comp.h << 4) | comp.v);
1344
+ output.push(comp.qTable);
1345
+ }
1346
+ }
1347
+ encodeScanFromCoeffs(coeffs) {
1348
+ const bitWriter = new BitWriter();
1349
+ const { components } = coeffs;
1350
+ // DC predictors for each component
1351
+ const dcPreds = new Map();
1352
+ for (const comp of components) {
1353
+ dcPreds.set(comp.id, 0);
1354
+ }
1355
+ // Encode MCUs in raster order
1356
+ // For non-subsampled images (1:1:1), each MCU contains one block per component
1357
+ const mcuHeight = coeffs.mcuHeight;
1358
+ const mcuWidth = coeffs.mcuWidth;
1359
+ for (let mcuY = 0; mcuY < mcuHeight; mcuY++) {
1360
+ for (let mcuX = 0; mcuX < mcuWidth; mcuX++) {
1361
+ // Encode each component's blocks in this MCU
1362
+ for (const comp of components) {
1363
+ // Handle subsampling: component may have multiple blocks per MCU
1364
+ for (let v = 0; v < comp.v; v++) {
1365
+ for (let h = 0; h < comp.h; h++) {
1366
+ const blockY = mcuY * comp.v + v;
1367
+ const blockX = mcuX * comp.h + h;
1368
+ if (blockY < comp.blocks.length &&
1369
+ blockX < comp.blocks[0].length) {
1370
+ const block = comp.blocks[blockY][blockX];
1371
+ // Get the block in zigzag order for encoding
1372
+ const zigzagBlock = new Int32Array(64);
1373
+ for (let i = 0; i < 64; i++) {
1374
+ zigzagBlock[i] = block[ZIGZAG[i]];
1375
+ }
1376
+ // Encode DC coefficient
1377
+ const prevDC = dcPreds.get(comp.id) ?? 0;
1378
+ const dcHuffman = comp.id === 1
1379
+ ? this.dcLuminanceHuffman
1380
+ : this.dcChrominanceHuffman;
1381
+ const acHuffman = comp.id === 1
1382
+ ? this.acLuminanceHuffman
1383
+ : this.acChrominanceHuffman;
1384
+ // DC coefficient is at index 0 of the block (already in zigzag order from decoder)
1385
+ const dc = block[0];
1386
+ const dcDiff = dc - prevDC;
1387
+ this.encodeDC(dcDiff, dcHuffman, bitWriter);
1388
+ dcPreds.set(comp.id, dc);
1389
+ // Encode AC coefficients (indices 1-63 in zigzag order)
1390
+ this.encodeACFromCoeffs(block, acHuffman, bitWriter);
1391
+ }
1392
+ }
1393
+ }
1394
+ }
1395
+ }
1396
+ }
1397
+ bitWriter.flush();
1398
+ return Array.from(bitWriter.getBytes());
1399
+ }
1400
+ encodeACFromCoeffs(block, huffTable, bitWriter) {
1401
+ let zeroCount = 0;
1402
+ // Block is stored with DC at index 0, and AC at indices corresponding to zigzag positions
1403
+ // We need to iterate in zigzag order for AC coefficients (indices 1-63)
1404
+ for (let i = 1; i < 64; i++) {
1405
+ const coef = block[ZIGZAG[i]];
1406
+ if (coef === 0) {
1407
+ zeroCount++;
1408
+ }
1409
+ else {
1410
+ // Write any pending zero runs
1411
+ while (zeroCount >= 16) {
1412
+ bitWriter.writeBits(huffTable.codes[0xf0], huffTable.sizes[0xf0]);
1413
+ zeroCount -= 16;
1414
+ }
1415
+ // Clamp coefficient
1416
+ const maxAC = 1023;
1417
+ const clampedCoef = Math.max(-maxAC, Math.min(maxAC, coef));
1418
+ const absCoef = Math.abs(clampedCoef);
1419
+ const size = Math.floor(Math.log2(absCoef)) + 1;
1420
+ const symbol = (zeroCount << 4) | size;
1421
+ bitWriter.writeBits(huffTable.codes[symbol], huffTable.sizes[symbol]);
1422
+ const magnitude = clampedCoef < 0 ? clampedCoef + (1 << size) - 1 : clampedCoef;
1423
+ bitWriter.writeBits(magnitude, size);
1424
+ zeroCount = 0;
1425
+ }
1426
+ }
1427
+ // Write EOB if there are trailing zeros
1428
+ if (zeroCount > 0) {
1429
+ bitWriter.writeBits(huffTable.codes[0x00], huffTable.sizes[0x00]);
1430
+ }
1431
+ }
1432
+ encodeProgressiveFromCoeffs(output, coeffs) {
1433
+ const { components } = coeffs;
1434
+ // Pre-extract blocks for each component (already quantized)
1435
+ const componentBlocks = new Map();
1436
+ for (const comp of components) {
1437
+ const blocks = [];
1438
+ for (const row of comp.blocks) {
1439
+ for (const block of row) {
1440
+ blocks.push(block);
1441
+ }
1442
+ }
1443
+ componentBlocks.set(comp.id, blocks);
1444
+ }
1445
+ // Scan 1: DC-only (interleaved across all components)
1446
+ this.writeProgressiveSOS(output, components.map((c) => c.id), 0, 0, 0, 0);
1447
+ const dcScanData = this.encodeProgressiveDCScanFromCoeffs(componentBlocks, components);
1448
+ for (let i = 0; i < dcScanData.length; i++) {
1449
+ output.push(dcScanData[i]);
1450
+ }
1451
+ // Scan 2+: AC coefficients (one scan per component)
1452
+ for (const comp of components) {
1453
+ this.writeProgressiveSOS(output, [comp.id], 1, 63, 0, 0);
1454
+ const blocks = componentBlocks.get(comp.id) ?? [];
1455
+ const acHuffman = comp.id === 1 ? this.acLuminanceHuffman : this.acChrominanceHuffman;
1456
+ const acScanData = this.encodeProgressiveACScanFromCoeffs(blocks, acHuffman);
1457
+ for (let i = 0; i < acScanData.length; i++) {
1458
+ output.push(acScanData[i]);
1459
+ }
1460
+ }
1461
+ }
1462
+ encodeProgressiveDCScanFromCoeffs(componentBlocks, components) {
1463
+ const bitWriter = new BitWriter();
1464
+ const dcPreds = new Map();
1465
+ for (const comp of components) {
1466
+ dcPreds.set(comp.id, 0);
1467
+ }
1468
+ // Get number of blocks (should be same for all components in interleaved scan)
1469
+ const numBlocks = componentBlocks.get(components[0].id)?.length ?? 0;
1470
+ for (let i = 0; i < numBlocks; i++) {
1471
+ for (const comp of components) {
1472
+ const blocks = componentBlocks.get(comp.id);
1473
+ if (!blocks || i >= blocks.length)
1474
+ continue;
1475
+ const block = blocks[i];
1476
+ const dc = block[0]; // DC coefficient at index 0
1477
+ const prevDC = dcPreds.get(comp.id) ?? 0;
1478
+ const dcHuffman = comp.id === 1 ? this.dcLuminanceHuffman : this.dcChrominanceHuffman;
1479
+ this.encodeOnlyDC(dc, prevDC, dcHuffman, bitWriter);
1480
+ dcPreds.set(comp.id, dc);
1481
+ }
1482
+ }
1483
+ bitWriter.flush();
1484
+ return Array.from(bitWriter.getBytes());
1485
+ }
1486
+ encodeProgressiveACScanFromCoeffs(blocks, acHuffman) {
1487
+ const bitWriter = new BitWriter();
1488
+ for (const block of blocks) {
1489
+ this.encodeOnlyACFromCoeffs(block, acHuffman, bitWriter);
1490
+ }
1491
+ bitWriter.flush();
1492
+ return Array.from(bitWriter.getBytes());
1493
+ }
1494
+ encodeOnlyACFromCoeffs(block, acTable, bitWriter) {
1495
+ let zeroCount = 0;
1496
+ // Iterate AC coefficients in zigzag order (indices 1-63)
1497
+ for (let i = 1; i < 64; i++) {
1498
+ const coef = block[ZIGZAG[i]];
1499
+ const clampedCoef = Math.max(-1023, Math.min(1023, coef));
1500
+ if (clampedCoef === 0) {
1501
+ zeroCount++;
1502
+ if (zeroCount === 16) {
1503
+ bitWriter.writeBits(acTable.codes[0xf0], acTable.sizes[0xf0]);
1504
+ zeroCount = 0;
1505
+ }
1506
+ }
1507
+ else {
1508
+ while (zeroCount >= 16) {
1509
+ bitWriter.writeBits(acTable.codes[0xf0], acTable.sizes[0xf0]);
1510
+ zeroCount -= 16;
1511
+ }
1512
+ const absCoef = Math.abs(clampedCoef);
1513
+ const size = Math.floor(Math.log2(absCoef)) + 1;
1514
+ const symbol = (zeroCount << 4) | size;
1515
+ bitWriter.writeBits(acTable.codes[symbol], acTable.sizes[symbol]);
1516
+ const magnitude = clampedCoef < 0 ? clampedCoef + (1 << size) - 1 : clampedCoef;
1517
+ bitWriter.writeBits(magnitude, size);
1518
+ zeroCount = 0;
1519
+ }
1520
+ }
1521
+ // Write EOB if there are trailing zeros
1522
+ if (zeroCount > 0) {
1523
+ bitWriter.writeBits(acTable.codes[0x00], acTable.sizes[0x00]);
1524
+ }
1525
+ }
1259
1526
  }
1260
1527
  exports.JPEGEncoder = JPEGEncoder;
@@ -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,7 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Deflate compression/decompression for TIFF
4
- * Uses native JavaScript CompressionStream/DecompressionStream APIs
5
4
  * Compression code: 8 (Adobe-style Deflate)
6
5
  */
7
6
  Object.defineProperty(exports, "__esModule", { value: true });