roxify 1.2.7 → 1.2.9

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,422 +1,391 @@
1
- import cliProgress from 'cli-progress';
2
- import { createCipheriv, pbkdf2Sync, randomBytes } from 'crypto';
3
- import sharp from 'sharp';
4
- import * as zlib from 'zlib';
5
- import { unpackBuffer } from '../pack.js';
6
- import { COMPRESSION_MARKERS, ENC_AES, ENC_NONE, ENC_XOR, MAGIC, MARKER_END, MARKER_START, PIXEL_MAGIC, PNG_HEADER, } from './constants.js';
7
- import { crc32 } from './crc.js';
8
- import { colorsToBytes } from './helpers.js';
9
- import { optimizePngBuffer } from './optimization.js';
10
- import { parallelZstdCompress } from './zstd.js';
11
- export async function encodeBinaryToPng(input, opts = {}) {
12
- let progressBar = null;
13
- 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' });
18
- const startTime = Date.now();
19
- if (!opts.onProgress) {
20
- opts.onProgress = (info) => {
21
- let pct = 0;
22
- if (info.phase === 'compress_progress' && info.loaded && info.total) {
23
- pct = (info.loaded / info.total) * 50;
24
- }
25
- else if (info.phase === 'compress_done') {
26
- pct = 50;
27
- }
28
- else if (info.phase === 'encrypt_done') {
29
- pct = 80;
30
- }
31
- else if (info.phase === 'png_gen') {
32
- pct = 90;
33
- }
34
- else if (info.phase === 'done') {
35
- pct = 100;
36
- }
37
- progressBar.update(Math.floor(pct), {
38
- step: info.phase.replace('_', ' '),
39
- elapsed: String(Math.floor((Date.now() - startTime) / 1000)),
40
- });
41
- };
42
- }
43
- }
44
- let payloadInput;
45
- let totalLen = 0;
46
- if (Array.isArray(input)) {
47
- payloadInput = [MAGIC, ...input];
48
- totalLen = MAGIC.length + input.reduce((a, b) => a + b.length, 0);
49
- }
50
- else {
51
- payloadInput = [MAGIC, input];
52
- totalLen = MAGIC.length + input.length;
53
- }
54
- if (opts.onProgress)
55
- opts.onProgress({ phase: 'compress_start', total: totalLen });
56
- let payload = await parallelZstdCompress(payloadInput, 15, (loaded, total) => {
57
- if (opts.onProgress) {
58
- opts.onProgress({
59
- phase: 'compress_progress',
60
- loaded,
61
- total,
62
- });
63
- }
64
- });
65
- if (opts.onProgress)
66
- opts.onProgress({ phase: 'compress_done', loaded: payload.length });
67
- if (Array.isArray(input)) {
68
- input.length = 0;
69
- }
70
- if (opts.passphrase && !opts.encrypt) {
71
- opts.encrypt = 'aes';
72
- }
73
- if (opts.encrypt === 'auto' && !opts._skipAuto) {
74
- const candidates = ['none', 'xor', 'aes'];
75
- const candidateBufs = [];
76
- for (const c of candidates) {
77
- const testBuf = await encodeBinaryToPng(input, {
78
- ...opts,
79
- encrypt: c,
80
- _skipAuto: true,
81
- });
82
- candidateBufs.push({ enc: c, buf: testBuf });
83
- }
84
- candidateBufs.sort((a, b) => a.buf.length - b.buf.length);
85
- return candidateBufs[0].buf;
86
- }
87
- if (opts.passphrase && opts.encrypt && opts.encrypt !== 'auto') {
88
- const encChoice = opts.encrypt;
89
- if (opts.onProgress)
90
- opts.onProgress({ phase: 'encrypt_start' });
91
- if (encChoice === 'aes') {
92
- const salt = randomBytes(16);
93
- const iv = randomBytes(12);
94
- const PBKDF2_ITERS = 1000000;
95
- const key = pbkdf2Sync(opts.passphrase, salt, PBKDF2_ITERS, 32, 'sha256');
96
- const cipher = createCipheriv('aes-256-gcm', key, iv);
97
- const encParts = [];
98
- for (const chunk of payload) {
99
- encParts.push(cipher.update(chunk));
100
- }
101
- encParts.push(cipher.final());
102
- const tag = cipher.getAuthTag();
103
- payload = [Buffer.from([ENC_AES]), salt, iv, tag, ...encParts];
104
- if (opts.onProgress)
105
- opts.onProgress({ phase: 'encrypt_done' });
106
- }
107
- else if (encChoice === 'xor') {
108
- const xoredParts = [];
109
- let offset = 0;
110
- const keyBuf = Buffer.from(opts.passphrase, 'utf8');
111
- for (const chunk of payload) {
112
- const out = Buffer.alloc(chunk.length);
113
- for (let i = 0; i < chunk.length; i++) {
114
- out[i] = chunk[i] ^ keyBuf[(offset + i) % keyBuf.length];
115
- }
116
- offset += chunk.length;
117
- xoredParts.push(out);
118
- }
119
- payload = [Buffer.from([ENC_XOR]), ...xoredParts];
120
- if (opts.onProgress)
121
- opts.onProgress({ phase: 'encrypt_done' });
122
- }
123
- else if (encChoice === 'none') {
124
- payload = [Buffer.from([ENC_NONE]), ...payload];
125
- if (opts.onProgress)
126
- opts.onProgress({ phase: 'encrypt_done' });
127
- }
128
- }
129
- else {
130
- payload = [Buffer.from([ENC_NONE]), ...payload];
131
- }
132
- const payloadTotalLen = payload.reduce((a, b) => a + b.length, 0);
133
- if (opts.onProgress)
134
- opts.onProgress({ phase: 'meta_prep_done', loaded: payloadTotalLen });
135
- const metaParts = [];
136
- const includeName = opts.includeName === undefined ? true : !!opts.includeName;
137
- if (includeName && opts.name) {
138
- const nameBuf = Buffer.from(opts.name, 'utf8');
139
- metaParts.push(Buffer.from([nameBuf.length]));
140
- metaParts.push(nameBuf);
141
- }
142
- else {
143
- metaParts.push(Buffer.from([0]));
144
- }
145
- let meta = [...metaParts, ...payload];
146
- if (opts.includeFileList && opts.fileList) {
147
- let sizeMap = null;
148
- if (!Array.isArray(input)) {
149
- try {
150
- const unpack = unpackBuffer(input);
151
- if (unpack) {
152
- sizeMap = {};
153
- for (const ef of unpack.files)
154
- sizeMap[ef.path] = ef.buf.length;
155
- }
156
- }
157
- catch (e) { }
158
- }
159
- const normalized = opts.fileList.map((f) => {
160
- if (typeof f === 'string')
161
- return { name: f, size: sizeMap && sizeMap[f] ? sizeMap[f] : 0 };
162
- if (f && typeof f === 'object') {
163
- if (f.name)
164
- return { name: f.name, size: f.size ?? 0 };
165
- if (f.path)
166
- return { name: f.path, size: f.size ?? 0 };
167
- }
168
- return { name: String(f), size: 0 };
169
- });
170
- const jsonBuf = Buffer.from(JSON.stringify(normalized), 'utf8');
171
- const lenBuf = Buffer.alloc(4);
172
- lenBuf.writeUInt32BE(jsonBuf.length, 0);
173
- meta = [...meta, Buffer.from('rXFL', 'utf8'), lenBuf, jsonBuf];
174
- }
175
- if (opts.output === 'rox') {
176
- return Buffer.concat([MAGIC, ...meta]);
177
- }
178
- {
179
- const nameBuf = opts.name
180
- ? Buffer.from(opts.name, 'utf8')
181
- : Buffer.alloc(0);
182
- const nameLen = nameBuf.length;
183
- const payloadLenBuf = Buffer.alloc(4);
184
- payloadLenBuf.writeUInt32BE(payloadTotalLen, 0);
185
- const version = 1;
186
- let metaPixel = [
187
- Buffer.from([version]),
188
- Buffer.from([nameLen]),
189
- nameBuf,
190
- payloadLenBuf,
191
- ...payload,
192
- ];
193
- if (opts.includeFileList && opts.fileList) {
194
- let sizeMap2 = null;
195
- if (!Array.isArray(input)) {
196
- try {
197
- const unpack = unpackBuffer(input);
198
- if (unpack) {
199
- sizeMap2 = {};
200
- for (const ef of unpack.files)
201
- sizeMap2[ef.path] = ef.buf.length;
202
- }
203
- }
204
- catch (e) { }
205
- }
206
- const normalized = opts.fileList.map((f) => {
207
- if (typeof f === 'string')
208
- return { name: f, size: sizeMap2 && sizeMap2[f] ? sizeMap2[f] : 0 };
209
- if (f && typeof f === 'object') {
210
- if (f.name)
211
- return { name: f.name, size: f.size ?? 0 };
212
- if (f.path)
213
- return { name: f.path, size: f.size ?? 0 };
214
- }
215
- return { name: String(f), size: 0 };
216
- });
217
- const jsonBuf = Buffer.from(JSON.stringify(normalized), 'utf8');
218
- const lenBuf = Buffer.alloc(4);
219
- lenBuf.writeUInt32BE(jsonBuf.length, 0);
220
- metaPixel = [...metaPixel, Buffer.from('rXFL', 'utf8'), lenBuf, jsonBuf];
221
- }
222
- const dataWithoutMarkers = [PIXEL_MAGIC, ...metaPixel];
223
- const dataWithoutMarkersLen = dataWithoutMarkers.reduce((a, b) => a + b.length, 0);
224
- const padding = (3 - (dataWithoutMarkersLen % 3)) % 3;
225
- const paddedData = padding > 0
226
- ? [...dataWithoutMarkers, Buffer.alloc(padding)]
227
- : dataWithoutMarkers;
228
- const markerStartBytes = colorsToBytes(MARKER_START);
229
- const compressionMarkerBytes = colorsToBytes(COMPRESSION_MARKERS.zstd);
230
- const dataWithMarkers = [
231
- markerStartBytes,
232
- compressionMarkerBytes,
233
- ...paddedData,
234
- ];
235
- const bytesPerPixel = 3;
236
- const dataWithMarkersLen = dataWithMarkers.reduce((a, b) => a + b.length, 0);
237
- const dataPixels = Math.ceil(dataWithMarkersLen / 3);
238
- const totalPixels = dataPixels + MARKER_END.length;
239
- const maxWidth = 16384;
240
- let side = Math.ceil(Math.sqrt(totalPixels));
241
- if (side < MARKER_END.length)
242
- side = MARKER_END.length;
243
- let logicalWidth;
244
- let logicalHeight;
245
- if (side <= maxWidth) {
246
- logicalWidth = side;
247
- logicalHeight = side;
248
- }
249
- else {
250
- logicalWidth = Math.min(maxWidth, totalPixels);
251
- logicalHeight = Math.ceil(totalPixels / logicalWidth);
252
- }
253
- const scale = 1;
254
- const width = logicalWidth * scale;
255
- const height = logicalHeight * scale;
256
- const LARGE_IMAGE_PIXELS = 50000000;
257
- const useManualPng = width * height > LARGE_IMAGE_PIXELS || !!process.env.ROX_FAST_PNG;
258
- let raw;
259
- let stride = 0;
260
- if (useManualPng) {
261
- stride = width * 3 + 1;
262
- raw = Buffer.alloc(height * stride);
263
- }
264
- else {
265
- raw = Buffer.alloc(width * height * bytesPerPixel);
266
- }
267
- let currentBufIdx = 0;
268
- let currentBufOffset = 0;
269
- const getNextByte = () => {
270
- while (currentBufIdx < dataWithMarkers.length) {
271
- const buf = dataWithMarkers[currentBufIdx];
272
- if (currentBufOffset < buf.length) {
273
- return buf[currentBufOffset++];
274
- }
275
- currentBufIdx++;
276
- currentBufOffset = 0;
277
- }
278
- return 0;
279
- };
280
- for (let ly = 0; ly < logicalHeight; ly++) {
281
- if (useManualPng) {
282
- for (let sy = 0; sy < scale; sy++) {
283
- const py = ly * scale + sy;
284
- raw[py * stride] = 0;
285
- }
286
- }
287
- for (let lx = 0; lx < logicalWidth; lx++) {
288
- const linearIdx = ly * logicalWidth + lx;
289
- let r = 0, g = 0, b = 0;
290
- if (ly === logicalHeight - 1 &&
291
- lx >= logicalWidth - MARKER_END.length) {
292
- const markerIdx = lx - (logicalWidth - MARKER_END.length);
293
- r = MARKER_END[markerIdx].r;
294
- g = MARKER_END[markerIdx].g;
295
- b = MARKER_END[markerIdx].b;
296
- }
297
- else if (linearIdx < dataPixels) {
298
- r = getNextByte();
299
- g = getNextByte();
300
- b = getNextByte();
301
- }
302
- for (let sy = 0; sy < scale; sy++) {
303
- for (let sx = 0; sx < scale; sx++) {
304
- const px = lx * scale + sx;
305
- const py = ly * scale + sy;
306
- if (useManualPng) {
307
- const dstIdx = py * stride + 1 + px * 3;
308
- raw[dstIdx] = r;
309
- raw[dstIdx + 1] = g;
310
- raw[dstIdx + 2] = b;
311
- }
312
- else {
313
- const dstIdx = (py * width + px) * 3;
314
- raw[dstIdx] = r;
315
- raw[dstIdx + 1] = g;
316
- raw[dstIdx + 2] = b;
317
- }
318
- }
319
- }
320
- }
321
- }
322
- payload.length = 0;
323
- dataWithMarkers.length = 0;
324
- metaPixel.length = 0;
325
- meta.length = 0;
326
- paddedData.length = 0;
327
- dataWithoutMarkers.length = 0;
328
- if (opts.onProgress)
329
- opts.onProgress({ phase: 'png_gen', loaded: 0, total: height });
330
- let bufScr;
331
- if (useManualPng) {
332
- const bytesPerRow = width * 3;
333
- const scanlinesData = Buffer.alloc(height * (1 + bytesPerRow));
334
- const progressStep = Math.max(1, Math.floor(height / 20));
335
- for (let row = 0; row < height; row++) {
336
- scanlinesData[row * (1 + bytesPerRow)] = 0;
337
- const srcStart = row * stride + 1;
338
- const dstStart = row * (1 + bytesPerRow) + 1;
339
- raw.copy(scanlinesData, dstStart, srcStart, srcStart + bytesPerRow);
340
- if (opts.onProgress && row % progressStep === 0) {
341
- opts.onProgress({ phase: 'png_gen', loaded: row, total: height });
342
- }
343
- }
344
- if (opts.onProgress)
345
- opts.onProgress({ phase: 'png_compress', loaded: 0, total: 100 });
346
- const idatData = zlib.deflateSync(scanlinesData, {
347
- level: 3,
348
- memLevel: 8,
349
- strategy: zlib.constants.Z_DEFAULT_STRATEGY,
350
- });
351
- raw = Buffer.alloc(0);
352
- const ihdrData = Buffer.alloc(13);
353
- ihdrData.writeUInt32BE(width, 0);
354
- ihdrData.writeUInt32BE(height, 4);
355
- ihdrData[8] = 8;
356
- ihdrData[9] = 2;
357
- ihdrData[10] = 0;
358
- ihdrData[11] = 0;
359
- ihdrData[12] = 0;
360
- const ihdrType = Buffer.from('IHDR', 'utf8');
361
- const ihdrCrc = crc32(ihdrData, crc32(ihdrType));
362
- const ihdrCrcBuf = Buffer.alloc(4);
363
- ihdrCrcBuf.writeUInt32BE(ihdrCrc, 0);
364
- const ihdrLen = Buffer.alloc(4);
365
- ihdrLen.writeUInt32BE(ihdrData.length, 0);
366
- const idatType = Buffer.from('IDAT', 'utf8');
367
- const idatCrc = crc32(idatData, crc32(idatType));
368
- const idatCrcBuf = Buffer.alloc(4);
369
- idatCrcBuf.writeUInt32BE(idatCrc, 0);
370
- const idatLen = Buffer.alloc(4);
371
- idatLen.writeUInt32BE(idatData.length, 0);
372
- const iendType = Buffer.from('IEND', 'utf8');
373
- const iendCrc = crc32(Buffer.alloc(0), crc32(iendType));
374
- const iendCrcBuf = Buffer.alloc(4);
375
- iendCrcBuf.writeUInt32BE(iendCrc, 0);
376
- const iendLen = Buffer.alloc(4);
377
- iendLen.writeUInt32BE(0, 0);
378
- bufScr = Buffer.concat([
379
- PNG_HEADER,
380
- ihdrLen,
381
- ihdrType,
382
- ihdrData,
383
- ihdrCrcBuf,
384
- idatLen,
385
- idatType,
386
- idatData,
387
- idatCrcBuf,
388
- iendLen,
389
- iendType,
390
- iendCrcBuf,
391
- ]);
392
- }
393
- else {
394
- bufScr = await sharp(raw, {
395
- raw: { width, height, channels: 3 },
396
- })
397
- .png({
398
- compressionLevel: 3,
399
- palette: false,
400
- effort: 1,
401
- adaptiveFiltering: false,
402
- })
403
- .toBuffer();
404
- }
405
- raw = Buffer.alloc(0);
406
- if (opts.onProgress)
407
- opts.onProgress({ phase: 'png_compress', loaded: 100, total: 100 });
408
- if (opts.onProgress)
409
- opts.onProgress({ phase: 'optimizing', loaded: 0, total: 100 });
410
- try {
411
- const optimized = await optimizePngBuffer(bufScr, true);
412
- if (opts.onProgress)
413
- opts.onProgress({ phase: 'optimizing', loaded: 100, total: 100 });
414
- progressBar?.stop();
415
- return optimized;
416
- }
417
- catch (e) {
418
- progressBar?.stop();
419
- return bufScr;
420
- }
421
- }
422
- }
1
+ import cliProgress from 'cli-progress';
2
+ import { createCipheriv, pbkdf2Sync, randomBytes } from 'crypto';
3
+ import sharp from 'sharp';
4
+ import * as zlib from 'zlib';
5
+ import { unpackBuffer } from '../pack.js';
6
+ import { COMPRESSION_MARKERS, ENC_AES, ENC_NONE, ENC_XOR, MAGIC, MARKER_END, MARKER_START, PIXEL_MAGIC, PNG_HEADER, } from './constants.js';
7
+ import { crc32 } from './crc.js';
8
+ import { colorsToBytes } from './helpers.js';
9
+ import { optimizePngBuffer } from './optimization.js';
10
+ import { parallelZstdCompress } from './zstd.js';
11
+ export async function encodeBinaryToPng(input, opts = {}) {
12
+ let progressBar = null;
13
+ 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' });
18
+ const startTime = Date.now();
19
+ if (!opts.onProgress) {
20
+ opts.onProgress = (info) => {
21
+ let pct = 0;
22
+ if (info.phase === 'compress_progress' && info.loaded && info.total) {
23
+ pct = (info.loaded / info.total) * 50;
24
+ }
25
+ else if (info.phase === 'compress_done') {
26
+ pct = 50;
27
+ }
28
+ else if (info.phase === 'encrypt_done') {
29
+ pct = 80;
30
+ }
31
+ else if (info.phase === 'png_gen') {
32
+ pct = 90;
33
+ }
34
+ else if (info.phase === 'done') {
35
+ pct = 100;
36
+ }
37
+ progressBar.update(Math.floor(pct), {
38
+ step: info.phase.replace('_', ' '),
39
+ elapsed: String(Math.floor((Date.now() - startTime) / 1000)),
40
+ });
41
+ };
42
+ }
43
+ }
44
+ let payloadInput;
45
+ let totalLen = 0;
46
+ if (Array.isArray(input)) {
47
+ payloadInput = [MAGIC, ...input];
48
+ totalLen = MAGIC.length + input.reduce((a, b) => a + b.length, 0);
49
+ }
50
+ else {
51
+ payloadInput = [MAGIC, input];
52
+ totalLen = MAGIC.length + input.length;
53
+ }
54
+ if (opts.onProgress)
55
+ opts.onProgress({ phase: 'compress_start', total: totalLen });
56
+ const compressionLevel = opts.compressionLevel ?? 3;
57
+ let payload = await parallelZstdCompress(payloadInput, compressionLevel, (loaded, total) => {
58
+ if (opts.onProgress) {
59
+ opts.onProgress({
60
+ phase: 'compress_progress',
61
+ loaded,
62
+ total,
63
+ });
64
+ }
65
+ });
66
+ if (opts.onProgress)
67
+ opts.onProgress({ phase: 'compress_done', loaded: payload.length });
68
+ if (Array.isArray(input)) {
69
+ input.length = 0;
70
+ }
71
+ if (opts.passphrase && !opts.encrypt) {
72
+ opts.encrypt = 'aes';
73
+ }
74
+ if (opts.encrypt === 'auto' && !opts._skipAuto) {
75
+ const candidates = ['none', 'xor', 'aes'];
76
+ const candidateBufs = [];
77
+ for (const c of candidates) {
78
+ const testBuf = await encodeBinaryToPng(input, {
79
+ ...opts,
80
+ encrypt: c,
81
+ _skipAuto: true,
82
+ });
83
+ candidateBufs.push({ enc: c, buf: testBuf });
84
+ }
85
+ candidateBufs.sort((a, b) => a.buf.length - b.buf.length);
86
+ return candidateBufs[0].buf;
87
+ }
88
+ if (opts.passphrase && opts.encrypt && opts.encrypt !== 'auto') {
89
+ const encChoice = opts.encrypt;
90
+ if (opts.onProgress)
91
+ opts.onProgress({ phase: 'encrypt_start' });
92
+ if (encChoice === 'aes') {
93
+ const salt = randomBytes(16);
94
+ const iv = randomBytes(12);
95
+ const PBKDF2_ITERS = 1000000;
96
+ const key = pbkdf2Sync(opts.passphrase, salt, PBKDF2_ITERS, 32, 'sha256');
97
+ const cipher = createCipheriv('aes-256-gcm', key, iv);
98
+ const encParts = [];
99
+ for (const chunk of payload) {
100
+ encParts.push(cipher.update(chunk));
101
+ }
102
+ encParts.push(cipher.final());
103
+ const tag = cipher.getAuthTag();
104
+ payload = [Buffer.from([ENC_AES]), salt, iv, tag, ...encParts];
105
+ if (opts.onProgress)
106
+ opts.onProgress({ phase: 'encrypt_done' });
107
+ }
108
+ else if (encChoice === 'xor') {
109
+ const xoredParts = [];
110
+ let offset = 0;
111
+ const keyBuf = Buffer.from(opts.passphrase, 'utf8');
112
+ for (const chunk of payload) {
113
+ const out = Buffer.alloc(chunk.length);
114
+ for (let i = 0; i < chunk.length; i++) {
115
+ out[i] = chunk[i] ^ keyBuf[(offset + i) % keyBuf.length];
116
+ }
117
+ offset += chunk.length;
118
+ xoredParts.push(out);
119
+ }
120
+ payload = [Buffer.from([ENC_XOR]), ...xoredParts];
121
+ if (opts.onProgress)
122
+ opts.onProgress({ phase: 'encrypt_done' });
123
+ }
124
+ else if (encChoice === 'none') {
125
+ payload = [Buffer.from([ENC_NONE]), ...payload];
126
+ if (opts.onProgress)
127
+ opts.onProgress({ phase: 'encrypt_done' });
128
+ }
129
+ }
130
+ else {
131
+ payload = [Buffer.from([ENC_NONE]), ...payload];
132
+ }
133
+ const payloadTotalLen = payload.reduce((a, b) => a + b.length, 0);
134
+ if (opts.onProgress)
135
+ opts.onProgress({ phase: 'meta_prep_done', loaded: payloadTotalLen });
136
+ const metaParts = [];
137
+ const includeName = opts.includeName === undefined ? true : !!opts.includeName;
138
+ if (includeName && opts.name) {
139
+ const nameBuf = Buffer.from(opts.name, 'utf8');
140
+ metaParts.push(Buffer.from([nameBuf.length]));
141
+ metaParts.push(nameBuf);
142
+ }
143
+ else {
144
+ metaParts.push(Buffer.from([0]));
145
+ }
146
+ let meta = [...metaParts, ...payload];
147
+ if (opts.includeFileList && opts.fileList) {
148
+ let sizeMap = null;
149
+ if (!Array.isArray(input)) {
150
+ try {
151
+ const unpack = unpackBuffer(input);
152
+ if (unpack) {
153
+ sizeMap = {};
154
+ for (const ef of unpack.files)
155
+ sizeMap[ef.path] = ef.buf.length;
156
+ }
157
+ }
158
+ catch (e) { }
159
+ }
160
+ const normalized = opts.fileList.map((f) => {
161
+ if (typeof f === 'string')
162
+ return { name: f, size: sizeMap && sizeMap[f] ? sizeMap[f] : 0 };
163
+ if (f && typeof f === 'object') {
164
+ if (f.name)
165
+ return { name: f.name, size: f.size ?? 0 };
166
+ if (f.path)
167
+ return { name: f.path, size: f.size ?? 0 };
168
+ }
169
+ return { name: String(f), size: 0 };
170
+ });
171
+ const jsonBuf = Buffer.from(JSON.stringify(normalized), 'utf8');
172
+ const lenBuf = Buffer.alloc(4);
173
+ lenBuf.writeUInt32BE(jsonBuf.length, 0);
174
+ meta = [...meta, Buffer.from('rXFL', 'utf8'), lenBuf, jsonBuf];
175
+ }
176
+ if (opts.output === 'rox') {
177
+ return Buffer.concat([MAGIC, ...meta]);
178
+ }
179
+ {
180
+ const nameBuf = opts.name
181
+ ? Buffer.from(opts.name, 'utf8')
182
+ : Buffer.alloc(0);
183
+ const nameLen = nameBuf.length;
184
+ const payloadLenBuf = Buffer.alloc(4);
185
+ payloadLenBuf.writeUInt32BE(payloadTotalLen, 0);
186
+ const version = 1;
187
+ let metaPixel = [
188
+ Buffer.from([version]),
189
+ Buffer.from([nameLen]),
190
+ nameBuf,
191
+ payloadLenBuf,
192
+ ...payload,
193
+ ];
194
+ if (opts.includeFileList && opts.fileList) {
195
+ let sizeMap2 = null;
196
+ if (!Array.isArray(input)) {
197
+ try {
198
+ const unpack = unpackBuffer(input);
199
+ if (unpack) {
200
+ sizeMap2 = {};
201
+ for (const ef of unpack.files)
202
+ sizeMap2[ef.path] = ef.buf.length;
203
+ }
204
+ }
205
+ catch (e) { }
206
+ }
207
+ const normalized = opts.fileList.map((f) => {
208
+ if (typeof f === 'string')
209
+ return { name: f, size: sizeMap2 && sizeMap2[f] ? sizeMap2[f] : 0 };
210
+ if (f && typeof f === 'object') {
211
+ if (f.name)
212
+ return { name: f.name, size: f.size ?? 0 };
213
+ if (f.path)
214
+ return { name: f.path, size: f.size ?? 0 };
215
+ }
216
+ return { name: String(f), size: 0 };
217
+ });
218
+ const jsonBuf = Buffer.from(JSON.stringify(normalized), 'utf8');
219
+ const lenBuf = Buffer.alloc(4);
220
+ lenBuf.writeUInt32BE(jsonBuf.length, 0);
221
+ metaPixel = [...metaPixel, Buffer.from('rXFL', 'utf8'), lenBuf, jsonBuf];
222
+ }
223
+ const dataWithoutMarkers = [PIXEL_MAGIC, ...metaPixel];
224
+ const dataWithoutMarkersLen = dataWithoutMarkers.reduce((a, b) => a + b.length, 0);
225
+ const padding = (3 - (dataWithoutMarkersLen % 3)) % 3;
226
+ const paddedData = padding > 0
227
+ ? [...dataWithoutMarkers, Buffer.alloc(padding)]
228
+ : dataWithoutMarkers;
229
+ const markerStartBytes = colorsToBytes(MARKER_START);
230
+ const compressionMarkerBytes = colorsToBytes(COMPRESSION_MARKERS.zstd);
231
+ const dataWithMarkers = [
232
+ markerStartBytes,
233
+ compressionMarkerBytes,
234
+ ...paddedData,
235
+ ];
236
+ const bytesPerPixel = 3;
237
+ const dataWithMarkersLen = dataWithMarkers.reduce((a, b) => a + b.length, 0);
238
+ const dataPixels = Math.ceil(dataWithMarkersLen / 3);
239
+ const totalPixels = dataPixels + MARKER_END.length;
240
+ const maxWidth = 16384;
241
+ let side = Math.ceil(Math.sqrt(totalPixels));
242
+ if (side < MARKER_END.length)
243
+ side = MARKER_END.length;
244
+ let logicalWidth;
245
+ let logicalHeight;
246
+ if (side <= maxWidth) {
247
+ logicalWidth = side;
248
+ logicalHeight = side;
249
+ }
250
+ else {
251
+ logicalWidth = Math.min(maxWidth, totalPixels);
252
+ logicalHeight = Math.ceil(totalPixels / logicalWidth);
253
+ }
254
+ const scale = 1;
255
+ const width = logicalWidth * scale;
256
+ const height = logicalHeight * scale;
257
+ const LARGE_IMAGE_PIXELS = 10000000;
258
+ const useManualPng = width * height > LARGE_IMAGE_PIXELS || !!process.env.ROX_FAST_PNG;
259
+ let raw;
260
+ let stride = 0;
261
+ if (useManualPng) {
262
+ stride = width * 3 + 1;
263
+ raw = Buffer.alloc(height * stride);
264
+ const flatData = Buffer.concat(dataWithMarkers);
265
+ const markerEndBytes = Buffer.alloc(MARKER_END.length * 3);
266
+ for (let i = 0; i < MARKER_END.length; i++) {
267
+ markerEndBytes[i * 3] = MARKER_END[i].r;
268
+ markerEndBytes[i * 3 + 1] = MARKER_END[i].g;
269
+ markerEndBytes[i * 3 + 2] = MARKER_END[i].b;
270
+ }
271
+ const totalDataBytes = logicalWidth * logicalHeight * 3;
272
+ const fullData = Buffer.alloc(totalDataBytes);
273
+ const markerStartPos = (logicalHeight - 1) * logicalWidth * 3 +
274
+ (logicalWidth - MARKER_END.length) * 3;
275
+ flatData.copy(fullData, 0, 0, Math.min(flatData.length, markerStartPos));
276
+ markerEndBytes.copy(fullData, markerStartPos);
277
+ for (let row = 0; row < height; row++) {
278
+ raw[row * stride] = 0;
279
+ fullData.copy(raw, row * stride + 1, row * width * 3, (row + 1) * width * 3);
280
+ }
281
+ }
282
+ else {
283
+ raw = Buffer.alloc(width * height * bytesPerPixel);
284
+ const flatData = Buffer.concat(dataWithMarkers);
285
+ flatData.copy(raw, 0, 0, Math.min(flatData.length, raw.length));
286
+ }
287
+ payload.length = 0;
288
+ dataWithMarkers.length = 0;
289
+ metaPixel.length = 0;
290
+ meta.length = 0;
291
+ paddedData.length = 0;
292
+ dataWithoutMarkers.length = 0;
293
+ if (opts.onProgress)
294
+ opts.onProgress({ phase: 'png_gen', loaded: 0, total: height });
295
+ let bufScr;
296
+ if (useManualPng) {
297
+ const bytesPerRow = width * 3;
298
+ const scanlinesData = Buffer.alloc(height * (1 + bytesPerRow));
299
+ const progressStep = Math.max(1, Math.floor(height / 20));
300
+ for (let row = 0; row < height; row++) {
301
+ scanlinesData[row * (1 + bytesPerRow)] = 0;
302
+ const srcStart = row * stride + 1;
303
+ const dstStart = row * (1 + bytesPerRow) + 1;
304
+ raw.copy(scanlinesData, dstStart, srcStart, srcStart + bytesPerRow);
305
+ if (opts.onProgress && row % progressStep === 0) {
306
+ opts.onProgress({ phase: 'png_gen', loaded: row, total: height });
307
+ }
308
+ }
309
+ if (opts.onProgress)
310
+ opts.onProgress({ phase: 'png_compress', loaded: 0, total: 100 });
311
+ const idatData = zlib.deflateSync(scanlinesData, {
312
+ level: 0,
313
+ memLevel: 8,
314
+ strategy: zlib.constants.Z_FILTERED,
315
+ });
316
+ raw = Buffer.alloc(0);
317
+ const ihdrData = Buffer.alloc(13);
318
+ ihdrData.writeUInt32BE(width, 0);
319
+ ihdrData.writeUInt32BE(height, 4);
320
+ ihdrData[8] = 8;
321
+ ihdrData[9] = 2;
322
+ ihdrData[10] = 0;
323
+ ihdrData[11] = 0;
324
+ ihdrData[12] = 0;
325
+ const ihdrType = Buffer.from('IHDR', 'utf8');
326
+ const ihdrCrc = crc32(ihdrData, crc32(ihdrType));
327
+ const ihdrCrcBuf = Buffer.alloc(4);
328
+ ihdrCrcBuf.writeUInt32BE(ihdrCrc, 0);
329
+ const ihdrLen = Buffer.alloc(4);
330
+ ihdrLen.writeUInt32BE(ihdrData.length, 0);
331
+ const idatType = Buffer.from('IDAT', 'utf8');
332
+ const idatCrc = crc32(idatData, crc32(idatType));
333
+ const idatCrcBuf = Buffer.alloc(4);
334
+ idatCrcBuf.writeUInt32BE(idatCrc, 0);
335
+ const idatLen = Buffer.alloc(4);
336
+ idatLen.writeUInt32BE(idatData.length, 0);
337
+ const iendType = Buffer.from('IEND', 'utf8');
338
+ const iendCrc = crc32(Buffer.alloc(0), crc32(iendType));
339
+ const iendCrcBuf = Buffer.alloc(4);
340
+ iendCrcBuf.writeUInt32BE(iendCrc, 0);
341
+ const iendLen = Buffer.alloc(4);
342
+ iendLen.writeUInt32BE(0, 0);
343
+ bufScr = Buffer.concat([
344
+ PNG_HEADER,
345
+ ihdrLen,
346
+ ihdrType,
347
+ ihdrData,
348
+ ihdrCrcBuf,
349
+ idatLen,
350
+ idatType,
351
+ idatData,
352
+ idatCrcBuf,
353
+ iendLen,
354
+ iendType,
355
+ iendCrcBuf,
356
+ ]);
357
+ }
358
+ else {
359
+ bufScr = await sharp(raw, {
360
+ raw: { width, height, channels: 3 },
361
+ })
362
+ .png({
363
+ compressionLevel: 3,
364
+ palette: false,
365
+ effort: 1,
366
+ adaptiveFiltering: false,
367
+ })
368
+ .toBuffer();
369
+ }
370
+ raw = Buffer.alloc(0);
371
+ if (opts.onProgress)
372
+ opts.onProgress({ phase: 'png_compress', loaded: 100, total: 100 });
373
+ if (opts.skipOptimization) {
374
+ progressBar?.stop();
375
+ return bufScr;
376
+ }
377
+ if (opts.onProgress)
378
+ opts.onProgress({ phase: 'optimizing', loaded: 0, total: 100 });
379
+ try {
380
+ const optimized = await optimizePngBuffer(bufScr, true);
381
+ if (opts.onProgress)
382
+ opts.onProgress({ phase: 'optimizing', loaded: 100, total: 100 });
383
+ progressBar?.stop();
384
+ return optimized;
385
+ }
386
+ catch (e) {
387
+ progressBar?.stop();
388
+ return bufScr;
389
+ }
390
+ }
391
+ }