roxify 1.1.12 → 1.1.13

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.
@@ -0,0 +1,8 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ export declare function encodeMinPng(rgb: Buffer, width: number, height: number): Promise<Buffer>;
4
+ export declare function decodeMinPng(pngBuf: Buffer): Promise<{
5
+ buf: Buffer;
6
+ width: number;
7
+ height: number;
8
+ } | null>;
package/dist/minpng.js ADDED
@@ -0,0 +1,231 @@
1
+ import { compress as zstdCompress, decompress as zstdDecompress, } from '@mongodb-js/zstd';
2
+ import encode from 'png-chunks-encode';
3
+ import { deflateSync } from 'zlib';
4
+ const PIXEL_MAGIC = Buffer.from('MNPG');
5
+ const MARKER_START = [
6
+ { r: 255, g: 0, b: 0 },
7
+ { r: 0, g: 255, b: 0 },
8
+ { r: 0, b: 0, g: 255 },
9
+ ];
10
+ const MARKER_END = [...MARKER_START].reverse();
11
+ function paeth(a, b, c) {
12
+ const p = a + b - c;
13
+ const pa = Math.abs(p - a);
14
+ const pb = Math.abs(p - b);
15
+ const pc = Math.abs(p - c);
16
+ if (pa <= pb && pa <= pc)
17
+ return a;
18
+ if (pb <= pc)
19
+ return b;
20
+ return c;
21
+ }
22
+ function zigzagOrder(width, height) {
23
+ const coords = [];
24
+ for (let y = 0; y < height; y++) {
25
+ if (y % 2 === 0) {
26
+ for (let x = 0; x < width; x++)
27
+ coords.push([x, y]);
28
+ }
29
+ else {
30
+ for (let x = width - 1; x >= 0; x--)
31
+ coords.push([x, y]);
32
+ }
33
+ }
34
+ return coords;
35
+ }
36
+ export async function encodeMinPng(rgb, width, height) {
37
+ const w = width, h = height;
38
+ const idx = (x, y) => (y * w + x) * 3;
39
+ const residualR = Buffer.alloc(w * h);
40
+ const residualG = Buffer.alloc(w * h);
41
+ const residualB = Buffer.alloc(w * h);
42
+ for (let y = 0; y < h; y++) {
43
+ for (let x = 0; x < w; x++) {
44
+ const i = idx(x, y);
45
+ const r = rgb[i];
46
+ const g = rgb[i + 1];
47
+ const b = rgb[i + 2];
48
+ const leftI = x > 0 ? idx(x - 1, y) : -1;
49
+ const upI = y > 0 ? idx(x, y - 1) : -1;
50
+ const upLeftI = x > 0 && y > 0 ? idx(x - 1, y - 1) : -1;
51
+ const leftR = leftI >= 0 ? rgb[leftI] : 0;
52
+ const upR = upI >= 0 ? rgb[upI] : 0;
53
+ const upLeftR = upLeftI >= 0 ? rgb[upLeftI] : 0;
54
+ const predR = paeth(leftR, upR, upLeftR);
55
+ residualR[y * w + x] = (r - predR + 256) & 0xff;
56
+ const leftG = leftI >= 0 ? rgb[leftI + 1] : 0;
57
+ const upG = upI >= 0 ? rgb[upI + 1] : 0;
58
+ const upLeftG = upLeftI >= 0 ? rgb[upLeftI + 1] : 0;
59
+ const predG = paeth(leftG, upG, upLeftG);
60
+ residualG[y * w + x] = (g - predG + 256) & 0xff;
61
+ const leftB = leftI >= 0 ? rgb[leftI + 2] : 0;
62
+ const upB = upI >= 0 ? rgb[upI + 2] : 0;
63
+ const upLeftB = upLeftI >= 0 ? rgb[upLeftI + 2] : 0;
64
+ const predB = paeth(leftB, upB, upLeftB);
65
+ residualB[y * w + x] = (b - predB + 256) & 0xff;
66
+ }
67
+ }
68
+ const coords = zigzagOrder(w, h);
69
+ const transformed = [];
70
+ for (const [x, y] of coords) {
71
+ const pos = y * w + x;
72
+ const g = residualG[pos];
73
+ const r = (residualR[pos] - g + 256) & 0xff;
74
+ const b = (residualB[pos] - g + 256) & 0xff;
75
+ transformed.push(g, r, b);
76
+ }
77
+ const transformedBuf = Buffer.from(transformed);
78
+ const compressed = Buffer.from(await zstdCompress(transformedBuf, 22));
79
+ const header = Buffer.alloc(4 + 1 + 4 + 4);
80
+ PIXEL_MAGIC.copy(header, 0);
81
+ header[4] = 1;
82
+ header.writeUInt32BE(w, 5);
83
+ header.writeUInt32BE(h, 9);
84
+ const lenBuf = Buffer.alloc(4);
85
+ lenBuf.writeUInt32BE(compressed.length, 0);
86
+ const payload = Buffer.concat([header, lenBuf, compressed]);
87
+ const markerStartBytes = Buffer.alloc(MARKER_START.length * 3);
88
+ for (let i = 0; i < MARKER_START.length; i++) {
89
+ markerStartBytes[i * 3] = MARKER_START[i].r;
90
+ markerStartBytes[i * 3 + 1] = MARKER_START[i].g;
91
+ markerStartBytes[i * 3 + 2] = MARKER_START[i].b;
92
+ }
93
+ const markerEndBytes = Buffer.alloc(MARKER_END.length * 3);
94
+ for (let i = 0; i < MARKER_END.length; i++) {
95
+ markerEndBytes[i * 3] = MARKER_END[i].r;
96
+ markerEndBytes[i * 3 + 1] = MARKER_END[i].g;
97
+ markerEndBytes[i * 3 + 2] = MARKER_END[i].b;
98
+ }
99
+ const dataWithMarkers = Buffer.concat([
100
+ markerStartBytes,
101
+ payload,
102
+ markerEndBytes,
103
+ ]);
104
+ const bytesPerPixel = 3;
105
+ const nPixels = Math.ceil(dataWithMarkers.length / bytesPerPixel);
106
+ const side = Math.max(1, Math.ceil(Math.sqrt(nPixels)));
107
+ const widthOut = side;
108
+ const heightOut = Math.ceil(nPixels / widthOut);
109
+ const rowLen = 1 + widthOut * bytesPerPixel;
110
+ const raw = Buffer.alloc(rowLen * heightOut);
111
+ for (let y = 0; y < heightOut; y++)
112
+ raw[y * rowLen] = 0;
113
+ for (let p = 0; p < nPixels; p++) {
114
+ const srcIdx = p * 3;
115
+ const y = Math.floor(p / widthOut);
116
+ const x = p % widthOut;
117
+ const dst = y * rowLen + 1 + x * 3;
118
+ raw[dst] = srcIdx < dataWithMarkers.length ? dataWithMarkers[srcIdx] : 0;
119
+ raw[dst + 1] =
120
+ srcIdx + 1 < dataWithMarkers.length ? dataWithMarkers[srcIdx + 1] : 0;
121
+ raw[dst + 2] =
122
+ srcIdx + 2 < dataWithMarkers.length ? dataWithMarkers[srcIdx + 2] : 0;
123
+ }
124
+ const idat = deflateSync(raw, { level: 9 });
125
+ const ihdr = Buffer.alloc(13);
126
+ ihdr.writeUInt32BE(widthOut, 0);
127
+ ihdr.writeUInt32BE(heightOut, 4);
128
+ ihdr[8] = 8;
129
+ ihdr[9] = 2;
130
+ ihdr[10] = 0;
131
+ ihdr[11] = 0;
132
+ ihdr[12] = 0;
133
+ const chunks = [
134
+ { name: 'IHDR', data: ihdr },
135
+ { name: 'IDAT', data: idat },
136
+ { name: 'IEND', data: Buffer.alloc(0) },
137
+ ];
138
+ return Buffer.from(encode(chunks));
139
+ }
140
+ export async function decodeMinPng(pngBuf) {
141
+ const sharp = await import('sharp');
142
+ const { data, info } = await sharp
143
+ .default(pngBuf)
144
+ .raw()
145
+ .toBuffer({ resolveWithObject: true });
146
+ const currentWidth = info.width;
147
+ const currentHeight = info.height;
148
+ const rawRGB = Buffer.alloc(currentWidth * currentHeight * 3);
149
+ for (let i = 0; i < currentWidth * currentHeight; i++) {
150
+ rawRGB[i * 3] = data[i * 3];
151
+ rawRGB[i * 3 + 1] = data[i * 3 + 1];
152
+ rawRGB[i * 3 + 2] = data[i * 3 + 2];
153
+ }
154
+ function findMarkerStart(buf) {
155
+ for (let i = 0; i <= buf.length - MARKER_START.length * 3; i += 3) {
156
+ let ok = true;
157
+ for (let m = 0; m < MARKER_START.length; m++) {
158
+ const j = i + m * 3;
159
+ if (buf[j] !== MARKER_START[m].r ||
160
+ buf[j + 1] !== MARKER_START[m].g ||
161
+ buf[j + 2] !== MARKER_START[m].b) {
162
+ ok = false;
163
+ break;
164
+ }
165
+ }
166
+ if (ok)
167
+ return i + MARKER_START.length * 3;
168
+ }
169
+ return -1;
170
+ }
171
+ const startIdxBytes = findMarkerStart(rawRGB);
172
+ if (startIdxBytes === -1)
173
+ return null;
174
+ const headerStart = startIdxBytes;
175
+ if (headerStart + 13 > rawRGB.length)
176
+ return null;
177
+ if (!rawRGB.slice(headerStart, headerStart + 4).equals(PIXEL_MAGIC))
178
+ return null;
179
+ const origW = rawRGB.readUInt32BE(headerStart + 5);
180
+ const origH = rawRGB.readUInt32BE(headerStart + 9);
181
+ const compressedLen = rawRGB.readUInt32BE(headerStart + 13);
182
+ const compStart = headerStart + 17;
183
+ if (compStart + compressedLen > rawRGB.length)
184
+ return null;
185
+ const compressed = rawRGB.slice(compStart, compStart + compressedLen);
186
+ const decompressed = Buffer.from(await zstdDecompress(compressed));
187
+ const coords = zigzagOrder(origW, origH);
188
+ const residualR = Buffer.alloc(origW * origH);
189
+ const residualG = Buffer.alloc(origW * origH);
190
+ const residualB = Buffer.alloc(origW * origH);
191
+ let p = 0;
192
+ for (const [x, y] of coords) {
193
+ if (p + 3 > decompressed.length)
194
+ break;
195
+ const g = decompressed[p++];
196
+ const rminusg = decompressed[p++];
197
+ const bminusg = decompressed[p++];
198
+ const pos = y * origW + x;
199
+ residualG[pos] = g;
200
+ residualR[pos] = (rminusg + g) & 0xff;
201
+ residualB[pos] = (bminusg + g) & 0xff;
202
+ }
203
+ const out = Buffer.alloc(origW * origH * 3);
204
+ for (let y = 0; y < origH; y++) {
205
+ for (let x = 0; x < origW; x++) {
206
+ const pos = y * origW + x;
207
+ const leftPos = x > 0 ? y * origW + (x - 1) : -1;
208
+ const upPos = y > 0 ? (y - 1) * origW + x : -1;
209
+ const upLeftPos = x > 0 && y > 0 ? (y - 1) * origW + (x - 1) : -1;
210
+ const leftR = leftPos >= 0 ? out[leftPos * 3] : 0;
211
+ const upR = upPos >= 0 ? out[upPos * 3] : 0;
212
+ const upLeftR = upLeftPos >= 0 ? out[upLeftPos * 3] : 0;
213
+ const predR = paeth(leftR, upR, upLeftR);
214
+ const r = (residualR[pos] + predR) & 0xff;
215
+ const leftG = leftPos >= 0 ? out[leftPos * 3 + 1] : 0;
216
+ const upG = upPos >= 0 ? out[upPos * 3 + 1] : 0;
217
+ const upLeftG = upLeftPos >= 0 ? out[upLeftPos * 3 + 1] : 0;
218
+ const predG = paeth(leftG, upG, upLeftG);
219
+ const g = (residualG[pos] + predG) & 0xff;
220
+ const leftB = leftPos >= 0 ? out[leftPos * 3 + 2] : 0;
221
+ const upB = upPos >= 0 ? out[upPos * 3 + 2] : 0;
222
+ const upLeftB = upLeftPos >= 0 ? out[upLeftPos * 3 + 2] : 0;
223
+ const predB = paeth(leftB, upB, upLeftB);
224
+ const b = (residualB[pos] + predB) & 0xff;
225
+ out[pos * 3] = r;
226
+ out[pos * 3 + 1] = g;
227
+ out[pos * 3 + 2] = b;
228
+ }
229
+ }
230
+ return { buf: out, width: origW, height: origH };
231
+ }
package/dist/pack.d.ts CHANGED
@@ -4,7 +4,7 @@ export interface PackedFile {
4
4
  path: string;
5
5
  buf: Buffer;
6
6
  }
7
- export declare function packPaths(paths: string[], baseDir?: string, onProgress?: (readBytes: number, totalBytes: number) => void): {
7
+ export declare function packPaths(paths: string[], baseDir?: string, onProgress?: (readBytes: number, totalBytes: number, currentFile?: string) => void): {
8
8
  buf: Buffer;
9
9
  list: string[];
10
10
  };
package/dist/pack.js CHANGED
@@ -41,7 +41,7 @@ export function packPaths(paths, baseDir, onProgress) {
41
41
  list.push(rel);
42
42
  readSoFar += sizes[idx];
43
43
  if (onProgress)
44
- onProgress(readSoFar, total);
44
+ onProgress(readSoFar, total, rel);
45
45
  }
46
46
  const header = Buffer.alloc(8);
47
47
  header.writeUInt32BE(0x524f5850, 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roxify",
3
- "version": "1.1.12",
3
+ "version": "1.1.13",
4
4
  "description": "Encode binary data into PNG images with Zstd compression and decode them back. Supports CLI and programmatic API (Node.js ESM).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,7 +16,7 @@
16
16
  "build": "tsc",
17
17
  "check-publish": "node ../scripts/check-publish.js roxify",
18
18
  "cli": "node dist/cli.js",
19
- "test": "npm run build && node test/pack.test.js && node test/screenshot.test.js && node test/list.test.js"
19
+ "test": "npm run build && node test/pack.test.js && node test/screenshot.test.js && node test/list.test.js && node test/minpng.test.js && node test/optimize.test.js"
20
20
  },
21
21
  "keywords": [
22
22
  "steganography",
@@ -45,6 +45,9 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@mongodb-js/zstd": "^2.0.1",
48
+ "zopflipng-bin": "^1.0.1",
49
+ "fflate": "^0.7.4",
50
+ "lzma-purejs": "^1.0.4",
48
51
  "png-chunks-encode": "^1.0.0",
49
52
  "png-chunks-extract": "^1.0.0",
50
53
  "sharp": "^0.34.5",