roxify 1.4.1 → 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 +1 -1
- package/dist/hybrid-compression.d.ts +0 -2
- package/dist/hybrid-compression.js +1 -1
- package/dist/minpng.d.ts +0 -2
- package/dist/minpng.js +45 -12
- package/dist/pack.d.ts +0 -2
- package/dist/roxify-cli +0 -0
- package/dist/stub-progress.d.ts +9 -0
- package/dist/stub-progress.js +9 -0
- package/dist/utils/constants.d.ts +5 -7
- package/dist/utils/crc.d.ts +0 -2
- package/dist/utils/decoder.d.ts +0 -2
- package/dist/utils/decoder.js +71 -122
- package/dist/utils/encoder.d.ts +0 -2
- package/dist/utils/encoder.js +12 -57
- package/dist/utils/helpers.d.ts +0 -2
- package/dist/utils/helpers.js +2 -4
- package/dist/utils/inspection.d.ts +0 -2
- package/dist/utils/inspection.js +23 -40
- package/dist/utils/native.d.ts +1 -0
- package/dist/utils/native.js +7 -0
- package/dist/utils/optimization.d.ts +0 -2
- package/dist/utils/optimization.js +19 -15
- package/dist/utils/reconstitution.d.ts +0 -2
- package/dist/utils/reconstitution.js +1 -264
- package/dist/utils/rust-cli-wrapper.js +2 -7
- package/dist/utils/types.d.ts +0 -2
- package/dist/utils/zstd.d.ts +0 -2
- package/dist/utils/zstd.js +32 -37
- package/libroxify_native.node +0 -0
- package/package.json +21 -23
package/dist/utils/inspection.js
CHANGED
|
@@ -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 =
|
|
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
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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 *
|
|
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
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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 *
|
|
231
|
-
rawRGB[i * 3 + 1] = data[i *
|
|
232
|
-
rawRGB[i * 3 + 2] = data[i *
|
|
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 =
|
|
285
|
+
const chunks = native.extractPngChunks(reconstructed);
|
|
291
286
|
const fileListChunk = chunks.find((c) => c.name === 'rXFL');
|
|
292
287
|
if (fileListChunk) {
|
|
293
|
-
const data = Buffer.
|
|
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.
|
|
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 =
|
|
339
|
+
const chunks = native.extractPngChunks(pngBuf);
|
|
349
340
|
const fileListChunk = chunks.find((c) => c.name === 'rXFL');
|
|
350
341
|
if (fileListChunk) {
|
|
351
|
-
const data = Buffer.
|
|
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.
|
|
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 =
|
|
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
|
|
464
|
-
const
|
|
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'));
|
|
@@ -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
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,266 +1,3 @@
|
|
|
1
|
-
import { join } from 'path';
|
|
2
|
-
import sharp from 'sharp';
|
|
3
1
|
export async function cropAndReconstitute(input, debugDir) {
|
|
4
|
-
|
|
5
|
-
const { data, info } = await sharp(imgInput)
|
|
6
|
-
.ensureAlpha()
|
|
7
|
-
.raw()
|
|
8
|
-
.toBuffer({ resolveWithObject: true });
|
|
9
|
-
return { data, info };
|
|
10
|
-
}
|
|
11
|
-
function idxFor(x, y, width) {
|
|
12
|
-
return (y * width + x) * 4;
|
|
13
|
-
}
|
|
14
|
-
function eqRGB(a, b) {
|
|
15
|
-
return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
|
|
16
|
-
}
|
|
17
|
-
const { info } = await loadRaw(input);
|
|
18
|
-
const doubledBuffer = await sharp(input)
|
|
19
|
-
.resize({
|
|
20
|
-
width: info.width * 2,
|
|
21
|
-
height: info.height * 2,
|
|
22
|
-
kernel: 'nearest',
|
|
23
|
-
})
|
|
24
|
-
.png()
|
|
25
|
-
.toBuffer();
|
|
26
|
-
if (debugDir) {
|
|
27
|
-
await sharp(doubledBuffer).toFile(join(debugDir, 'doubled.png'));
|
|
28
|
-
}
|
|
29
|
-
const { data: doubledData, info: doubledInfo } = await loadRaw(doubledBuffer);
|
|
30
|
-
const w = doubledInfo.width, h = doubledInfo.height;
|
|
31
|
-
const at = (x, y) => {
|
|
32
|
-
const i = idxFor(x, y, w);
|
|
33
|
-
return [
|
|
34
|
-
doubledData[i],
|
|
35
|
-
doubledData[i + 1],
|
|
36
|
-
doubledData[i + 2],
|
|
37
|
-
doubledData[i + 3],
|
|
38
|
-
];
|
|
39
|
-
};
|
|
40
|
-
const findPattern = (startX, startY, dirX, dirY, pattern) => {
|
|
41
|
-
for (let y = startY; y >= 0 && y < h; y += dirY) {
|
|
42
|
-
for (let x = startX; x >= 0 && x < w; x += dirX) {
|
|
43
|
-
const p = at(x, y);
|
|
44
|
-
if (p[0] !== 255 || p[1] !== 0 || p[2] !== 0)
|
|
45
|
-
continue;
|
|
46
|
-
let nx = x + dirX;
|
|
47
|
-
while (nx >= 0 && nx < w && eqRGB(at(nx, y), p))
|
|
48
|
-
nx += dirX;
|
|
49
|
-
if (nx < 0 || nx >= w)
|
|
50
|
-
continue;
|
|
51
|
-
const a = at(nx, y);
|
|
52
|
-
let nx2 = nx + dirX;
|
|
53
|
-
while (nx2 >= 0 && nx2 < w && eqRGB(at(nx2, y), a))
|
|
54
|
-
nx2 += dirX;
|
|
55
|
-
if (nx2 < 0 || nx2 >= w)
|
|
56
|
-
continue;
|
|
57
|
-
const b = at(nx2, y);
|
|
58
|
-
if (a[0] === pattern[0][0] &&
|
|
59
|
-
a[1] === pattern[0][1] &&
|
|
60
|
-
a[2] === pattern[0][2] &&
|
|
61
|
-
b[0] === pattern[1][0] &&
|
|
62
|
-
b[1] === pattern[1][1] &&
|
|
63
|
-
b[2] === pattern[1][2]) {
|
|
64
|
-
return { x, y };
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return null;
|
|
69
|
-
};
|
|
70
|
-
const startPoint = findPattern(0, 0, 1, 1, [
|
|
71
|
-
[0, 255, 0],
|
|
72
|
-
[0, 0, 255],
|
|
73
|
-
]);
|
|
74
|
-
const endPoint = findPattern(w - 1, h - 1, -1, -1, [
|
|
75
|
-
[0, 255, 0],
|
|
76
|
-
[0, 0, 255],
|
|
77
|
-
]);
|
|
78
|
-
if (!startPoint || !endPoint)
|
|
79
|
-
throw new Error('Patterns not found');
|
|
80
|
-
const sx1 = Math.min(startPoint.x, endPoint.x), sy1 = Math.min(startPoint.y, endPoint.y);
|
|
81
|
-
const sx2 = Math.max(startPoint.x, endPoint.x), sy2 = Math.max(startPoint.y, endPoint.y);
|
|
82
|
-
const cropW = sx2 - sx1 + 1, cropH = sy2 - sy1 + 1;
|
|
83
|
-
if (cropW <= 0 || cropH <= 0)
|
|
84
|
-
throw new Error('Invalid crop dimensions');
|
|
85
|
-
const cropped = await sharp(doubledBuffer)
|
|
86
|
-
.extract({ left: sx1, top: sy1, width: cropW, height: cropH })
|
|
87
|
-
.png()
|
|
88
|
-
.toBuffer();
|
|
89
|
-
const { data: cdata, info: cinfo } = await loadRaw(cropped);
|
|
90
|
-
const cw = cinfo.width, ch = cinfo.height;
|
|
91
|
-
const newWidth = cw, newHeight = ch + 1;
|
|
92
|
-
const out = Buffer.alloc(newWidth * newHeight * 4, 0);
|
|
93
|
-
for (let i = 0; i < out.length; i += 4)
|
|
94
|
-
out[i + 3] = 255;
|
|
95
|
-
for (let y = 0; y < ch; y++) {
|
|
96
|
-
for (let x = 0; x < cw; x++) {
|
|
97
|
-
const srcI = (y * cw + x) * 4;
|
|
98
|
-
const dstI = (y * newWidth + x) * 4;
|
|
99
|
-
out[dstI] = cdata[srcI];
|
|
100
|
-
out[dstI + 1] = cdata[srcI + 1];
|
|
101
|
-
out[dstI + 2] = cdata[srcI + 2];
|
|
102
|
-
out[dstI + 3] = cdata[srcI + 3];
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
for (let x = 0; x < newWidth; x++) {
|
|
106
|
-
const i = ((ch - 1) * newWidth + x) * 4;
|
|
107
|
-
out[i] = out[i + 1] = out[i + 2] = 0;
|
|
108
|
-
out[i + 3] = 255;
|
|
109
|
-
const j = (ch * newWidth + x) * 4;
|
|
110
|
-
out[j] = out[j + 1] = out[j + 2] = 0;
|
|
111
|
-
out[j + 3] = 255;
|
|
112
|
-
}
|
|
113
|
-
if (newWidth >= 3) {
|
|
114
|
-
const bgrStart = newWidth - 3;
|
|
115
|
-
const bgr = [
|
|
116
|
-
[0, 0, 255],
|
|
117
|
-
[0, 255, 0],
|
|
118
|
-
[255, 0, 0],
|
|
119
|
-
];
|
|
120
|
-
for (let k = 0; k < 3; k++) {
|
|
121
|
-
const i = (ch * newWidth + bgrStart + k) * 4;
|
|
122
|
-
out[i] = bgr[k][0];
|
|
123
|
-
out[i + 1] = bgr[k][1];
|
|
124
|
-
out[i + 2] = bgr[k][2];
|
|
125
|
-
out[i + 3] = 255;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
const getPixel = (x, y) => {
|
|
129
|
-
const i = (y * newWidth + x) * 4;
|
|
130
|
-
return [out[i], out[i + 1], out[i + 2], out[i + 3]];
|
|
131
|
-
};
|
|
132
|
-
const compressedLines = [];
|
|
133
|
-
for (let y = 0; y < newHeight; y++) {
|
|
134
|
-
const line = [];
|
|
135
|
-
for (let x = 0; x < newWidth; x++)
|
|
136
|
-
line.push(getPixel(x, y));
|
|
137
|
-
const isAllBlack = line.every((p) => p[0] === 0 && p[1] === 0 && p[2] === 0 && p[3] === 255);
|
|
138
|
-
if (!isAllBlack &&
|
|
139
|
-
(compressedLines.length === 0 ||
|
|
140
|
-
!line.every((p, i) => p.every((v, j) => v === compressedLines[compressedLines.length - 1][i][j])))) {
|
|
141
|
-
compressedLines.push(line);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (compressedLines.length === 0) {
|
|
145
|
-
return sharp({
|
|
146
|
-
create: {
|
|
147
|
-
width: 1,
|
|
148
|
-
height: 1,
|
|
149
|
-
channels: 4,
|
|
150
|
-
background: { r: 0, g: 0, b: 0, alpha: 1 },
|
|
151
|
-
},
|
|
152
|
-
})
|
|
153
|
-
.png()
|
|
154
|
-
.toBuffer();
|
|
155
|
-
}
|
|
156
|
-
let finalWidth = newWidth, finalHeight = compressedLines.length;
|
|
157
|
-
let finalOut = Buffer.alloc(finalWidth * finalHeight * 4, 0);
|
|
158
|
-
for (let i = 0; i < finalOut.length; i += 4)
|
|
159
|
-
finalOut[i + 3] = 255;
|
|
160
|
-
for (let y = 0; y < finalHeight; y++) {
|
|
161
|
-
for (let x = 0; x < finalWidth; x++) {
|
|
162
|
-
const i = (y * finalWidth + x) * 4;
|
|
163
|
-
finalOut[i] = compressedLines[y][x][0];
|
|
164
|
-
finalOut[i + 1] = compressedLines[y][x][1];
|
|
165
|
-
finalOut[i + 2] = compressedLines[y][x][2];
|
|
166
|
-
finalOut[i + 3] = compressedLines[y][x][3] || 255;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
if (finalHeight >= 1 && finalWidth >= 3) {
|
|
170
|
-
const lastY = finalHeight - 1;
|
|
171
|
-
for (let k = 0; k < 3; k++) {
|
|
172
|
-
const i = (lastY * finalWidth + finalWidth - 3 + k) * 4;
|
|
173
|
-
finalOut[i] = finalOut[i + 1] = finalOut[i + 2] = 0;
|
|
174
|
-
finalOut[i + 3] = 255;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
if (finalWidth >= 2) {
|
|
178
|
-
const kept = [];
|
|
179
|
-
for (let x = 0; x < finalWidth; x++) {
|
|
180
|
-
if (kept.length === 0) {
|
|
181
|
-
kept.push(x);
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
const prevX = kept[kept.length - 1];
|
|
185
|
-
let same = true;
|
|
186
|
-
for (let y = 0; y < finalHeight; y++) {
|
|
187
|
-
const ia = (y * finalWidth + prevX) * 4, ib = (y * finalWidth + x) * 4;
|
|
188
|
-
if (finalOut[ia] !== finalOut[ib] ||
|
|
189
|
-
finalOut[ia + 1] !== finalOut[ib + 1] ||
|
|
190
|
-
finalOut[ia + 2] !== finalOut[ib + 2] ||
|
|
191
|
-
finalOut[ia + 3] !== finalOut[ib + 3]) {
|
|
192
|
-
same = false;
|
|
193
|
-
break;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if (!same)
|
|
197
|
-
kept.push(x);
|
|
198
|
-
}
|
|
199
|
-
if (kept.length !== finalWidth) {
|
|
200
|
-
const newFinalWidth = kept.length;
|
|
201
|
-
const newOut = Buffer.alloc(newFinalWidth * finalHeight * 4, 0);
|
|
202
|
-
for (let i = 0; i < newOut.length; i += 4)
|
|
203
|
-
newOut[i + 3] = 255;
|
|
204
|
-
for (let nx = 0; nx < kept.length; nx++) {
|
|
205
|
-
const sx = kept[nx];
|
|
206
|
-
for (let y = 0; y < finalHeight; y++) {
|
|
207
|
-
const srcI = (y * finalWidth + sx) * 4, dstI = (y * newFinalWidth + nx) * 4;
|
|
208
|
-
newOut[dstI] = finalOut[srcI];
|
|
209
|
-
newOut[dstI + 1] = finalOut[srcI + 1];
|
|
210
|
-
newOut[dstI + 2] = finalOut[srcI + 2];
|
|
211
|
-
newOut[dstI + 3] = finalOut[srcI + 3];
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
finalOut = newOut;
|
|
215
|
-
finalWidth = newFinalWidth;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (finalHeight >= 2 && finalWidth >= 3) {
|
|
219
|
-
const secondLastY = finalHeight - 2;
|
|
220
|
-
const bgrSeq = [
|
|
221
|
-
[0, 0, 255],
|
|
222
|
-
[0, 255, 0],
|
|
223
|
-
[255, 0, 0],
|
|
224
|
-
];
|
|
225
|
-
let hasBGR = true;
|
|
226
|
-
for (let k = 0; k < 3; k++) {
|
|
227
|
-
const i = (secondLastY * finalWidth + finalWidth - 3 + k) * 4;
|
|
228
|
-
if (finalOut[i] !== bgrSeq[k][0] ||
|
|
229
|
-
finalOut[i + 1] !== bgrSeq[k][1] ||
|
|
230
|
-
finalOut[i + 2] !== bgrSeq[k][2]) {
|
|
231
|
-
hasBGR = false;
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
if (hasBGR) {
|
|
236
|
-
for (let k = 0; k < 3; k++) {
|
|
237
|
-
const i = (secondLastY * finalWidth + finalWidth - 3 + k) * 4;
|
|
238
|
-
finalOut[i] = finalOut[i + 1] = finalOut[i + 2] = 0;
|
|
239
|
-
finalOut[i + 3] = 255;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (finalHeight >= 1 && finalWidth >= 1) {
|
|
244
|
-
const lastYFinal = finalHeight - 1;
|
|
245
|
-
const bgrSeq = [
|
|
246
|
-
[0, 0, 255],
|
|
247
|
-
[0, 255, 0],
|
|
248
|
-
[255, 0, 0],
|
|
249
|
-
];
|
|
250
|
-
for (let k = 0; k < 3; k++) {
|
|
251
|
-
const sx = finalWidth - 3 + k;
|
|
252
|
-
if (sx >= 0) {
|
|
253
|
-
const i = (lastYFinal * finalWidth + sx) * 4;
|
|
254
|
-
finalOut[i] = bgrSeq[k][0];
|
|
255
|
-
finalOut[i + 1] = bgrSeq[k][1];
|
|
256
|
-
finalOut[i + 2] = bgrSeq[k][2];
|
|
257
|
-
finalOut[i + 3] = 255;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
return sharp(finalOut, {
|
|
262
|
-
raw: { width: finalWidth, height: finalHeight, channels: 4 },
|
|
263
|
-
})
|
|
264
|
-
.png()
|
|
265
|
-
.toBuffer();
|
|
2
|
+
return input;
|
|
266
3
|
}
|
|
@@ -62,13 +62,7 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
|
|
|
62
62
|
throw new Error('Rust CLI binary not found. Run: cargo build --release');
|
|
63
63
|
}
|
|
64
64
|
return new Promise((resolve, reject) => {
|
|
65
|
-
const args = [
|
|
66
|
-
'encode',
|
|
67
|
-
inputPath,
|
|
68
|
-
outputPath,
|
|
69
|
-
'--level',
|
|
70
|
-
String(compressionLevel),
|
|
71
|
-
];
|
|
65
|
+
const args = ['encode', '--level', String(compressionLevel)];
|
|
72
66
|
if (name) {
|
|
73
67
|
args.push('--name', name);
|
|
74
68
|
}
|
|
@@ -76,6 +70,7 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
|
|
|
76
70
|
args.push('--passphrase', passphrase);
|
|
77
71
|
args.push('--encrypt', encryptType);
|
|
78
72
|
}
|
|
73
|
+
args.push(inputPath, outputPath);
|
|
79
74
|
const proc = spawn(cliPath, args);
|
|
80
75
|
let stderr = '';
|
|
81
76
|
proc.stderr.on('data', (data) => {
|
package/dist/utils/types.d.ts
CHANGED
package/dist/utils/zstd.d.ts
CHANGED