roxify 1.2.5 → 1.2.7
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/dist/cli.js +45 -18
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/pack.d.ts +4 -0
- package/dist/pack.js +5 -1
- package/dist/utils/crc.js +1 -1
- package/dist/utils/decoder.js +113 -74
- package/dist/utils/encoder.d.ts +1 -1
- package/dist/utils/encoder.js +219 -102
- package/dist/utils/inspection.d.ts +6 -1
- package/dist/utils/inspection.js +152 -22
- package/dist/utils/optimization.js +4 -0
- package/dist/utils/types.d.ts +4 -1
- package/dist/utils/zstd.d.ts +1 -1
- package/dist/utils/zstd.js +26 -13
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { mkdirSync, readFileSync, statSync, writeFileSync } from 'fs';
|
|
|
4
4
|
import { basename, dirname, join, resolve } from 'path';
|
|
5
5
|
import { DataFormatError, decodePngToBinary, encodeBinaryToPng, hasPassphraseInPng, IncorrectPassphraseError, listFilesInPng, PassphraseRequiredError, } from './index.js';
|
|
6
6
|
import { packPathsGenerator, unpackBuffer } from './pack.js';
|
|
7
|
-
const VERSION = '1.2.
|
|
7
|
+
const VERSION = '1.2.6';
|
|
8
8
|
function showHelp() {
|
|
9
9
|
console.log(`
|
|
10
10
|
ROX CLI — Encode/decode binary in PNG
|
|
@@ -24,6 +24,8 @@ Options:
|
|
|
24
24
|
-e, --encrypt <type> auto|aes|xor|none
|
|
25
25
|
--no-compress Disable compression
|
|
26
26
|
-o, --output <path> Output file path
|
|
27
|
+
-s, --sizes Show file sizes in 'list' output (default)
|
|
28
|
+
--no-sizes Disable file size reporting in 'list'
|
|
27
29
|
--files <list> Extract only specified files (comma-separated)
|
|
28
30
|
--view-reconst Export the reconstituted PNG for debugging
|
|
29
31
|
--debug Export debug images (doubled.png, reconstructed.png)
|
|
@@ -51,6 +53,14 @@ function parseArgs(args) {
|
|
|
51
53
|
parsed.viewReconst = true;
|
|
52
54
|
i++;
|
|
53
55
|
}
|
|
56
|
+
else if (key === 'sizes') {
|
|
57
|
+
parsed.sizes = true;
|
|
58
|
+
i++;
|
|
59
|
+
}
|
|
60
|
+
else if (key === 'no-sizes') {
|
|
61
|
+
parsed.sizes = false;
|
|
62
|
+
i++;
|
|
63
|
+
}
|
|
54
64
|
else if (key === 'debug') {
|
|
55
65
|
parsed.debug = true;
|
|
56
66
|
i++;
|
|
@@ -92,6 +102,11 @@ function parseArgs(args) {
|
|
|
92
102
|
parsed.verbose = true;
|
|
93
103
|
i += 1;
|
|
94
104
|
break;
|
|
105
|
+
case 's':
|
|
106
|
+
parsed.sizes = true;
|
|
107
|
+
i += 1;
|
|
108
|
+
break;
|
|
109
|
+
break;
|
|
95
110
|
case 'd':
|
|
96
111
|
parsed.debugDir = value;
|
|
97
112
|
i += 2;
|
|
@@ -202,7 +217,10 @@ async function encodeCommand(args) {
|
|
|
202
217
|
inputSizeVal = totalSize;
|
|
203
218
|
displayName = parsed.outputName || 'archive';
|
|
204
219
|
options.includeFileList = true;
|
|
205
|
-
options.fileList = index.map((e) =>
|
|
220
|
+
options.fileList = index.map((e) => ({
|
|
221
|
+
name: e.path,
|
|
222
|
+
size: e.size,
|
|
223
|
+
}));
|
|
206
224
|
}
|
|
207
225
|
else {
|
|
208
226
|
const resolvedInput = resolvedInputs[0];
|
|
@@ -214,14 +232,17 @@ async function encodeCommand(args) {
|
|
|
214
232
|
inputSizeVal = totalSize;
|
|
215
233
|
displayName = parsed.outputName || basename(resolvedInput);
|
|
216
234
|
options.includeFileList = true;
|
|
217
|
-
options.fileList = index.map((e) =>
|
|
235
|
+
options.fileList = index.map((e) => ({
|
|
236
|
+
name: e.path,
|
|
237
|
+
size: e.size,
|
|
238
|
+
}));
|
|
218
239
|
}
|
|
219
240
|
else {
|
|
220
241
|
inputData = readFileSync(resolvedInput);
|
|
221
242
|
inputSizeVal = inputData.length;
|
|
222
243
|
displayName = basename(resolvedInput);
|
|
223
244
|
options.includeFileList = true;
|
|
224
|
-
options.fileList = [basename(resolvedInput)];
|
|
245
|
+
options.fileList = [{ name: basename(resolvedInput), size: st.size }];
|
|
225
246
|
}
|
|
226
247
|
}
|
|
227
248
|
options.name = displayName;
|
|
@@ -283,7 +304,7 @@ async function encodeCommand(args) {
|
|
|
283
304
|
for await (const chunk of inputData) {
|
|
284
305
|
chunks.push(chunk);
|
|
285
306
|
}
|
|
286
|
-
inputBuffer =
|
|
307
|
+
inputBuffer = chunks;
|
|
287
308
|
}
|
|
288
309
|
else {
|
|
289
310
|
inputBuffer = inputData;
|
|
@@ -404,7 +425,8 @@ async function decodeCommand(args) {
|
|
|
404
425
|
const baseDir = parsed.output || outputPath || '.';
|
|
405
426
|
const totalBytes = result.files.reduce((s, f) => s + f.buf.length, 0);
|
|
406
427
|
const extractBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
|
|
407
|
-
|
|
428
|
+
const extractStart = Date.now();
|
|
429
|
+
extractBar.start(totalBytes, 0, { step: 'Writing files', elapsed: '0' });
|
|
408
430
|
let written = 0;
|
|
409
431
|
for (const file of result.files) {
|
|
410
432
|
const fullPath = join(baseDir, file.path);
|
|
@@ -412,9 +434,15 @@ async function decodeCommand(args) {
|
|
|
412
434
|
mkdirSync(dir, { recursive: true });
|
|
413
435
|
writeFileSync(fullPath, file.buf);
|
|
414
436
|
written += file.buf.length;
|
|
415
|
-
extractBar.update(written, {
|
|
437
|
+
extractBar.update(written, {
|
|
438
|
+
step: `Writing ${file.path}`,
|
|
439
|
+
elapsed: String(Math.floor((Date.now() - extractStart) / 1000)),
|
|
440
|
+
});
|
|
416
441
|
}
|
|
417
|
-
extractBar.update(totalBytes, {
|
|
442
|
+
extractBar.update(totalBytes, {
|
|
443
|
+
step: 'Done',
|
|
444
|
+
elapsed: String(Math.floor((Date.now() - extractStart) / 1000)),
|
|
445
|
+
});
|
|
418
446
|
extractBar.stop();
|
|
419
447
|
console.log(`\nSuccess!`);
|
|
420
448
|
console.log(`Unpacked ${result.files.length} files to directory : ${resolve(baseDir)}`);
|
|
@@ -424,20 +452,12 @@ async function decodeCommand(args) {
|
|
|
424
452
|
const unpacked = unpackBuffer(result.buf);
|
|
425
453
|
if (unpacked) {
|
|
426
454
|
const baseDir = parsed.output || outputPath || '.';
|
|
427
|
-
const totalBytes = unpacked.files.reduce((s, f) => s + f.buf.length, 0);
|
|
428
|
-
const extractBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
|
|
429
|
-
extractBar.start(totalBytes, 0, { step: 'Writing files' });
|
|
430
|
-
let written = 0;
|
|
431
455
|
for (const file of unpacked.files) {
|
|
432
456
|
const fullPath = join(baseDir, file.path);
|
|
433
457
|
const dir = dirname(fullPath);
|
|
434
458
|
mkdirSync(dir, { recursive: true });
|
|
435
459
|
writeFileSync(fullPath, file.buf);
|
|
436
|
-
written += file.buf.length;
|
|
437
|
-
extractBar.update(written, { step: `Writing ${file.path}` });
|
|
438
460
|
}
|
|
439
|
-
extractBar.update(totalBytes, { step: 'Done' });
|
|
440
|
-
extractBar.stop();
|
|
441
461
|
console.log(`\nSuccess!`);
|
|
442
462
|
console.log(`Time: ${decodeTime}ms`);
|
|
443
463
|
console.log(`Unpacked ${unpacked.files.length} files to current directory`);
|
|
@@ -506,11 +526,18 @@ async function listCommand(args) {
|
|
|
506
526
|
const resolvedInput = resolve(inputPath);
|
|
507
527
|
try {
|
|
508
528
|
const inputBuffer = readFileSync(resolvedInput);
|
|
509
|
-
const fileList = await listFilesInPng(inputBuffer
|
|
529
|
+
const fileList = await listFilesInPng(inputBuffer, {
|
|
530
|
+
includeSizes: parsed.sizes !== false,
|
|
531
|
+
});
|
|
510
532
|
if (fileList) {
|
|
511
533
|
console.log(`Files in ${resolvedInput}:`);
|
|
512
534
|
for (const file of fileList) {
|
|
513
|
-
|
|
535
|
+
if (typeof file === 'string') {
|
|
536
|
+
console.log(` ${file}`);
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
console.log(` ${file.name} (${file.size} bytes)`);
|
|
540
|
+
}
|
|
514
541
|
}
|
|
515
542
|
}
|
|
516
543
|
else {
|
package/dist/index.d.ts
CHANGED
|
@@ -10,4 +10,4 @@ export * from './utils/reconstitution.js';
|
|
|
10
10
|
export * from './utils/types.js';
|
|
11
11
|
export * from './utils/zstd.js';
|
|
12
12
|
export { decodeMinPng, encodeMinPng } from './minpng.js';
|
|
13
|
-
export { packPaths, unpackBuffer } from './pack.js';
|
|
13
|
+
export { packPaths, packPathsToParts, unpackBuffer } from './pack.js';
|
package/dist/index.js
CHANGED
|
@@ -10,4 +10,4 @@ export * from './utils/reconstitution.js';
|
|
|
10
10
|
export * from './utils/types.js';
|
|
11
11
|
export * from './utils/zstd.js';
|
|
12
12
|
export { decodeMinPng, encodeMinPng } from './minpng.js';
|
|
13
|
-
export { packPaths, unpackBuffer } from './pack.js';
|
|
13
|
+
export { packPaths, packPathsToParts, unpackBuffer } from './pack.js';
|
package/dist/pack.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ export interface VFSIndexEntry {
|
|
|
10
10
|
offset: number;
|
|
11
11
|
size: number;
|
|
12
12
|
}
|
|
13
|
+
export declare function packPathsToParts(paths: string[], baseDir?: string, onProgress?: (readBytes: number, totalBytes: number, currentFile?: string) => void): {
|
|
14
|
+
parts: Buffer[];
|
|
15
|
+
list: string[];
|
|
16
|
+
};
|
|
13
17
|
export declare function packPaths(paths: string[], baseDir?: string, onProgress?: (readBytes: number, totalBytes: number, currentFile?: string) => void): {
|
|
14
18
|
buf: Buffer;
|
|
15
19
|
list: string[];
|
package/dist/pack.js
CHANGED
|
@@ -14,7 +14,7 @@ function* collectFilesGenerator(paths) {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
export function
|
|
17
|
+
export function packPathsToParts(paths, baseDir, onProgress) {
|
|
18
18
|
const files = [];
|
|
19
19
|
for (const f of collectFilesGenerator(paths)) {
|
|
20
20
|
files.push(f);
|
|
@@ -48,6 +48,10 @@ export function packPaths(paths, baseDir, onProgress) {
|
|
|
48
48
|
header.writeUInt32BE(0x524f5850, 0);
|
|
49
49
|
header.writeUInt32BE(files.length, 4);
|
|
50
50
|
parts.unshift(header);
|
|
51
|
+
return { parts, list };
|
|
52
|
+
}
|
|
53
|
+
export function packPaths(paths, baseDir, onProgress) {
|
|
54
|
+
const { parts, list } = packPathsToParts(paths, baseDir, onProgress);
|
|
51
55
|
return { buf: Buffer.concat(parts), list };
|
|
52
56
|
}
|
|
53
57
|
export function unpackBuffer(buf, fileList) {
|
package/dist/utils/crc.js
CHANGED
|
@@ -16,7 +16,7 @@ export function crc32(buf, previous = 0) {
|
|
|
16
16
|
for (let i = 0; i < buf.length; i++) {
|
|
17
17
|
crc = CRC_TABLE[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
|
|
18
18
|
}
|
|
19
|
-
return crc ^ 0xffffffff;
|
|
19
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
20
20
|
}
|
|
21
21
|
export function adler32(buf, prev = 1) {
|
|
22
22
|
let s1 = prev & 0xffff;
|
package/dist/utils/decoder.js
CHANGED
|
@@ -92,7 +92,7 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
92
92
|
try {
|
|
93
93
|
const info = await sharp(pngBuf).metadata();
|
|
94
94
|
if (info.width && info.height) {
|
|
95
|
-
const MAX_RAW_BYTES =
|
|
95
|
+
const MAX_RAW_BYTES = 1200 * 1024 * 1024;
|
|
96
96
|
const rawBytesEstimate = info.width * info.height * 4;
|
|
97
97
|
if (rawBytesEstimate > MAX_RAW_BYTES) {
|
|
98
98
|
throw new DataFormatError(`Image too large to decode in-process (${Math.round(rawBytesEstimate / 1024 / 1024)} MB). Increase Node heap or use a smaller image/compact mode.`);
|
|
@@ -225,17 +225,44 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
225
225
|
return { buf: payload, meta: { name } };
|
|
226
226
|
}
|
|
227
227
|
try {
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
228
|
+
const metadata = await sharp(processedBuf).metadata();
|
|
229
|
+
const currentWidth = metadata.width;
|
|
230
|
+
const currentHeight = metadata.height;
|
|
231
|
+
const rawRGB = Buffer.allocUnsafe(currentWidth * currentHeight * 3);
|
|
232
|
+
let writeOffset = 0;
|
|
233
|
+
const rowsPerChunk = 2000;
|
|
234
|
+
for (let startRow = 0; startRow < currentHeight; startRow += rowsPerChunk) {
|
|
235
|
+
const endRow = Math.min(startRow + rowsPerChunk, currentHeight);
|
|
236
|
+
const chunkHeight = endRow - startRow;
|
|
237
|
+
const { data: chunkData, info: chunkInfo } = await sharp(processedBuf)
|
|
238
|
+
.extract({
|
|
239
|
+
left: 0,
|
|
240
|
+
top: startRow,
|
|
241
|
+
width: currentWidth,
|
|
242
|
+
height: chunkHeight,
|
|
243
|
+
})
|
|
244
|
+
.raw()
|
|
245
|
+
.toBuffer({ resolveWithObject: true });
|
|
246
|
+
const channels = chunkInfo.channels;
|
|
247
|
+
const pixelsInChunk = currentWidth * chunkHeight;
|
|
248
|
+
if (channels === 3) {
|
|
249
|
+
chunkData.copy(rawRGB, writeOffset);
|
|
250
|
+
writeOffset += pixelsInChunk * 3;
|
|
251
|
+
}
|
|
252
|
+
else if (channels === 4) {
|
|
253
|
+
for (let i = 0; i < pixelsInChunk; i++) {
|
|
254
|
+
rawRGB[writeOffset++] = chunkData[i * 4];
|
|
255
|
+
rawRGB[writeOffset++] = chunkData[i * 4 + 1];
|
|
256
|
+
rawRGB[writeOffset++] = chunkData[i * 4 + 2];
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (opts.onProgress) {
|
|
260
|
+
opts.onProgress({
|
|
261
|
+
phase: 'extract_pixels',
|
|
262
|
+
loaded: endRow,
|
|
263
|
+
total: currentHeight,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
239
266
|
}
|
|
240
267
|
const firstPixels = [];
|
|
241
268
|
for (let i = 0; i < Math.min(MARKER_START.length, rawRGB.length / 3); i++) {
|
|
@@ -278,29 +305,25 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
278
305
|
else {
|
|
279
306
|
const reconstructed = await cropAndReconstitute(processedBuf, opts.debugDir);
|
|
280
307
|
const { data: rdata, info: rinfo } = await sharp(reconstructed)
|
|
281
|
-
.ensureAlpha()
|
|
282
308
|
.raw()
|
|
283
309
|
.toBuffer({ resolveWithObject: true });
|
|
284
310
|
logicalWidth = rinfo.width;
|
|
285
311
|
logicalHeight = rinfo.height;
|
|
286
312
|
logicalData = Buffer.alloc(rinfo.width * rinfo.height * 3);
|
|
287
|
-
|
|
288
|
-
logicalData
|
|
289
|
-
|
|
290
|
-
|
|
313
|
+
if (rinfo.channels === 3) {
|
|
314
|
+
rdata.copy(logicalData);
|
|
315
|
+
}
|
|
316
|
+
else if (rinfo.channels === 4) {
|
|
317
|
+
for (let i = 0; i < logicalWidth * logicalHeight; i++) {
|
|
318
|
+
logicalData[i * 3] = rdata[i * 4];
|
|
319
|
+
logicalData[i * 3 + 1] = rdata[i * 4 + 1];
|
|
320
|
+
logicalData[i * 3 + 2] = rdata[i * 4 + 2];
|
|
321
|
+
}
|
|
291
322
|
}
|
|
292
323
|
}
|
|
293
324
|
if (process.env.ROX_DEBUG) {
|
|
294
325
|
console.log('DEBUG: Logical grid reconstructed:', logicalWidth, 'x', logicalHeight, '=', logicalWidth * logicalHeight, 'pixels');
|
|
295
326
|
}
|
|
296
|
-
const finalGrid = [];
|
|
297
|
-
for (let i = 0; i < logicalData.length; i += 3) {
|
|
298
|
-
finalGrid.push({
|
|
299
|
-
r: logicalData[i],
|
|
300
|
-
g: logicalData[i + 1],
|
|
301
|
-
b: logicalData[i + 2],
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
327
|
if (hasPixelMagic) {
|
|
305
328
|
if (logicalData.length < 8 + PIXEL_MAGIC.length) {
|
|
306
329
|
throw new DataFormatError('Pixel mode data too short');
|
|
@@ -337,15 +360,15 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
337
360
|
payload = payload.slice(MAGIC.length);
|
|
338
361
|
return { buf: payload, meta: { name } };
|
|
339
362
|
}
|
|
363
|
+
const totalPixels = (logicalData.length / 3) | 0;
|
|
340
364
|
let startIdx = -1;
|
|
341
|
-
for (let i = 0; i <=
|
|
365
|
+
for (let i = 0; i <= totalPixels - MARKER_START.length; i++) {
|
|
342
366
|
let match = true;
|
|
343
367
|
for (let mi = 0; mi < MARKER_START.length && match; mi++) {
|
|
344
|
-
const
|
|
345
|
-
if (
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
p.b !== MARKER_START[mi].b) {
|
|
368
|
+
const offset = (i + mi) * 3;
|
|
369
|
+
if (logicalData[offset] !== MARKER_START[mi].r ||
|
|
370
|
+
logicalData[offset + 1] !== MARKER_START[mi].g ||
|
|
371
|
+
logicalData[offset + 2] !== MARKER_START[mi].b) {
|
|
349
372
|
match = false;
|
|
350
373
|
}
|
|
351
374
|
}
|
|
@@ -356,7 +379,7 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
356
379
|
}
|
|
357
380
|
if (startIdx === -1) {
|
|
358
381
|
if (process.env.ROX_DEBUG) {
|
|
359
|
-
console.log('DEBUG: MARKER_START not found in grid of',
|
|
382
|
+
console.log('DEBUG: MARKER_START not found in grid of', totalPixels, 'pixels');
|
|
360
383
|
console.log('DEBUG: Trying 2D scan for START marker...');
|
|
361
384
|
}
|
|
362
385
|
let found2D = false;
|
|
@@ -417,17 +440,20 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
417
440
|
if (process.env.ROX_DEBUG) {
|
|
418
441
|
console.log(`DEBUG: Extracted rectangle: ${rectWidth}x${rectHeight} from (${x},${y})`);
|
|
419
442
|
}
|
|
420
|
-
|
|
443
|
+
const newDataLen = rectWidth * rectHeight * 3;
|
|
444
|
+
const newData = Buffer.allocUnsafe(newDataLen);
|
|
445
|
+
let writeIdx = 0;
|
|
421
446
|
for (let ry = y; ry <= endY; ry++) {
|
|
422
447
|
for (let rx = x; rx <= endX; rx++) {
|
|
423
448
|
const idx = (ry * logicalWidth + rx) * 3;
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
b: logicalData[idx + 2],
|
|
428
|
-
});
|
|
449
|
+
newData[writeIdx++] = logicalData[idx];
|
|
450
|
+
newData[writeIdx++] = logicalData[idx + 1];
|
|
451
|
+
newData[writeIdx++] = logicalData[idx + 2];
|
|
429
452
|
}
|
|
430
453
|
}
|
|
454
|
+
logicalData = newData;
|
|
455
|
+
logicalWidth = rectWidth;
|
|
456
|
+
logicalHeight = rectHeight;
|
|
431
457
|
startIdx = 0;
|
|
432
458
|
found2D = true;
|
|
433
459
|
}
|
|
@@ -435,34 +461,43 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
435
461
|
}
|
|
436
462
|
if (!found2D) {
|
|
437
463
|
if (process.env.ROX_DEBUG) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
.
|
|
464
|
+
const first20 = [];
|
|
465
|
+
for (let i = 0; i < Math.min(20, totalPixels); i++) {
|
|
466
|
+
const offset = i * 3;
|
|
467
|
+
first20.push(`(${logicalData[offset]},${logicalData[offset + 1]},${logicalData[offset + 2]})`);
|
|
468
|
+
}
|
|
469
|
+
console.log('DEBUG: First 20 pixels:', first20.join(' '));
|
|
442
470
|
}
|
|
443
471
|
throw new Error('Marker START not found - image format not supported');
|
|
444
472
|
}
|
|
445
473
|
}
|
|
446
474
|
if (process.env.ROX_DEBUG && startIdx === 0) {
|
|
447
|
-
console.log(`DEBUG: MARKER_START at index ${startIdx}, grid size: ${
|
|
475
|
+
console.log(`DEBUG: MARKER_START at index ${startIdx}, grid size: ${totalPixels}`);
|
|
448
476
|
}
|
|
449
|
-
const
|
|
450
|
-
|
|
477
|
+
const dataStartPixel = startIdx + MARKER_START.length + 1;
|
|
478
|
+
const curTotalPixels = (logicalData.length / 3) | 0;
|
|
479
|
+
if (curTotalPixels < dataStartPixel + MARKER_END.length) {
|
|
451
480
|
if (process.env.ROX_DEBUG) {
|
|
452
|
-
console.log('DEBUG:
|
|
481
|
+
console.log('DEBUG: grid too small:', curTotalPixels, 'pixels');
|
|
453
482
|
}
|
|
454
483
|
throw new Error('Marker START or END not found - image format not supported');
|
|
455
484
|
}
|
|
456
485
|
for (let i = 0; i < MARKER_START.length; i++) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
486
|
+
const offset = (startIdx + i) * 3;
|
|
487
|
+
if (logicalData[offset] !== MARKER_START[i].r ||
|
|
488
|
+
logicalData[offset + 1] !== MARKER_START[i].g ||
|
|
489
|
+
logicalData[offset + 2] !== MARKER_START[i].b) {
|
|
460
490
|
throw new Error('Marker START not found - image format not supported');
|
|
461
491
|
}
|
|
462
492
|
}
|
|
463
493
|
let compression = 'zstd';
|
|
464
|
-
if (
|
|
465
|
-
const
|
|
494
|
+
if (curTotalPixels > startIdx + MARKER_START.length) {
|
|
495
|
+
const compOffset = (startIdx + MARKER_START.length) * 3;
|
|
496
|
+
const compPixel = {
|
|
497
|
+
r: logicalData[compOffset],
|
|
498
|
+
g: logicalData[compOffset + 1],
|
|
499
|
+
b: logicalData[compOffset + 2],
|
|
500
|
+
};
|
|
466
501
|
if (compPixel.r === 0 && compPixel.g === 255 && compPixel.b === 0) {
|
|
467
502
|
compression = 'zstd';
|
|
468
503
|
}
|
|
@@ -473,47 +508,51 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
473
508
|
if (process.env.ROX_DEBUG) {
|
|
474
509
|
console.log(`DEBUG: Detected compression: ${compression}`);
|
|
475
510
|
}
|
|
476
|
-
let
|
|
511
|
+
let endStartPixel = -1;
|
|
477
512
|
const lastLineStart = (logicalHeight - 1) * logicalWidth;
|
|
478
513
|
const endMarkerStartCol = logicalWidth - MARKER_END.length;
|
|
479
|
-
if (lastLineStart + endMarkerStartCol <
|
|
514
|
+
if (lastLineStart + endMarkerStartCol < curTotalPixels) {
|
|
480
515
|
let matchEnd = true;
|
|
481
516
|
for (let mi = 0; mi < MARKER_END.length && matchEnd; mi++) {
|
|
482
|
-
const
|
|
483
|
-
if (
|
|
517
|
+
const pixelIdx = lastLineStart + endMarkerStartCol + mi;
|
|
518
|
+
if (pixelIdx >= curTotalPixels) {
|
|
484
519
|
matchEnd = false;
|
|
485
520
|
break;
|
|
486
521
|
}
|
|
487
|
-
const
|
|
488
|
-
if (
|
|
489
|
-
|
|
490
|
-
|
|
522
|
+
const offset = pixelIdx * 3;
|
|
523
|
+
if (logicalData[offset] !== MARKER_END[mi].r ||
|
|
524
|
+
logicalData[offset + 1] !== MARKER_END[mi].g ||
|
|
525
|
+
logicalData[offset + 2] !== MARKER_END[mi].b) {
|
|
491
526
|
matchEnd = false;
|
|
492
527
|
}
|
|
493
528
|
}
|
|
494
529
|
if (matchEnd) {
|
|
495
|
-
|
|
530
|
+
endStartPixel = lastLineStart + endMarkerStartCol - startIdx;
|
|
496
531
|
if (process.env.ROX_DEBUG) {
|
|
497
532
|
console.log(`DEBUG: Found END marker at last line, col ${endMarkerStartCol}`);
|
|
498
533
|
}
|
|
499
534
|
}
|
|
500
535
|
}
|
|
501
|
-
if (
|
|
536
|
+
if (endStartPixel === -1) {
|
|
502
537
|
if (process.env.ROX_DEBUG) {
|
|
503
538
|
console.log('DEBUG: END marker not found at expected position');
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
.
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
539
|
+
const lastLinePixels = [];
|
|
540
|
+
for (let i = Math.max(0, lastLineStart); i < curTotalPixels && i < lastLineStart + 20; i++) {
|
|
541
|
+
const offset = i * 3;
|
|
542
|
+
lastLinePixels.push(`(${logicalData[offset]},${logicalData[offset + 1]},${logicalData[offset + 2]})`);
|
|
543
|
+
}
|
|
544
|
+
console.log('DEBUG: Last line pixels:', lastLinePixels.join(' '));
|
|
545
|
+
}
|
|
546
|
+
endStartPixel = curTotalPixels - startIdx;
|
|
547
|
+
}
|
|
548
|
+
const dataPixelCount = endStartPixel - (MARKER_START.length + 1);
|
|
549
|
+
const pixelBytes = Buffer.allocUnsafe(dataPixelCount * 3);
|
|
550
|
+
for (let i = 0; i < dataPixelCount; i++) {
|
|
551
|
+
const srcOffset = (dataStartPixel + i) * 3;
|
|
552
|
+
const dstOffset = i * 3;
|
|
553
|
+
pixelBytes[dstOffset] = logicalData[srcOffset];
|
|
554
|
+
pixelBytes[dstOffset + 1] = logicalData[srcOffset + 1];
|
|
555
|
+
pixelBytes[dstOffset + 2] = logicalData[srcOffset + 2];
|
|
517
556
|
}
|
|
518
557
|
if (process.env.ROX_DEBUG) {
|
|
519
558
|
console.log('DEBUG: extracted len', pixelBytes.length);
|
package/dist/utils/encoder.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import { EncodeOptions } from './types.js';
|
|
4
|
-
export declare function encodeBinaryToPng(input: Buffer, opts?: EncodeOptions): Promise<Buffer>;
|
|
4
|
+
export declare function encodeBinaryToPng(input: Buffer | Buffer[], opts?: EncodeOptions): Promise<Buffer>;
|