roxify 1.4.0 → 1.5.0

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
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import cliProgress from 'cli-progress';
3
2
  import { mkdirSync, readFileSync, statSync, writeFileSync } from 'fs';
4
3
  import { open } from 'fs/promises';
5
4
  import { basename, dirname, join, resolve } from 'path';
6
5
  import { DataFormatError, decodePngToBinary, encodeBinaryToPng, hasPassphraseInPng, IncorrectPassphraseError, listFilesInPng, PassphraseRequiredError, } from './index.js';
7
6
  import { packPathsGenerator, unpackBuffer } from './pack.js';
7
+ import * as cliProgress from './stub-progress.js';
8
8
  import { encodeWithRustCLI, isRustBinaryAvailable, } from './utils/rust-cli-wrapper.js';
9
9
  const VERSION = '1.4.0';
10
10
  async function readLargeFile(filePath) {
@@ -211,7 +211,8 @@ async function encodeCommand(args) {
211
211
  });
212
212
  }, 500);
213
213
  const encryptType = parsed.encrypt === 'xor' ? 'xor' : 'aes';
214
- await encodeWithRustCLI(inputPaths.length === 1 ? resolvedInputs[0] : resolvedInputs[0], resolvedOutput, 19, parsed.passphrase, encryptType);
214
+ const fileName = basename(inputPaths[0]);
215
+ await encodeWithRustCLI(inputPaths.length === 1 ? resolvedInputs[0] : resolvedInputs[0], resolvedOutput, 12, parsed.passphrase, encryptType, fileName);
215
216
  clearInterval(progressInterval);
216
217
  const encodeTime = Date.now() - startTime;
217
218
  encodeBar.update(100, {
@@ -284,7 +285,7 @@ async function encodeCommand(args) {
284
285
  mode,
285
286
  name: parsed.outputName || 'archive',
286
287
  skipOptimization: false,
287
- compressionLevel: 19,
288
+ compressionLevel: 12,
288
289
  outputFormat: 'auto',
289
290
  });
290
291
  if (parsed.verbose)
@@ -1,5 +1,3 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  export interface CompressionStats {
4
2
  originalSize: number;
5
3
  compressedSize: number;
@@ -1,10 +1,10 @@
1
+ import { native } from './utils/native.js';
1
2
  let check_gpu_status;
2
3
  let entropy_estimate;
3
4
  let get_compression_stats;
4
5
  let hybrid_compress;
5
6
  let hybrid_decompress;
6
7
  try {
7
- const native = require('../libroxify_native.node');
8
8
  check_gpu_status = native.check_gpu_status;
9
9
  entropy_estimate = native.entropy_estimate;
10
10
  get_compression_stats = native.get_compression_stats;
package/dist/minpng.d.ts CHANGED
@@ -1,5 +1,3 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  export declare function encodeMinPng(rgb: Buffer, width: number, height: number): Promise<Buffer>;
4
2
  export declare function decodeMinPng(pngBuf: Buffer): Promise<{
5
3
  buf: Buffer;
package/dist/minpng.js CHANGED
@@ -1,6 +1,20 @@
1
- import { compress as zstdCompress, decompress as zstdDecompress, } from '@mongodb-js/zstd';
2
- import encode from 'png-chunks-encode';
3
1
  import { deflateSync } from 'zlib';
2
+ import { native } from './utils/native.js';
3
+ let nativeZstdCompress = null;
4
+ let nativeZstdDecompress = null;
5
+ let nativeEncodePngChunks = null;
6
+ try {
7
+ if (native?.nativeZstdCompress) {
8
+ nativeZstdCompress = native.nativeZstdCompress;
9
+ }
10
+ if (native?.nativeZstdDecompress) {
11
+ nativeZstdDecompress = native.nativeZstdDecompress;
12
+ }
13
+ if (native?.encodePngChunks) {
14
+ nativeEncodePngChunks = native.encodePngChunks;
15
+ }
16
+ }
17
+ catch (e) { }
4
18
  const PIXEL_MAGIC = Buffer.from('MNPG');
5
19
  const MARKER_START = [
6
20
  { r: 255, g: 0, b: 0 },
@@ -82,7 +96,10 @@ export async function encodeMinPng(rgb, width, height) {
82
96
  transformed[tIdx++] = b;
83
97
  }
84
98
  const transformedBuf = Buffer.from(transformed);
85
- const compressed = Buffer.from(await zstdCompress(transformedBuf, 19));
99
+ if (!nativeZstdCompress) {
100
+ throw new Error('Native zstd compression not available');
101
+ }
102
+ const compressed = Buffer.from(nativeZstdCompress(transformedBuf, 19));
86
103
  const header = Buffer.alloc(4 + 1 + 4 + 4);
87
104
  PIXEL_MAGIC.copy(header, 0);
88
105
  header[4] = 1;
@@ -142,16 +159,29 @@ export async function encodeMinPng(rgb, width, height) {
142
159
  { name: 'IDAT', data: idat },
143
160
  { name: 'IEND', data: Buffer.alloc(0) },
144
161
  ];
145
- return Buffer.from(encode(chunks));
162
+ if (nativeEncodePngChunks) {
163
+ return Buffer.from(nativeEncodePngChunks(chunks));
164
+ }
165
+ const PNG_SIG = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
166
+ const output = [PNG_SIG];
167
+ for (const chunk of chunks) {
168
+ const type = Buffer.from(chunk.name, 'ascii');
169
+ const length = Buffer.alloc(4);
170
+ length.writeUInt32BE(chunk.data.length, 0);
171
+ const crcData = Buffer.concat([type, chunk.data]);
172
+ const crc = Buffer.alloc(4);
173
+ const crc32fast = native?.nativeCrc32;
174
+ const crcVal = crc32fast ? crc32fast(crcData) : 0;
175
+ crc.writeUInt32BE(crcVal, 0);
176
+ output.push(length, type, chunk.data, crc);
177
+ }
178
+ return Buffer.concat(output);
146
179
  }
147
180
  export async function decodeMinPng(pngBuf) {
148
- const sharp = await import('sharp');
149
- const { data, info } = await sharp
150
- .default(pngBuf)
151
- .raw()
152
- .toBuffer({ resolveWithObject: true });
153
- const currentWidth = info.width;
154
- const currentHeight = info.height;
181
+ const rawData = native.sharpToRaw(pngBuf);
182
+ const data = rawData.pixels;
183
+ const currentWidth = rawData.width;
184
+ const currentHeight = rawData.height;
155
185
  const rawRGB = Buffer.alloc(currentWidth * currentHeight * 3);
156
186
  for (let i = 0; i < currentWidth * currentHeight; i++) {
157
187
  rawRGB[i * 3] = data[i * 3];
@@ -190,7 +220,10 @@ export async function decodeMinPng(pngBuf) {
190
220
  if (compStart + compressedLen > rawRGB.length)
191
221
  return null;
192
222
  const compressed = rawRGB.subarray(compStart, compStart + compressedLen);
193
- const decompressed = Buffer.from(await zstdDecompress(compressed));
223
+ if (!nativeZstdDecompress) {
224
+ throw new Error('Native zstd decompression not available');
225
+ }
226
+ const decompressed = Buffer.from(nativeZstdDecompress(compressed));
194
227
  const indices = zigzagOrderIndices(origW, origH);
195
228
  const residualR = new Uint8Array(origW * origH);
196
229
  const residualG = new Uint8Array(origW * origH);
package/dist/pack.d.ts CHANGED
@@ -1,5 +1,3 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  export interface PackedFile {
4
2
  path: string;
5
3
  buf: Buffer;
package/dist/roxify-cli CHANGED
Binary file
@@ -0,0 +1,9 @@
1
+ export declare class SingleBar {
2
+ constructor(...args: any[]);
3
+ start(...args: any[]): void;
4
+ update(...args: any[]): void;
5
+ stop(...args: any[]): void;
6
+ }
7
+ export declare const Presets: {
8
+ shades_classic: {};
9
+ };
@@ -0,0 +1,9 @@
1
+ export class SingleBar {
2
+ constructor(...args) { }
3
+ start(...args) { }
4
+ update(...args) { }
5
+ stop(...args) { }
6
+ }
7
+ export const Presets = {
8
+ shades_classic: {},
9
+ };
@@ -1,14 +1,12 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  export declare const CHUNK_TYPE = "rXDT";
4
- export declare const MAGIC: Buffer;
5
- export declare const PIXEL_MAGIC: Buffer;
6
- export declare const PIXEL_MAGIC_BLOCK: Buffer;
2
+ export declare const MAGIC: Buffer<ArrayBuffer>;
3
+ export declare const PIXEL_MAGIC: Buffer<ArrayBuffer>;
4
+ export declare const PIXEL_MAGIC_BLOCK: Buffer<ArrayBuffer>;
7
5
  export declare const ENC_NONE = 0;
8
6
  export declare const ENC_AES = 1;
9
7
  export declare const ENC_XOR = 2;
10
- export declare const FILTER_ZERO: Buffer;
11
- export declare const PNG_HEADER: Buffer;
8
+ export declare const FILTER_ZERO: Buffer<ArrayBuffer>;
9
+ export declare const PNG_HEADER: Buffer<ArrayBuffer>;
12
10
  export declare const PNG_HEADER_HEX: string;
13
11
  export declare const MARKER_COLORS: {
14
12
  r: number;
@@ -37,3 +35,20 @@ export declare const COMPRESSION_MARKERS: {
37
35
  b: number;
38
36
  }[];
39
37
  };
38
+ export declare const FORMAT_MARKERS: {
39
+ png: {
40
+ r: number;
41
+ g: number;
42
+ b: number;
43
+ };
44
+ webp: {
45
+ r: number;
46
+ g: number;
47
+ b: number;
48
+ };
49
+ jxl: {
50
+ r: number;
51
+ g: number;
52
+ b: number;
53
+ };
54
+ };
@@ -21,3 +21,8 @@ export const COMPRESSION_MARKERS = {
21
21
  zstd: [{ r: 0, g: 255, b: 0 }],
22
22
  lzma: [{ r: 255, g: 255, b: 0 }],
23
23
  };
24
+ export const FORMAT_MARKERS = {
25
+ png: { r: 0, g: 255, b: 255 },
26
+ webp: { r: 255, g: 0, b: 255 },
27
+ jxl: { r: 255, g: 255, b: 0 },
28
+ };
@@ -1,4 +1,2 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  export declare function crc32(buf: Buffer, previous?: number): number;
4
2
  export declare function adler32(buf: Buffer, prev?: number): number;
@@ -1,4 +1,2 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  import { DecodeOptions, DecodeResult } from './types.js';
4
2
  export declare function decodePngToBinary(input: Buffer | string, opts?: DecodeOptions): Promise<DecodeResult>;
@@ -1,11 +1,12 @@
1
- import cliProgress from 'cli-progress';
2
- import { readFileSync } from 'fs';
3
- import extract from 'png-chunks-extract';
4
- import sharp from 'sharp';
1
+ import { execFileSync } from 'child_process';
2
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs';
3
+ import { tmpdir } from 'os';
4
+ import { join } from 'path';
5
5
  import { unpackBuffer } from '../pack.js';
6
6
  import { CHUNK_TYPE, MAGIC, MARKER_END, MARKER_START, PIXEL_MAGIC, PIXEL_MAGIC_BLOCK, PNG_HEADER, } from './constants.js';
7
7
  import { DataFormatError, IncorrectPassphraseError, PassphraseRequiredError, } from './errors.js';
8
8
  import { colorsToBytes, deltaDecode, tryDecryptIfNeeded } from './helpers.js';
9
+ import { native } from './native.js';
9
10
  import { cropAndReconstitute } from './reconstitution.js';
10
11
  import { parallelZstdDecompress, tryZstdDecompress } from './zstd.js';
11
12
  async function tryDecompress(payload, onProgress) {
@@ -34,6 +35,52 @@ async function tryDecompress(payload, onProgress) {
34
35
  }
35
36
  }
36
37
  }
38
+ function detectImageFormat(buf) {
39
+ if (buf.length < 12)
40
+ return 'unknown';
41
+ if (buf[0] === 0x89 &&
42
+ buf[1] === 0x50 &&
43
+ buf[2] === 0x4e &&
44
+ buf[3] === 0x47) {
45
+ return 'png';
46
+ }
47
+ if (buf[0] === 0x52 &&
48
+ buf[1] === 0x49 &&
49
+ buf[2] === 0x46 &&
50
+ buf[3] === 0x46 &&
51
+ buf[8] === 0x57 &&
52
+ buf[9] === 0x45 &&
53
+ buf[10] === 0x42 &&
54
+ buf[11] === 0x50) {
55
+ return 'webp';
56
+ }
57
+ if (buf[0] === 0xff && buf[1] === 0x0a) {
58
+ return 'jxl';
59
+ }
60
+ return 'unknown';
61
+ }
62
+ function convertToPng(buf, format) {
63
+ const tempDir = mkdtempSync(join(tmpdir(), 'rox-decode-'));
64
+ const inputPath = join(tempDir, format === 'webp' ? 'input.webp' : 'input.jxl');
65
+ const outputPath = join(tempDir, 'output.png');
66
+ try {
67
+ writeFileSync(inputPath, buf);
68
+ if (format === 'webp') {
69
+ execFileSync('dwebp', [inputPath, '-o', outputPath]);
70
+ }
71
+ else if (format === 'jxl') {
72
+ execFileSync('djxl', [inputPath, outputPath]);
73
+ }
74
+ const pngBuf = readFileSync(outputPath);
75
+ return pngBuf;
76
+ }
77
+ finally {
78
+ try {
79
+ rmSync(tempDir, { recursive: true, force: true });
80
+ }
81
+ catch (e) { }
82
+ }
83
+ }
37
84
  export async function decodePngToBinary(input, opts = {}) {
38
85
  let pngBuf;
39
86
  if (Buffer.isBuffer(input)) {
@@ -41,11 +88,17 @@ export async function decodePngToBinary(input, opts = {}) {
41
88
  }
42
89
  else {
43
90
  try {
44
- const metadata = await sharp(input).metadata();
45
- const rawBytesEstimate = (metadata.width || 0) * (metadata.height || 0) * 4;
46
- const MAX_RAW_BYTES = 200 * 1024 * 1024;
47
- if (rawBytesEstimate > MAX_RAW_BYTES) {
48
- pngBuf = readFileSync(input);
91
+ if (native?.sharpMetadata) {
92
+ const inputBuf = readFileSync(input);
93
+ const metadata = native.sharpMetadata(inputBuf);
94
+ const rawBytesEstimate = metadata.width * metadata.height * 4;
95
+ const MAX_RAW_BYTES = 200 * 1024 * 1024;
96
+ if (rawBytesEstimate > MAX_RAW_BYTES) {
97
+ pngBuf = inputBuf;
98
+ }
99
+ else {
100
+ pngBuf = inputBuf;
101
+ }
49
102
  }
50
103
  else {
51
104
  pngBuf = readFileSync(input);
@@ -62,10 +115,11 @@ export async function decodePngToBinary(input, opts = {}) {
62
115
  }
63
116
  let progressBar = null;
64
117
  if (opts.showProgress) {
65
- progressBar = new cliProgress.SingleBar({
66
- format: ' {bar} {percentage}% | {step} | {elapsed}s',
67
- }, cliProgress.Presets.shades_classic);
68
- progressBar.start(100, 0, { step: 'Starting', elapsed: '0' });
118
+ progressBar = {
119
+ start: () => { },
120
+ update: () => { },
121
+ stop: () => { },
122
+ };
69
123
  const startTime = Date.now();
70
124
  if (!opts.onProgress) {
71
125
  opts.onProgress = (info) => {
@@ -79,10 +133,6 @@ export async function decodePngToBinary(input, opts = {}) {
79
133
  else if (info.phase === 'done') {
80
134
  pct = 100;
81
135
  }
82
- progressBar.update(Math.floor(pct), {
83
- step: info.phase.replace('_', ' '),
84
- elapsed: String(Math.floor((Date.now() - startTime) / 1000)),
85
- });
86
136
  };
87
137
  }
88
138
  }
@@ -90,28 +140,17 @@ export async function decodePngToBinary(input, opts = {}) {
90
140
  opts.onProgress({ phase: 'start' });
91
141
  let processedBuf = pngBuf;
92
142
  try {
93
- const info = await sharp(pngBuf).metadata();
94
- if (info.width && info.height) {
95
- const MAX_RAW_BYTES = 1200 * 1024 * 1024;
96
- const rawBytesEstimate = info.width * info.height * 4;
97
- if (rawBytesEstimate > MAX_RAW_BYTES) {
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.`);
99
- }
100
- if (false) {
101
- const doubledBuffer = await sharp(pngBuf)
102
- .resize({
103
- width: info.width * 2,
104
- height: info.height * 2,
105
- kernel: 'nearest',
106
- })
107
- .png()
108
- .toBuffer();
109
- processedBuf = await cropAndReconstitute(doubledBuffer, opts.debugDir);
110
- }
111
- else {
112
- processedBuf = pngBuf;
143
+ if (native?.sharpMetadata) {
144
+ const info = native.sharpMetadata(pngBuf);
145
+ if (info.width && info.height) {
146
+ const MAX_RAW_BYTES = 1200 * 1024 * 1024;
147
+ const rawBytesEstimate = info.width * info.height * 4;
148
+ if (rawBytesEstimate > MAX_RAW_BYTES) {
149
+ 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.`);
150
+ }
113
151
  }
114
152
  }
153
+ processedBuf = pngBuf;
115
154
  }
116
155
  catch (e) {
117
156
  if (e instanceof DataFormatError)
@@ -155,24 +194,30 @@ export async function decodePngToBinary(input, opts = {}) {
155
194
  }
156
195
  let chunks = [];
157
196
  try {
158
- const chunksRaw = extract(processedBuf);
159
- chunks = chunksRaw.map((c) => ({
160
- name: c.name,
161
- data: Buffer.isBuffer(c.data)
162
- ? c.data
163
- : Buffer.from(c.data),
164
- }));
197
+ if (native?.extractPngChunks) {
198
+ const chunksRaw = native.extractPngChunks(processedBuf);
199
+ chunks = chunksRaw.map((c) => ({
200
+ name: c.name,
201
+ data: Buffer.from(c.data),
202
+ }));
203
+ }
204
+ else {
205
+ throw new Error('Native PNG chunk extraction not available');
206
+ }
165
207
  }
166
208
  catch (e) {
167
209
  try {
168
210
  const withHeader = Buffer.concat([PNG_HEADER, pngBuf]);
169
- const chunksRaw = extract(withHeader);
170
- chunks = chunksRaw.map((c) => ({
171
- name: c.name,
172
- data: Buffer.isBuffer(c.data)
173
- ? c.data
174
- : Buffer.from(c.data),
175
- }));
211
+ if (native?.extractPngChunks) {
212
+ const chunksRaw = native.extractPngChunks(withHeader);
213
+ chunks = chunksRaw.map((c) => ({
214
+ name: c.name,
215
+ data: Buffer.from(c.data),
216
+ }));
217
+ }
218
+ else {
219
+ throw new Error('Native PNG chunk extraction not available');
220
+ }
176
221
  }
177
222
  catch (e2) {
178
223
  chunks = [];
@@ -225,28 +270,21 @@ export async function decodePngToBinary(input, opts = {}) {
225
270
  return { buf: payload, meta: { name } };
226
271
  }
227
272
  try {
228
- const metadata = await sharp(processedBuf).metadata();
273
+ const metadata = native.sharpMetadata(processedBuf);
229
274
  const currentWidth = metadata.width;
230
275
  const currentHeight = metadata.height;
231
276
  let rawRGB = Buffer.alloc(0);
232
277
  let isBlockEncoded = false;
233
278
  if (currentWidth % 2 === 0 && currentHeight % 2 === 0) {
234
- const { data: testData } = await sharp(processedBuf)
235
- .extract({
236
- left: 0,
237
- top: 0,
238
- width: Math.min(4, currentWidth),
239
- height: Math.min(4, currentHeight),
240
- })
241
- .raw()
242
- .toBuffer({ resolveWithObject: true });
279
+ const rawData = native.sharpToRaw(processedBuf);
280
+ const testData = rawData.pixels;
243
281
  let hasBlockPattern = true;
244
282
  for (let y = 0; y < Math.min(2, currentHeight / 2); y++) {
245
283
  for (let x = 0; x < Math.min(2, currentWidth / 2); x++) {
246
- const px00 = (y * 2 * Math.min(4, currentWidth) + x * 2) * 3;
247
- const px01 = (y * 2 * Math.min(4, currentWidth) + (x * 2 + 1)) * 3;
248
- const px10 = ((y * 2 + 1) * Math.min(4, currentWidth) + x * 2) * 3;
249
- const px11 = ((y * 2 + 1) * Math.min(4, currentWidth) + (x * 2 + 1)) * 3;
284
+ const px00 = (y * 2 * currentWidth + x * 2) * 3;
285
+ const px01 = (y * 2 * currentWidth + (x * 2 + 1)) * 3;
286
+ const px10 = ((y * 2 + 1) * currentWidth + x * 2) * 3;
287
+ const px11 = ((y * 2 + 1) * currentWidth + (x * 2 + 1)) * 3;
250
288
  if (testData[px00] !== testData[px01] ||
251
289
  testData[px00] !== testData[px10] ||
252
290
  testData[px00] !== testData[px11] ||
@@ -265,56 +303,28 @@ export async function decodePngToBinary(input, opts = {}) {
265
303
  const blocksWide = currentWidth / 2;
266
304
  const blocksHigh = currentHeight / 2;
267
305
  rawRGB = Buffer.alloc(blocksWide * blocksHigh * 3);
306
+ const fullRaw = native.sharpToRaw(processedBuf);
307
+ const fullData = fullRaw.pixels;
268
308
  let outIdx = 0;
269
309
  for (let by = 0; by < blocksHigh; by++) {
270
310
  for (let bx = 0; bx < blocksWide; bx++) {
271
- const { data: blockData } = await sharp(processedBuf)
272
- .extract({ left: bx * 2, top: by * 2, width: 1, height: 1 })
273
- .raw()
274
- .toBuffer({ resolveWithObject: true });
275
- rawRGB[outIdx++] = blockData[0];
276
- rawRGB[outIdx++] = blockData[1];
277
- rawRGB[outIdx++] = blockData[2];
311
+ const pixelOffset = (by * 2 * currentWidth + bx * 2) * 3;
312
+ rawRGB[outIdx++] = fullData[pixelOffset];
313
+ rawRGB[outIdx++] = fullData[pixelOffset + 1];
314
+ rawRGB[outIdx++] = fullData[pixelOffset + 2];
278
315
  }
279
316
  }
280
317
  }
281
318
  }
282
319
  if (!isBlockEncoded) {
283
- rawRGB = Buffer.allocUnsafe(currentWidth * currentHeight * 3);
284
- let writeOffset = 0;
285
- const rowsPerChunk = 2000;
286
- for (let startRow = 0; startRow < currentHeight; startRow += rowsPerChunk) {
287
- const endRow = Math.min(startRow + rowsPerChunk, currentHeight);
288
- const chunkHeight = endRow - startRow;
289
- const { data: chunkData, info: chunkInfo } = await sharp(processedBuf)
290
- .extract({
291
- left: 0,
292
- top: startRow,
293
- width: currentWidth,
294
- height: chunkHeight,
295
- })
296
- .raw()
297
- .toBuffer({ resolveWithObject: true });
298
- const channels = chunkInfo.channels;
299
- const pixelsInChunk = currentWidth * chunkHeight;
300
- if (channels === 3) {
301
- chunkData.copy(rawRGB, writeOffset);
302
- writeOffset += pixelsInChunk * 3;
303
- }
304
- else if (channels === 4) {
305
- for (let i = 0; i < pixelsInChunk; i++) {
306
- rawRGB[writeOffset++] = chunkData[i * 4];
307
- rawRGB[writeOffset++] = chunkData[i * 4 + 1];
308
- rawRGB[writeOffset++] = chunkData[i * 4 + 2];
309
- }
310
- }
311
- if (opts.onProgress) {
312
- opts.onProgress({
313
- phase: 'extract_pixels',
314
- loaded: endRow,
315
- total: currentHeight,
316
- });
317
- }
320
+ const rawData = native.sharpToRaw(processedBuf);
321
+ rawRGB = Buffer.from(rawData.pixels);
322
+ if (opts.onProgress) {
323
+ opts.onProgress({
324
+ phase: 'extract_pixels',
325
+ loaded: currentHeight,
326
+ total: currentHeight,
327
+ });
318
328
  }
319
329
  }
320
330
  const firstPixels = [];
@@ -361,22 +371,10 @@ export async function decodePngToBinary(input, opts = {}) {
361
371
  }
362
372
  else {
363
373
  const reconstructed = await cropAndReconstitute(processedBuf, opts.debugDir);
364
- const { data: rdata, info: rinfo } = await sharp(reconstructed)
365
- .raw()
366
- .toBuffer({ resolveWithObject: true });
367
- logicalWidth = rinfo.width;
368
- logicalHeight = rinfo.height;
369
- logicalData = Buffer.alloc(rinfo.width * rinfo.height * 3);
370
- if (rinfo.channels === 3) {
371
- rdata.copy(logicalData);
372
- }
373
- else if (rinfo.channels === 4) {
374
- for (let i = 0; i < logicalWidth * logicalHeight; i++) {
375
- logicalData[i * 3] = rdata[i * 4];
376
- logicalData[i * 3 + 1] = rdata[i * 4 + 1];
377
- logicalData[i * 3 + 2] = rdata[i * 4 + 2];
378
- }
379
- }
374
+ const rawData = native.sharpToRaw(reconstructed);
375
+ logicalWidth = rawData.width;
376
+ logicalHeight = rawData.height;
377
+ logicalData = Buffer.from(rawData.pixels);
380
378
  }
381
379
  if (process.env.ROX_DEBUG) {
382
380
  console.log('DEBUG: Logical grid reconstructed:', logicalWidth, 'x', logicalHeight, '=', logicalWidth * logicalHeight, 'pixels');
@@ -1,4 +1,2 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  import { EncodeOptions } from './types.js';
4
2
  export declare function encodeBinaryToPng(input: Buffer | Buffer[], opts?: EncodeOptions): Promise<Buffer>;