roxify 1.0.0 → 1.1.1

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/README.md CHANGED
@@ -1,368 +1,97 @@
1
- # RoxCompressor Transform
2
-
3
- > Encode binary data into PNG images and decode them back. Fast, efficient, with optional encryption.
1
+ # roxify
4
2
 
5
3
  [![npm version](https://img.shields.io/npm/v/roxify.svg)](https://www.npmjs.com/package/roxify)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
-
8
- ## Features
9
4
 
10
- - 🚀 **Fast**: Optimized Brotli compression (quality 1 by default) encode 3.8 MB in ~1 second
11
- - 🔒 **Secure**: AES-256-GCM encryption support with PBKDF2 key derivation
12
- - 🎨 **Multiple modes**: Compact, chunk, pixel, and screenshot modes
13
- - 📦 **CLI & API**: Use as command-line tool or JavaScript library
14
- - 🔄 **Lossless**: Perfect roundtrip encoding/decoding
15
- - 📊 **Efficient**: Typically 20-30% of original size with compression
16
- - 📖 **Full TSDoc**: Complete TypeScript documentation
5
+ Encode binary data into PNG images and decode them back. Supports CLI and programmatic API (Node.js ESM).
17
6
 
18
- ## Documentation
7
+ Roxify is a compact, color-based alternative to QR codes, designed specifically for digital-only use (not for printing). It encodes data using color channels (rather than monochrome patterns) for higher density, and is optimized for decoding from approximate screenshots — including nearest-neighbour resize/stretch and solid or gradient backgrounds. It is not intended for printed media and is not resilient to lossy compression or heavy image filtering.
19
8
 
20
- - 📘 **[CLI Documentation](./CLI.md)** - Complete command-line usage guide
21
- - 📗 **[JavaScript SDK](./JAVASCRIPT_SDK.md)** - Programmatic API reference with examples
22
- - 📙 **[Quick Start](#quick-start)** - Get started in 2 minutes
9
+ Roxified PNGs are often more space-efficient than ZIP archives for similar payloads and provide a visual indication of the embedded data size.
23
10
 
24
11
  ## Installation
25
12
 
26
- ### As CLI tool (npx)
27
-
28
- No installation needed! Use directly with npx:
29
-
30
- ```bash
31
- npx rox encode input.zip output.png
32
- npx rox decode output.png original.zip
33
- ```
34
-
35
- ### As library
36
-
37
13
  ```bash
38
14
  npm install roxify
39
15
  ```
40
16
 
41
17
  ## CLI Usage
42
18
 
43
- ### Quick Start
44
-
45
19
  ```bash
46
- # Encode a file
47
- npx rox encode document.pdf document.png
48
-
49
- # Decode it back
50
- npx rox decode document.png document.pdf
51
-
52
- # With encryption
53
- npx rox encode secret.zip secret.png -p mypassword
54
- npx rox decode secret.png secret.zip -p mypassword
55
- ```
56
-
57
- ### CLI Commands
58
-
59
- #### `encode` - Encode file to PNG
20
+ npx rox encode <inputName>.ext (<outputName>.png)
60
21
 
61
- ```bash
62
- npx rox encode <input> [output] [options]
22
+ npx rox decode <inputName>.png (<outputName>.ext)
63
23
  ```
64
24
 
65
- **Options:**
66
-
67
- - `-p, --passphrase <pass>` - Encrypt with passphrase (AES-256-GCM)
68
- - `-m, --mode <mode>` - Encoding mode: `compact|chunk|pixel|screenshot` (default: `screenshot`)
69
- - `-q, --quality <0-11>` - Brotli compression quality (default: `1`)
70
- - `0` = fastest, largest
71
- - `11` = slowest, smallest
72
- - `-e, --encrypt <type>` - Encryption: `auto|aes|xor|none` (default: `aes` if passphrase)
73
- - `--no-compress` - Disable compression
74
- - `-o, --output <path>` - Output file path
75
-
76
- **Examples:**
77
-
78
- ```bash
79
- # Basic encoding
80
- npx rox encode data.bin output.png
81
-
82
- # Fast compression for large files
83
- npx rox encode large-video.mp4 output.png -q 0
25
+ If no output name is provided:
84
26
 
85
- # High compression for small files
86
- npx rox encode config.json output.png -q 11
27
+ - Encoding: output defaults to `<inputName>.png`.
28
+ - Decoding: if the image contains the original filename it will be restored; otherwise the output will be `decoded.bin`.
87
29
 
88
- # With encryption
89
- npx rox encode secret.pdf secure.png -p "my secure password"
30
+ **Commands:**
90
31
 
91
- # Compact mode (smallest PNG)
92
- npx rox encode data.bin tiny.png -m compact
93
-
94
- # Screenshot mode (recommended, looks like a real image)
95
- npx rox encode archive.tar.gz screenshot.png -m screenshot
96
- ```
97
-
98
- #### `decode` - Decode PNG to file
99
-
100
- ```bash
101
- npx rox decode <input> [output] [options]
102
- ```
32
+ - `encode <input> [output]` — Encode file to PNG
33
+ - `decode <input> [output]` Decode PNG to file
103
34
 
104
35
  **Options:**
105
36
 
106
- - `-p, --passphrase <pass>` - Decryption passphrase
107
- - `-o, --output <path>` - Output file path (auto-detected from metadata if not provided)
108
-
109
- **Examples:**
37
+ - `-p, --passphrase <pass>` Encrypt with AES-256-GCM
38
+ - `-m, --mode <mode>` Encoding mode: `screenshot` (default), `pixel`, `compact`, `chunk`
39
+ - `-q, --quality <0-11>` — Brotli compression quality (default: 11)
40
+ - `--no-compress` — Disable compression
41
+ - `-v, --verbose` — Show detailed errors
110
42
 
111
- ```bash
112
- # Basic decoding
113
- npx rox decode encoded.png output.bin
114
-
115
- # Auto-detect filename from metadata
116
- npx rox decode encoded.png
117
-
118
- # With decryption
119
- npx rox decode encrypted.png output.pdf -p "my secure password"
120
- ```
43
+ Run `npx rox help` for full options.
121
44
 
122
- ## JavaScript API
45
+ ## API Usage
123
46
 
124
- ### Basic Usage
125
-
126
- ```typescript
47
+ ```js
127
48
  import { encodeBinaryToPng, decodePngToBinary } from 'roxify';
128
- import { readFileSync, writeFileSync } from 'fs';
129
-
130
- // Encode
131
- const input = readFileSync('input.zip');
132
- const png = await encodeBinaryToPng(input, {
133
- mode: 'screenshot',
134
- name: 'input.zip',
135
- });
136
- writeFileSync('output.png', png);
137
-
138
- // Decode
139
- const encoded = readFileSync('output.png');
140
- const result = await decodePngToBinary(encoded);
141
- writeFileSync(result.meta?.name || 'output.bin', result.buf);
142
- ```
143
-
144
- ### With Encryption
145
-
146
- ```typescript
147
- // Encode with AES-256-GCM
148
- const png = await encodeBinaryToPng(input, {
149
- mode: 'screenshot',
150
- passphrase: 'my-secret-password',
151
- encrypt: 'aes',
152
- name: 'secret.zip',
153
- });
154
49
 
155
- // Decode with passphrase
156
- const result = await decodePngToBinary(encoded, {
157
- passphrase: 'my-secret-password',
158
- });
159
- ```
160
-
161
- ### Fast Compression
162
-
163
- ```typescript
164
- // Optimize for speed (recommended for large files)
165
- const png = await encodeBinaryToPng(largeBuffer, {
50
+ const data = Buffer.from('Hello world');
51
+ const png = await encodeBinaryToPng(data, {
166
52
  mode: 'screenshot',
167
- brQuality: 0, // Fastest
168
- name: 'large-file.bin',
53
+ name: 'message.txt',
169
54
  });
170
55
 
171
- // Optimize for size (recommended for small files)
172
- const png = await encodeBinaryToPng(smallBuffer, {
173
- mode: 'compact',
174
- brQuality: 11, // Best compression
175
- name: 'config.json',
176
- });
177
- ```
178
-
179
- ### Encoding Modes
180
-
181
- #### `screenshot` (Recommended)
182
-
183
- Encodes data as RGB pixel values, optimized for screenshot-like appearance. Best balance of size and compatibility.
184
-
185
- ```typescript
186
- const png = await encodeBinaryToPng(data, { mode: 'screenshot' });
187
- ```
188
-
189
- #### `compact` (Smallest)
190
-
191
- Minimal 1x1 PNG with data in custom chunk. Fastest and smallest.
192
-
193
- ```typescript
194
- const png = await encodeBinaryToPng(data, { mode: 'compact' });
56
+ const { buf, meta } = await decodePngToBinary(png);
57
+ console.log(buf.toString('utf8'));
58
+ console.log(meta?.name);
195
59
  ```
196
60
 
197
- #### `pixel`
61
+ **API:**
198
62
 
199
- Encodes data as RGB pixel values without screenshot optimization.
200
-
201
- ```typescript
202
- const png = await encodeBinaryToPng(data, { mode: 'pixel' });
203
- ```
204
-
205
- #### `chunk`
206
-
207
- Standard PNG with data in custom rXDT chunk.
208
-
209
- ```typescript
210
- const png = await encodeBinaryToPng(data, { mode: 'chunk' });
211
- ```
212
-
213
- ## API Reference
214
-
215
- ### `encodeBinaryToPng(input, options)`
216
-
217
- Encodes binary data into a PNG image.
218
-
219
- **Parameters:**
220
-
221
- - `input: Buffer` - The binary data to encode
222
- - `options?: EncodeOptions` - Encoding options
223
-
224
- **Returns:** `Promise<Buffer>` - The encoded PNG
63
+ - `encodeBinaryToPng(input: Buffer, opts?: EncodeOptions): Promise<Buffer>`
64
+ - `decodePngToBinary(pngBuf: Buffer): Promise<{ buf: Buffer, meta?: { name?: string } }>`
225
65
 
226
66
  **Options:**
227
67
 
228
- ```typescript
229
- interface EncodeOptions {
230
- // Compression algorithm ('br' = Brotli, 'none' = no compression)
231
- compression?: 'br' | 'none';
232
-
233
- // Passphrase for encryption
234
- passphrase?: string;
235
-
236
- // Original filename to embed
237
- name?: string;
68
+ - `mode` — `'screenshot'` | `'pixel'` | `'compact'` | `'chunk'` (default: `'screenshot'`)
69
+ - `name` — Original filename (embedded as metadata)
70
+ - `passphrase` Encryption passphrase (uses AES-256-GCM)
71
+ - `compression` — `'br'` | `'none'` (default: `'br'`)
72
+ - `brQuality` — Brotli quality 0-11 (default: 4)
238
73
 
239
- // Encoding mode
240
- mode?: 'compact' | 'chunk' | 'pixel' | 'screenshot';
241
-
242
- // Encryption method
243
- encrypt?: 'auto' | 'aes' | 'xor' | 'none';
244
-
245
- // Output format
246
- output?: 'auto' | 'png' | 'rox';
247
-
248
- // Include filename in metadata (default: true)
249
- includeName?: boolean;
250
-
251
- // Brotli quality 0-11 (default: 1)
252
- brQuality?: number;
253
- }
254
- ```
255
-
256
- ### `decodePngToBinary(pngBuf, options)`
257
-
258
- Decodes a PNG image back to binary data.
259
-
260
- **Parameters:**
261
-
262
- - `pngBuf: Buffer` - The PNG image to decode
263
- - `options?: DecodeOptions` - Decoding options
264
-
265
- **Returns:** `Promise<DecodeResult>` - The decoded data and metadata
266
-
267
- **Options:**
268
-
269
- ```typescript
270
- interface DecodeOptions {
271
- // Passphrase for decryption
272
- passphrase?: string;
273
- }
274
- ```
74
+ ## Example: Express Endpoint
275
75
 
276
- **Result:**
76
+ ```js
77
+ import express from 'express';
78
+ import { encodeBinaryToPng } from 'roxify';
277
79
 
278
- ```typescript
279
- interface DecodeResult {
280
- // Decoded binary data
281
- buf: Buffer;
282
-
283
- // Extracted metadata
284
- meta?: {
285
- // Original filename
286
- name?: string;
287
- };
288
- }
289
- ```
290
-
291
- ## Performance Tips
292
-
293
- ### For Large Files (>10 MB)
294
-
295
- ```bash
296
- # Use quality 0 for fastest encoding
297
- npx rox encode large.bin output.png -q 0
298
- ```
299
-
300
- ```typescript
301
- const png = await encodeBinaryToPng(largeFile, {
302
- mode: 'screenshot',
303
- brQuality: 0, // 10-20x faster than default
80
+ const app = express();
81
+ app.get('/payload.png', async (req, res) => {
82
+ const payload = Buffer.from('Embedded data');
83
+ const png = await encodeBinaryToPng(payload, { mode: 'screenshot' });
84
+ res.setHeader('Content-Type', 'image/png');
85
+ res.send(png);
304
86
  });
87
+ app.listen(3000);
305
88
  ```
306
89
 
307
- ### For Small Files (<1 MB)
90
+ ## Requirements
308
91
 
309
- ```bash
310
- # Use quality 11 for best compression
311
- npx rox encode small.json output.png -q 11 -m compact
312
- ```
313
-
314
- ```typescript
315
- const png = await encodeBinaryToPng(smallFile, {
316
- mode: 'compact',
317
- brQuality: 11, // Best compression ratio
318
- });
319
- ```
320
-
321
- ### Benchmark Results
322
-
323
- File: 3.8 MB binary
324
-
325
- - **Quality 0**: ~500-800ms, output ~1.2 MB
326
- - **Quality 1** (default): ~1-2s, output ~800 KB
327
- - **Quality 5**: ~8-12s, output ~750 KB
328
- - **Quality 11**: ~20-30s, output ~720 KB
329
-
330
- ## Error Handling
331
-
332
- ```typescript
333
- try {
334
- const result = await decodePngToBinary(encoded, {
335
- passphrase: 'wrong-password',
336
- });
337
- } catch (err) {
338
- if (err.message.includes('Incorrect passphrase')) {
339
- console.error('Wrong password!');
340
- } else if (err.message.includes('Invalid ROX format')) {
341
- console.error('Not a valid RoxCompressor PNG');
342
- } else {
343
- console.error('Decode failed:', err.message);
344
- }
345
- }
346
- ```
347
-
348
- ## Security
349
-
350
- - **AES-256-GCM**: Authenticated encryption with 100,000 PBKDF2 iterations
351
- - **XOR cipher**: Simple obfuscation (not cryptographically secure)
352
- - **No encryption**: Data is compressed but not encrypted
353
-
354
- ⚠️ **Warning**: Use strong passphrases for sensitive data. The `xor` encryption mode is not secure and should only be used for obfuscation.
92
+ - Node.js 18+ (ESM)
93
+ - Native dependencies: `sharp` (auto-installed)
355
94
 
356
95
  ## License
357
96
 
358
- MIT © RoxCompressor
359
-
360
- ## Contributing
361
-
362
- Contributions welcome! Please open an issue or PR on GitHub.
363
-
364
- ## Links
365
-
366
- - [GitHub Repository](https://github.com/RoxasYTB/RoxCompressor)
367
- - [npm Package](https://www.npmjs.com/package/roxify)
368
- - [Report Issues](https://github.com/RoxasYTB/RoxCompressor/issues)
97
+ This package is proprietary (UNLICENSED). The repository remains private; the package is published to npm for distribution. If there is significant community interest, it may be open-sourced in the future.
package/dist/cli.js CHANGED
@@ -173,7 +173,7 @@ async function decodeCommand(args) {
173
173
  catch (e) {
174
174
  console.log('Could not generate reconst PNG:', e.message);
175
175
  }
176
- console.log(`Reading: ${resolvedInputPath}`);
176
+ console.log(`Reading: ${resolvedInputPath.replace('_reconst.png', '.png')}`);
177
177
  const options = {};
178
178
  if (parsed.passphrase) {
179
179
  options.passphrase = parsed.passphrase;
package/dist/index.js CHANGED
@@ -108,6 +108,19 @@ async function loadRaw(imgInput) {
108
108
  return { data, info };
109
109
  }
110
110
  export async function cropAndReconstitute(input) {
111
+ async function loadRaw(imgInput) {
112
+ const { data, info } = await sharp(imgInput)
113
+ .ensureAlpha()
114
+ .raw()
115
+ .toBuffer({ resolveWithObject: true });
116
+ return { data, info };
117
+ }
118
+ function idxFor(x, y, width) {
119
+ return (y * width + x) * 4;
120
+ }
121
+ function eqRGB(a, b) {
122
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
123
+ }
111
124
  const { data, info } = await loadRaw(input);
112
125
  const w = info.width;
113
126
  const h = info.height;
@@ -183,8 +196,8 @@ export async function cropAndReconstitute(input) {
183
196
  const sy1 = Math.min(startPoint.y, endPoint.y);
184
197
  const sx2 = Math.max(startPoint.x, endPoint.x);
185
198
  const sy2 = Math.max(startPoint.y, endPoint.y);
186
- const cropW = sx2 - sx1;
187
- const cropH = sy2 - sy1;
199
+ const cropW = sx2 - sx1 + 1;
200
+ const cropH = sy2 - sy1 + 1;
188
201
  if (cropW <= 0 || cropH <= 0)
189
202
  throw new Error('Invalid crop dimensions');
190
203
  const cropped = await sharp(input)
@@ -212,19 +225,84 @@ export async function cropAndReconstitute(input) {
212
225
  return false;
213
226
  return true;
214
227
  }
215
- const compressedLines = [];
228
+ const newWidth = cw;
229
+ const newHeight = ch + 1;
230
+ const out = Buffer.alloc(newWidth * newHeight * 4, 0);
231
+ for (let i = 0; i < out.length; i += 4)
232
+ out[i + 3] = 255;
216
233
  for (let y = 0; y < ch; y++) {
234
+ for (let x = 0; x < cw; x++) {
235
+ const srcI = ((y * cw + x) * 4) | 0;
236
+ const dstI = ((y * newWidth + x) * 4) | 0;
237
+ out[dstI] = cdata[srcI];
238
+ out[dstI + 1] = cdata[srcI + 1];
239
+ out[dstI + 2] = cdata[srcI + 2];
240
+ out[dstI + 3] = cdata[srcI + 3];
241
+ }
242
+ }
243
+ if (cw >= 3) {
244
+ const targetY = ch - 1;
245
+ for (let x = cw - 3; x < cw; x++) {
246
+ const i = ((targetY * newWidth + x) * 4) | 0;
247
+ out[i] = 0;
248
+ out[i + 1] = 0;
249
+ out[i + 2] = 0;
250
+ out[i + 3] = 255;
251
+ }
252
+ }
253
+ else {
254
+ const targetY = ch - 1;
255
+ for (let x = 0; x < cw; x++) {
256
+ const i = ((targetY * newWidth + x) * 4) | 0;
257
+ out[i] = 0;
258
+ out[i + 1] = 0;
259
+ out[i + 2] = 0;
260
+ out[i + 3] = 255;
261
+ }
262
+ }
263
+ const lastY = ch;
264
+ for (let x = 0; x < newWidth; x++) {
265
+ const i = ((lastY * newWidth + x) * 4) | 0;
266
+ out[i] = 0;
267
+ out[i + 1] = 0;
268
+ out[i + 2] = 0;
269
+ out[i + 3] = 255;
270
+ }
271
+ if (newWidth >= 3) {
272
+ const bgrStart = newWidth - 3;
273
+ let i = ((lastY * newWidth + bgrStart) * 4) | 0;
274
+ out[i] = 0;
275
+ out[i + 1] = 0;
276
+ out[i + 2] = 255;
277
+ out[i + 3] = 255;
278
+ i = ((lastY * newWidth + bgrStart + 1) * 4) | 0;
279
+ out[i] = 0;
280
+ out[i + 1] = 255;
281
+ out[i + 2] = 0;
282
+ out[i + 3] = 255;
283
+ i = ((lastY * newWidth + bgrStart + 2) * 4) | 0;
284
+ out[i] = 255;
285
+ out[i + 1] = 0;
286
+ out[i + 2] = 0;
287
+ out[i + 3] = 255;
288
+ }
289
+ function getPixel(x, y) {
290
+ const i = ((y * newWidth + x) * 4) | 0;
291
+ return [out[i], out[i + 1], out[i + 2], out[i + 3]];
292
+ }
293
+ const compressedLines = [];
294
+ for (let y = 0; y < newHeight; y++) {
217
295
  const line = [];
218
296
  let x = 0;
219
- while (x < cw) {
220
- const current = cat(x, y);
297
+ while (x < newWidth) {
298
+ const current = getPixel(x, y);
221
299
  if (current[0] === 0 && current[1] === 0 && current[2] === 0) {
222
300
  x++;
223
301
  continue;
224
302
  }
225
303
  line.push(current);
226
304
  let nx = x + 1;
227
- while (nx < cw && eq(cat(nx, y), current))
305
+ while (nx < newWidth && eq(getPixel(nx, y), current))
228
306
  nx++;
229
307
  x = nx;
230
308
  }
@@ -246,90 +324,25 @@ export async function cropAndReconstitute(input) {
246
324
  .png()
247
325
  .toBuffer();
248
326
  }
249
- const newWidth = Math.max(...compressedLines.map((l) => l.length));
250
- const newHeight = compressedLines.length + 1;
251
- const out = Buffer.alloc(newWidth * newHeight * 4, 0);
252
- for (let i = 0; i < out.length; i += 4)
253
- out[i + 3] = 255;
327
+ const finalWidth = Math.max(...compressedLines.map((l) => l.length));
328
+ const finalHeight = compressedLines.length;
329
+ const finalOut = Buffer.alloc(finalWidth * finalHeight * 4, 0);
330
+ for (let i = 0; i < finalOut.length; i += 4)
331
+ finalOut[i + 3] = 255;
254
332
  for (let y = 0; y < compressedLines.length; y++) {
255
333
  const line = compressedLines[y];
256
- const isSecondToLast = y === compressedLines.length - 1;
257
- const startX = 0;
258
- const effectiveLength = isSecondToLast
259
- ? Math.max(0, line.length - 3)
260
- : line.length;
261
- for (let x = 0; x < effectiveLength; x++) {
262
- const i = ((y * newWidth + startX + x) * 4) | 0;
263
- out[i] = line[x][0];
264
- out[i + 1] = line[x][1];
265
- out[i + 2] = line[x][2];
266
- out[i + 3] = line[x][3] === 0 ? 255 : line[x][3];
267
- }
268
- if (isSecondToLast) {
269
- for (let x = effectiveLength; x < Math.min(effectiveLength + 3, newWidth); x++) {
270
- const i = ((y * newWidth + x) * 4) | 0;
271
- out[i] = 0;
272
- out[i + 1] = 0;
273
- out[i + 2] = 0;
274
- out[i + 3] = 255;
275
- }
276
- }
277
- }
278
- const secondToLastY = newHeight - 2;
279
- let secondToLastIsBlack = true;
280
- for (let x = 0; x < newWidth; x++) {
281
- const i = ((secondToLastY * newWidth + x) * 4) | 0;
282
- if (out[i] !== 0 || out[i + 1] !== 0 || out[i + 2] !== 0) {
283
- secondToLastIsBlack = false;
284
- break;
285
- }
286
- }
287
- let finalHeight = newHeight;
288
- let finalOut = out;
289
- if (secondToLastIsBlack) {
290
- finalHeight = newHeight - 1;
291
- finalOut = Buffer.alloc(newWidth * finalHeight * 4, 0);
292
- for (let i = 0; i < finalOut.length; i += 4)
293
- finalOut[i + 3] = 255;
294
- for (let y = 0; y < newHeight - 2; y++) {
295
- for (let x = 0; x < newWidth; x++) {
296
- const srcI = ((y * newWidth + x) * 4) | 0;
297
- const dstI = ((y * newWidth + x) * 4) | 0;
298
- finalOut[dstI] = out[srcI];
299
- finalOut[dstI + 1] = out[srcI + 1];
300
- finalOut[dstI + 2] = out[srcI + 2];
301
- finalOut[dstI + 3] = out[srcI + 3];
302
- }
334
+ const isLastLine = y === compressedLines.length - 1;
335
+ const startX = isLastLine ? finalWidth - line.length : 0;
336
+ for (let x = 0; x < line.length; x++) {
337
+ const i = ((y * finalWidth + startX + x) * 4) | 0;
338
+ finalOut[i] = line[x][0];
339
+ finalOut[i + 1] = line[x][1];
340
+ finalOut[i + 2] = line[x][2];
341
+ finalOut[i + 3] = line[x][3] === 0 ? 255 : line[x][3];
303
342
  }
304
343
  }
305
- const lastY = finalHeight - 1;
306
- for (let x = 0; x < newWidth; x++) {
307
- const i = ((lastY * newWidth + x) * 4) | 0;
308
- finalOut[i] = 0;
309
- finalOut[i + 1] = 0;
310
- finalOut[i + 2] = 0;
311
- finalOut[i + 3] = 255;
312
- }
313
- if (newWidth >= 3) {
314
- const bgrStart = newWidth - 3;
315
- let i = ((lastY * newWidth + bgrStart) * 4) | 0;
316
- finalOut[i] = 0;
317
- finalOut[i + 1] = 0;
318
- finalOut[i + 2] = 255;
319
- finalOut[i + 3] = 255;
320
- i = ((lastY * newWidth + bgrStart + 1) * 4) | 0;
321
- finalOut[i] = 0;
322
- finalOut[i + 1] = 255;
323
- finalOut[i + 2] = 0;
324
- finalOut[i + 3] = 255;
325
- i = ((lastY * newWidth + bgrStart + 2) * 4) | 0;
326
- finalOut[i] = 255;
327
- finalOut[i + 1] = 0;
328
- finalOut[i + 2] = 0;
329
- finalOut[i + 3] = 255;
330
- }
331
344
  return sharp(finalOut, {
332
- raw: { width: newWidth, height: finalHeight, channels: 4 },
345
+ raw: { width: finalWidth, height: finalHeight, channels: 4 },
333
346
  })
334
347
  .png()
335
348
  .toBuffer();
@@ -442,7 +455,7 @@ export async function encodeBinaryToPng(input, opts = {}) {
442
455
  const spaceInLastRow = pixelsInLastRow === 0 ? logicalWidth : logicalWidth - pixelsInLastRow;
443
456
  const needsExtraRow = spaceInLastRow < MARKER_END.length;
444
457
  const logicalHeight = needsExtraRow ? dataRows + 1 : dataRows;
445
- const scale = 1;
458
+ const scale = 2;
446
459
  const width = logicalWidth * scale;
447
460
  const height = logicalHeight * scale;
448
461
  const raw = Buffer.alloc(width * height * bytesPerPixel);
@@ -489,10 +502,10 @@ export async function encodeBinaryToPng(input, opts = {}) {
489
502
  raw: { width, height, channels: 3 },
490
503
  })
491
504
  .png({
492
- compressionLevel: 0,
505
+ compressionLevel: 9,
493
506
  palette: false,
494
- effort: 1,
495
- adaptiveFiltering: false,
507
+ effort: 10,
508
+ adaptiveFiltering: true,
496
509
  })
497
510
  .toBuffer();
498
511
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roxify",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Encode binary data into PNG images and decode them back. Supports CLI and programmatic API (Node.js ESM).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",