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.
- package/README.md +77 -4
- package/esm/mod.d.ts +1 -1
- package/esm/src/formats/gif.d.ts +1 -0
- package/esm/src/formats/gif.js +19 -2
- 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/gif_encoder.d.ts +13 -4
- package/esm/src/utils/gif_encoder.js +100 -97
- 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/esm/src/utils/tiff_deflate.d.ts +0 -1
- package/esm/src/utils/tiff_deflate.js +0 -1
- package/package.json +1 -1
- package/script/mod.d.ts +1 -1
- package/script/src/formats/gif.d.ts +1 -0
- package/script/src/formats/gif.js +19 -2
- 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/gif_encoder.d.ts +13 -4
- package/script/src/utils/gif_encoder.js +100 -97
- 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
- package/script/src/utils/tiff_deflate.d.ts +0 -1
- package/script/src/utils/tiff_deflate.js +0 -1
|
@@ -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;
|