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 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.4';
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) => e.path);
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) => e.path);
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 = Buffer.concat(chunks);
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
- extractBar.start(totalBytes, 0, { step: 'Writing files' });
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, { step: `Writing ${file.path}` });
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, { step: 'Done' });
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
- console.log(` ${file}`);
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 packPaths(paths, baseDir, onProgress) {
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;
@@ -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 = 150 * 1024 * 1024;
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 { data, info } = await sharp(processedBuf)
229
- .ensureAlpha()
230
- .raw()
231
- .toBuffer({ resolveWithObject: true });
232
- const currentWidth = info.width;
233
- const currentHeight = info.height;
234
- const rawRGB = Buffer.alloc(currentWidth * currentHeight * 3);
235
- for (let i = 0; i < currentWidth * currentHeight; i++) {
236
- rawRGB[i * 3] = data[i * 4];
237
- rawRGB[i * 3 + 1] = data[i * 4 + 1];
238
- rawRGB[i * 3 + 2] = data[i * 4 + 2];
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
- for (let i = 0; i < rinfo.width * rinfo.height; i++) {
288
- logicalData[i * 3] = rdata[i * 4];
289
- logicalData[i * 3 + 1] = rdata[i * 4 + 1];
290
- logicalData[i * 3 + 2] = rdata[i * 4 + 2];
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 <= finalGrid.length - MARKER_START.length; 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 p = finalGrid[i + mi];
345
- if (!p ||
346
- p.r !== MARKER_START[mi].r ||
347
- p.g !== MARKER_START[mi].g ||
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', finalGrid.length, 'pixels');
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
- finalGrid.length = 0;
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
- finalGrid.push({
425
- r: logicalData[idx],
426
- g: logicalData[idx + 1],
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
- console.log('DEBUG: First 20 pixels:', finalGrid
439
- .slice(0, 20)
440
- .map((p) => `(${p.r},${p.g},${p.b})`)
441
- .join(' '));
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: ${finalGrid.length}`);
475
+ console.log(`DEBUG: MARKER_START at index ${startIdx}, grid size: ${totalPixels}`);
448
476
  }
449
- const gridFromStart = finalGrid.slice(startIdx);
450
- if (gridFromStart.length < MARKER_START.length + MARKER_END.length) {
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: gridFromStart too small:', gridFromStart.length, 'pixels');
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
- if (gridFromStart[i].r !== MARKER_START[i].r ||
458
- gridFromStart[i].g !== MARKER_START[i].g ||
459
- gridFromStart[i].b !== MARKER_START[i].b) {
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 (gridFromStart.length > MARKER_START.length) {
465
- const compPixel = gridFromStart[MARKER_START.length];
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 endStartIdx = -1;
511
+ let endStartPixel = -1;
477
512
  const lastLineStart = (logicalHeight - 1) * logicalWidth;
478
513
  const endMarkerStartCol = logicalWidth - MARKER_END.length;
479
- if (lastLineStart + endMarkerStartCol < finalGrid.length) {
514
+ if (lastLineStart + endMarkerStartCol < curTotalPixels) {
480
515
  let matchEnd = true;
481
516
  for (let mi = 0; mi < MARKER_END.length && matchEnd; mi++) {
482
- const idx = lastLineStart + endMarkerStartCol + mi;
483
- if (idx >= finalGrid.length) {
517
+ const pixelIdx = lastLineStart + endMarkerStartCol + mi;
518
+ if (pixelIdx >= curTotalPixels) {
484
519
  matchEnd = false;
485
520
  break;
486
521
  }
487
- const p = finalGrid[idx];
488
- if (p.r !== MARKER_END[mi].r ||
489
- p.g !== MARKER_END[mi].g ||
490
- p.b !== MARKER_END[mi].b) {
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
- endStartIdx = lastLineStart + endMarkerStartCol - startIdx;
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 (endStartIdx === -1) {
536
+ if (endStartPixel === -1) {
502
537
  if (process.env.ROX_DEBUG) {
503
538
  console.log('DEBUG: END marker not found at expected position');
504
- console.log('DEBUG: Last line pixels:', finalGrid
505
- .slice(Math.max(0, lastLineStart), finalGrid.length)
506
- .map((p) => `(${p.r},${p.g},${p.b})`)
507
- .join(' '));
508
- }
509
- endStartIdx = gridFromStart.length;
510
- }
511
- const dataGrid = gridFromStart.slice(MARKER_START.length + 1, endStartIdx);
512
- const pixelBytes = Buffer.alloc(dataGrid.length * 3);
513
- for (let i = 0; i < dataGrid.length; i++) {
514
- pixelBytes[i * 3] = dataGrid[i].r;
515
- pixelBytes[i * 3 + 1] = dataGrid[i].g;
516
- pixelBytes[i * 3 + 2] = dataGrid[i].b;
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);
@@ -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>;