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.
@@ -1,20 +1,19 @@
1
- import cliProgress from 'cli-progress';
2
1
  import { createCipheriv, pbkdf2Sync, randomBytes } from 'crypto';
3
- import sharp from 'sharp';
4
2
  import * as zlib from 'zlib';
5
3
  import { unpackBuffer } from '../pack.js';
6
4
  import { COMPRESSION_MARKERS, ENC_AES, ENC_NONE, ENC_XOR, MAGIC, MARKER_END, MARKER_START, PIXEL_MAGIC, PIXEL_MAGIC_BLOCK, PNG_HEADER, } from './constants.js';
7
5
  import { crc32 } from './crc.js';
8
6
  import { colorsToBytes } from './helpers.js';
9
- import { optimizePngBuffer } from './optimization.js';
7
+ import { native } from './native.js';
10
8
  import { parallelZstdCompress } from './zstd.js';
11
9
  export async function encodeBinaryToPng(input, opts = {}) {
12
10
  let progressBar = null;
13
11
  if (opts.showProgress) {
14
- progressBar = new cliProgress.SingleBar({
15
- format: ' {bar} {percentage}% | {step} | {elapsed}s',
16
- }, cliProgress.Presets.shades_classic);
17
- progressBar.start(100, 0, { step: 'Starting', elapsed: '0' });
12
+ progressBar = {
13
+ start: () => { },
14
+ update: () => { },
15
+ stop: () => { },
16
+ };
18
17
  const startTime = Date.now();
19
18
  if (!opts.onProgress) {
20
19
  opts.onProgress = (info) => {
@@ -34,10 +33,6 @@ export async function encodeBinaryToPng(input, opts = {}) {
34
33
  else if (info.phase === 'done') {
35
34
  pct = 100;
36
35
  }
37
- progressBar.update(Math.floor(pct), {
38
- step: info.phase.replace('_', ' '),
39
- elapsed: String(Math.floor((Date.now() - startTime) / 1000)),
40
- });
41
36
  };
42
37
  }
43
38
  }
@@ -220,7 +215,7 @@ export async function encodeBinaryToPng(input, opts = {}) {
220
215
  lenBuf.writeUInt32BE(jsonBuf.length, 0);
221
216
  metaPixel = [...metaPixel, Buffer.from('rXFL', 'utf8'), lenBuf, jsonBuf];
222
217
  }
223
- const useBlockEncoding = opts.useBlockEncoding ?? true;
218
+ const useBlockEncoding = false;
224
219
  const pixelMagic = useBlockEncoding ? PIXEL_MAGIC_BLOCK : PIXEL_MAGIC;
225
220
  const dataWithoutMarkers = [pixelMagic, ...metaPixel];
226
221
  const dataWithoutMarkersLen = dataWithoutMarkers.reduce((a, b) => a + b.length, 0);
@@ -261,15 +256,7 @@ export async function encodeBinaryToPng(input, opts = {}) {
261
256
  }
262
257
  }
263
258
  }
264
- bufScr = await sharp(rgbBuffer, {
265
- raw: { width, height, channels: 3 },
266
- })
267
- .png({
268
- compressionLevel: 9,
269
- adaptiveFiltering: true,
270
- effort: 9,
271
- })
272
- .toBuffer();
259
+ bufScr = Buffer.from(native.rgbToPng(rgbBuffer, width, height));
273
260
  }
274
261
  else {
275
262
  const bytesPerPixel = 3;
@@ -394,27 +381,10 @@ export async function encodeBinaryToPng(input, opts = {}) {
394
381
  else {
395
382
  const outputFormat = opts.outputFormat || 'png';
396
383
  if (outputFormat === 'webp') {
397
- bufScr = await sharp(raw, {
398
- raw: { width, height, channels: 3 },
399
- })
400
- .webp({
401
- lossless: true,
402
- quality: 100,
403
- effort: 6,
404
- })
405
- .toBuffer();
384
+ throw new Error('WebP output format not supported with native backend');
406
385
  }
407
386
  else {
408
- bufScr = await sharp(raw, {
409
- raw: { width, height, channels: 3 },
410
- })
411
- .png({
412
- compressionLevel: 3,
413
- palette: false,
414
- effort: 1,
415
- adaptiveFiltering: false,
416
- })
417
- .toBuffer();
387
+ bufScr = Buffer.from(native.rgbToPng(raw, width, height));
418
388
  }
419
389
  }
420
390
  }
@@ -426,22 +396,7 @@ export async function encodeBinaryToPng(input, opts = {}) {
426
396
  dataWithoutMarkers.length = 0;
427
397
  if (opts.onProgress)
428
398
  opts.onProgress({ phase: 'png_compress', loaded: 100, total: 100 });
429
- if (opts.skipOptimization || opts.outputFormat === 'webp') {
430
- progressBar?.stop();
431
- return bufScr;
432
- }
433
- if (opts.onProgress)
434
- opts.onProgress({ phase: 'optimizing', loaded: 0, total: 100 });
435
- try {
436
- const optimized = await optimizePngBuffer(bufScr, true);
437
- if (opts.onProgress)
438
- opts.onProgress({ phase: 'optimizing', loaded: 100, total: 100 });
439
- progressBar?.stop();
440
- return optimized;
441
- }
442
- catch (e) {
443
- progressBar?.stop();
444
- return bufScr;
445
- }
399
+ progressBar?.stop();
400
+ return bufScr;
446
401
  }
447
402
  }
@@ -1,5 +1,3 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  export declare function colorsToBytes(colors: Array<{
4
2
  r: number;
5
3
  g: number;
@@ -1,20 +1,18 @@
1
1
  import { createDecipheriv, pbkdf2Sync } from 'crypto';
2
2
  import { ENC_AES, ENC_NONE, ENC_XOR } from './constants.js';
3
3
  import { IncorrectPassphraseError, PassphraseRequiredError } from './errors.js';
4
+ import { native } from './native.js';
4
5
  let nativeDeltaEncode = null;
5
6
  let nativeDeltaDecode = null;
6
7
  let hasNative = false;
7
8
  try {
8
- const native = require('../../libroxify_native.node');
9
9
  if (native?.nativeDeltaEncode && native?.nativeDeltaDecode) {
10
10
  nativeDeltaEncode = native.nativeDeltaEncode;
11
11
  nativeDeltaDecode = native.nativeDeltaDecode;
12
12
  hasNative = true;
13
13
  }
14
14
  }
15
- catch (e) {
16
- // Native module not available, will use TS fallback
17
- }
15
+ catch (e) { }
18
16
  export function colorsToBytes(colors) {
19
17
  const buf = Buffer.alloc(colors.length * 3);
20
18
  for (let i = 0; i < colors.length; i++) {
@@ -1,5 +1,3 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  /**
4
2
  * List files in a Rox PNG archive without decoding the full payload.
5
3
  * Returns the file list if available, otherwise null.
@@ -1,10 +1,9 @@
1
- import extract from 'png-chunks-extract';
2
- import sharp from 'sharp';
3
1
  import * as zlib from 'zlib';
4
2
  import { unpackBuffer } from '../pack.js';
5
3
  import { CHUNK_TYPE, ENC_AES, ENC_XOR, MAGIC, MARKER_COLORS, PIXEL_MAGIC, } from './constants.js';
6
4
  import { decodePngToBinary } from './decoder.js';
7
5
  import { PassphraseRequiredError } from './errors.js';
6
+ import { native } from './native.js';
8
7
  import { cropAndReconstitute } from './reconstitution.js';
9
8
  /**
10
9
  * List files in a Rox PNG archive without decoding the full payload.
@@ -14,7 +13,7 @@ import { cropAndReconstitute } from './reconstitution.js';
14
13
  */
15
14
  export async function listFilesInPng(pngBuf, opts = {}) {
16
15
  try {
17
- const chunks = extract(pngBuf);
16
+ const chunks = native.extractPngChunks(pngBuf);
18
17
  const ihdr = chunks.find((c) => c.name === 'IHDR');
19
18
  const idatChunks = chunks.filter((c) => c.name === 'IDAT');
20
19
  if (ihdr && idatChunks.length > 0) {
@@ -147,15 +146,13 @@ export async function listFilesInPng(pngBuf, opts = {}) {
147
146
  }
148
147
  try {
149
148
  try {
150
- const { data, info } = await sharp(pngBuf)
151
- .ensureAlpha()
152
- .raw()
153
- .toBuffer({ resolveWithObject: true });
154
- const currentWidth = info.width;
155
- const currentHeight = info.height;
149
+ const rawData = native.sharpToRaw(pngBuf);
150
+ const data = rawData.pixels;
151
+ const currentWidth = rawData.width;
152
+ const currentHeight = rawData.height;
156
153
  const rawRGB = Buffer.alloc(currentWidth * currentHeight * 3);
157
154
  for (let i = 0; i < currentWidth * currentHeight; i++) {
158
- rawRGB[i * 3] = data[i * 4];
155
+ rawRGB[i * 3] = data[i * 3];
159
156
  rawRGB[i * 3 + 1] = data[i * 4 + 1];
160
157
  rawRGB[i * 3 + 2] = data[i * 4 + 2];
161
158
  }
@@ -219,17 +216,15 @@ export async function listFilesInPng(pngBuf, opts = {}) {
219
216
  try {
220
217
  const reconstructed = await cropAndReconstitute(pngBuf);
221
218
  try {
222
- const { data, info } = await sharp(reconstructed)
223
- .ensureAlpha()
224
- .raw()
225
- .toBuffer({ resolveWithObject: true });
226
- const currentWidth = info.width;
227
- const currentHeight = info.height;
219
+ const rawData = native.sharpToRaw(reconstructed);
220
+ const data = rawData.pixels;
221
+ const currentWidth = rawData.width;
222
+ const currentHeight = rawData.height;
228
223
  const rawRGB = Buffer.alloc(currentWidth * currentHeight * 3);
229
224
  for (let i = 0; i < currentWidth * currentHeight; i++) {
230
- rawRGB[i * 3] = data[i * 4];
231
- rawRGB[i * 3 + 1] = data[i * 4 + 1];
232
- rawRGB[i * 3 + 2] = data[i * 4 + 2];
225
+ rawRGB[i * 3] = data[i * 3];
226
+ rawRGB[i * 3 + 1] = data[i * 3 + 1];
227
+ rawRGB[i * 3 + 2] = data[i * 3 + 2];
233
228
  }
234
229
  const found = rawRGB.indexOf(PIXEL_MAGIC);
235
230
  if (found !== -1) {
@@ -287,12 +282,10 @@ export async function listFilesInPng(pngBuf, opts = {}) {
287
282
  }
288
283
  catch (e) { }
289
284
  try {
290
- const chunks = extract(reconstructed);
285
+ const chunks = native.extractPngChunks(reconstructed);
291
286
  const fileListChunk = chunks.find((c) => c.name === 'rXFL');
292
287
  if (fileListChunk) {
293
- const data = Buffer.isBuffer(fileListChunk.data)
294
- ? fileListChunk.data
295
- : Buffer.from(fileListChunk.data);
288
+ const data = Buffer.from(fileListChunk.data);
296
289
  const parsedFiles = JSON.parse(data.toString('utf8'));
297
290
  if (parsedFiles.length > 0 &&
298
291
  typeof parsedFiles[0] === 'object' &&
@@ -316,9 +309,7 @@ export async function listFilesInPng(pngBuf, opts = {}) {
316
309
  }
317
310
  const metaChunk = chunks.find((c) => c.name === CHUNK_TYPE);
318
311
  if (metaChunk) {
319
- const dataBuf = Buffer.isBuffer(metaChunk.data)
320
- ? metaChunk.data
321
- : Buffer.from(metaChunk.data);
312
+ const dataBuf = Buffer.from(metaChunk.data);
322
313
  const markerIdx = dataBuf.indexOf(Buffer.from('rXFL'));
323
314
  if (markerIdx !== -1 && markerIdx + 8 <= dataBuf.length) {
324
315
  const jsonLen = dataBuf.readUInt32BE(markerIdx + 4);
@@ -345,12 +336,10 @@ export async function listFilesInPng(pngBuf, opts = {}) {
345
336
  }
346
337
  catch (e) { }
347
338
  try {
348
- const chunks = extract(pngBuf);
339
+ const chunks = native.extractPngChunks(pngBuf);
349
340
  const fileListChunk = chunks.find((c) => c.name === 'rXFL');
350
341
  if (fileListChunk) {
351
- const data = Buffer.isBuffer(fileListChunk.data)
352
- ? fileListChunk.data
353
- : Buffer.from(fileListChunk.data);
342
+ const data = Buffer.from(fileListChunk.data);
354
343
  const parsedFiles = JSON.parse(data.toString('utf8'));
355
344
  if (parsedFiles.length > 0 &&
356
345
  typeof parsedFiles[0] === 'object' &&
@@ -374,9 +363,7 @@ export async function listFilesInPng(pngBuf, opts = {}) {
374
363
  }
375
364
  const metaChunk = chunks.find((c) => c.name === CHUNK_TYPE);
376
365
  if (metaChunk) {
377
- const dataBuf = Buffer.isBuffer(metaChunk.data)
378
- ? metaChunk.data
379
- : Buffer.from(metaChunk.data);
366
+ const dataBuf = Buffer.from(metaChunk.data);
380
367
  const markerIdx = dataBuf.indexOf(Buffer.from('rXFL'));
381
368
  if (markerIdx !== -1 && markerIdx + 8 <= dataBuf.length) {
382
369
  const jsonLen = dataBuf.readUInt32BE(markerIdx + 4);
@@ -442,7 +429,7 @@ export async function hasPassphraseInPng(pngBuf) {
442
429
  return flag === ENC_AES || flag === ENC_XOR;
443
430
  }
444
431
  try {
445
- const chunksRaw = extract(pngBuf);
432
+ const chunksRaw = native.extractPngChunks(pngBuf);
446
433
  const target = chunksRaw.find((c) => c.name === CHUNK_TYPE);
447
434
  if (target) {
448
435
  const data = Buffer.isBuffer(target.data)
@@ -460,12 +447,8 @@ export async function hasPassphraseInPng(pngBuf) {
460
447
  }
461
448
  catch (e) { }
462
449
  try {
463
- const sharpLib = await import('sharp');
464
- const { data } = await sharpLib
465
- .default(pngBuf)
466
- .raw()
467
- .toBuffer({ resolveWithObject: true });
468
- const rawRGB = Buffer.from(data);
450
+ const rawData = native.sharpToRaw(pngBuf);
451
+ const rawRGB = Buffer.from(rawData.pixels);
469
452
  const markerLen = MARKER_COLORS.length * 3;
470
453
  for (let i = 0; i <= rawRGB.length - markerLen; i += 3) {
471
454
  let ok = true;
@@ -0,0 +1 @@
1
+ export declare const native: any;
@@ -0,0 +1,7 @@
1
+ import { createRequire } from 'module';
2
+ import { dirname, join } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = dirname(__filename);
6
+ const require = createRequire(import.meta.url);
7
+ export const native = require(join(__dirname, '../../libroxify_native.node'));
@@ -1,3 +1 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  export declare function optimizePngBuffer(pngBuf: Buffer, fast?: boolean): Promise<Buffer>;
@@ -2,8 +2,6 @@ import { spawn, spawnSync } from 'child_process';
2
2
  import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
3
3
  import { tmpdir } from 'os';
4
4
  import { join } from 'path';
5
- import encode from 'png-chunks-encode';
6
- import extract from 'png-chunks-extract';
7
5
  import * as zlib from 'zlib';
8
6
  import { PNG_HEADER, PNG_HEADER_HEX } from './constants.js';
9
7
  export async function optimizePngBuffer(pngBuf, fast = false) {
@@ -11,6 +9,9 @@ export async function optimizePngBuffer(pngBuf, fast = false) {
11
9
  if (pngBuf.length > MAX_OPTIMIZE_SIZE) {
12
10
  return pngBuf;
13
11
  }
12
+ if (fast) {
13
+ return pngBuf;
14
+ }
14
15
  const runCommandAsync = (cmd, args, timeout = 120000) => {
15
16
  return new Promise((resolve) => {
16
17
  try {
@@ -67,13 +68,12 @@ export async function optimizePngBuffer(pngBuf, fast = false) {
67
68
  }
68
69
  catch (e) { }
69
70
  try {
70
- const chunksRaw = extract(pngBuf);
71
+ const nativeExtract = require('../../libroxify_native.node');
72
+ const chunksRaw = nativeExtract.extractPngChunks(pngBuf);
71
73
  const ihdr = chunksRaw.find((c) => c.name === 'IHDR');
72
74
  if (!ihdr)
73
75
  return pngBuf;
74
- const ihdrData = Buffer.isBuffer(ihdr.data)
75
- ? ihdr.data
76
- : Buffer.from(ihdr.data);
76
+ const ihdrData = Buffer.from(ihdr.data);
77
77
  const width = ihdrData.readUInt32BE(0);
78
78
  const height = ihdrData.readUInt32BE(4);
79
79
  const bitDepth = ihdrData[8];
@@ -81,9 +81,7 @@ export async function optimizePngBuffer(pngBuf, fast = false) {
81
81
  if (bitDepth !== 8 || colorType !== 2)
82
82
  return pngBuf;
83
83
  const idatChunks = chunksRaw.filter((c) => c.name === 'IDAT');
84
- const idatData = Buffer.concat(idatChunks.map((c) => Buffer.isBuffer(c.data)
85
- ? c.data
86
- : Buffer.from(c.data)));
84
+ const idatData = Buffer.concat(idatChunks.map((c) => Buffer.from(c.data)));
87
85
  let raw;
88
86
  try {
89
87
  raw = zlib.inflateSync(idatData);
@@ -204,7 +202,8 @@ export async function optimizePngBuffer(pngBuf, fast = false) {
204
202
  ? buf
205
203
  : Buffer.concat([PNG_HEADER, buf]);
206
204
  }
207
- const out = ensurePng(Buffer.from(encode(newChunks)));
205
+ const nativeEnc = require('../../libroxify_native.node');
206
+ const out = ensurePng(Buffer.from(nativeEnc.encodePngChunks(newChunks)));
208
207
  let bestBuf = out.length < pngBuf.length ? out : pngBuf;
209
208
  const strategies = [
210
209
  zlib.constants.Z_DEFAULT_STRATEGY,
@@ -227,7 +226,8 @@ export async function optimizePngBuffer(pngBuf, fast = false) {
227
226
  const idx = altChunks.findIndex((c) => c.name === 'IDAT');
228
227
  if (idx !== -1)
229
228
  altChunks[idx] = { name: 'IDAT', data: comp };
230
- const candidate = ensurePng(Buffer.from(encode(altChunks)));
229
+ const nativeOptim = require('../../libroxify_native.node');
230
+ const candidate = ensurePng(Buffer.from(nativeOptim.encodePngChunks(altChunks)));
231
231
  if (candidate.length < bestBuf.length)
232
232
  bestBuf = candidate;
233
233
  }
@@ -245,7 +245,8 @@ export async function optimizePngBuffer(pngBuf, fast = false) {
245
245
  const idx = altChunks.findIndex((c) => c.name === 'IDAT');
246
246
  if (idx !== -1)
247
247
  altChunks[idx] = { name: 'IDAT', data: Buffer.from(comp) };
248
- const candidate = ensurePng(Buffer.from(encode(altChunks)));
248
+ const native = require('../../libroxify_native.node');
249
+ const candidate = ensurePng(Buffer.from(native.encodePngChunks(altChunks)));
249
250
  if (candidate.length < bestBuf.length)
250
251
  bestBuf = candidate;
251
252
  }
@@ -303,7 +304,8 @@ export async function optimizePngBuffer(pngBuf, fast = false) {
303
304
  const idx = altChunks.findIndex((c) => c.name === 'IDAT');
304
305
  if (idx !== -1)
305
306
  altChunks[idx] = { name: 'IDAT', data: comp };
306
- const candidate = ensurePng(Buffer.from(encode(altChunks)));
307
+ const native = require('../../libroxify_native.node');
308
+ const candidate = ensurePng(Buffer.from(native.encodePngChunks(altChunks)));
307
309
  if (candidate.length < bestBuf.length)
308
310
  bestBuf = candidate;
309
311
  }
@@ -383,7 +385,8 @@ export async function optimizePngBuffer(pngBuf, fast = false) {
383
385
  const idx = altChunks.findIndex((c) => c.name === 'IDAT');
384
386
  if (idx !== -1)
385
387
  altChunks[idx] = { name: 'IDAT', data: comp };
386
- const candidate = ensurePng(Buffer.from(encode(altChunks)));
388
+ const nativeOptim2 = require('../../libroxify_native.node');
389
+ const candidate = ensurePng(Buffer.from(nativeOptim2.encodePngChunks(altChunks)));
387
390
  if (candidate.length < bestBuf.length)
388
391
  bestBuf = candidate;
389
392
  }
@@ -589,7 +592,8 @@ export async function optimizePngBuffer(pngBuf, fast = false) {
589
592
  data: zlib.deflateSync(idxFilteredAllVar, { level: 9 }),
590
593
  });
591
594
  palChunksVar.push({ name: 'IEND', data: Buffer.alloc(0) });
592
- const palOutVar = ensurePng(Buffer.from(encode(palChunksVar)));
595
+ const native = require('../../libroxify_native.node');
596
+ const palOutVar = ensurePng(Buffer.from(native.encodePngChunks(palChunksVar)));
593
597
  if (palOutVar.length < bestBuf.length)
594
598
  bestBuf = palOutVar;
595
599
  }
@@ -1,3 +1 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  export declare function cropAndReconstitute(input: Buffer, debugDir?: string): Promise<Buffer>;