roxify 1.1.2 → 1.1.3

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.
Files changed (3) hide show
  1. package/dist/cli.js +1 -4
  2. package/dist/index.js +173 -175
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync, writeFileSync } from 'fs';
3
- import { basename, dirname, join, resolve } from 'path';
3
+ import { basename, dirname, resolve } from 'path';
4
4
  import sharp from 'sharp';
5
5
  import { cropAndReconstitute, DataFormatError, decodePngToBinary, encodeBinaryToPng, IncorrectPassphraseError, PassphraseRequiredError, } from './index.js';
6
6
  const VERSION = '1.0.4';
@@ -187,9 +187,6 @@ async function decodeCommand(args) {
187
187
  })
188
188
  .png()
189
189
  .toBuffer();
190
- if (options.debugDir) {
191
- writeFileSync(join(options.debugDir, 'doubled.png'), doubledBuffer);
192
- }
193
190
  const reconstructedBuffer = await cropAndReconstitute(doubledBuffer, options.debugDir);
194
191
  const result = await decodePngToBinary(reconstructedBuffer, options);
195
192
  const decodeTime = Date.now() - startDecode;
package/dist/index.js CHANGED
@@ -148,9 +148,8 @@ export async function cropAndReconstitute(input, debugDir) {
148
148
  await sharp(doubledBuffer).toFile(join(debugDir, 'doubled.png'));
149
149
  }
150
150
  const { data: doubledData, info: doubledInfo } = await loadRaw(doubledBuffer);
151
- const w = doubledInfo.width;
152
- const h = doubledInfo.height;
153
- function at(x, y) {
151
+ const w = doubledInfo.width, h = doubledInfo.height;
152
+ const at = (x, y) => {
154
153
  const i = idxFor(x, y, w);
155
154
  return [
156
155
  doubledData[i],
@@ -158,178 +157,110 @@ export async function cropAndReconstitute(input, debugDir) {
158
157
  doubledData[i + 2],
159
158
  doubledData[i + 3],
160
159
  ];
161
- }
162
- let startPoint = null;
163
- for (let y = 0; y < h && !startPoint; y++) {
164
- for (let x = 0; x < w; x++) {
165
- const p = at(x, y);
166
- if (p[0] !== 255 || p[1] !== 0 || p[2] !== 0)
167
- continue;
168
- let nx = x + 1;
169
- while (nx < w && eqRGB(at(nx, y), p))
170
- nx++;
171
- if (nx >= w)
172
- continue;
173
- const a = at(nx, y);
174
- let nx2 = nx + 1;
175
- while (nx2 < w && eqRGB(at(nx2, y), a))
176
- nx2++;
177
- if (nx2 >= w)
178
- continue;
179
- const b = at(nx2, y);
180
- const isRgb = a[0] === 0 &&
181
- a[1] === 255 &&
182
- a[2] === 0 &&
183
- b[0] === 0 &&
184
- b[1] === 0 &&
185
- b[2] === 255;
186
- if (isRgb) {
187
- startPoint = { x, y, type: 'rgb' };
188
- break;
189
- }
190
- }
191
- }
192
- let endPoint = null;
193
- for (let y = h - 1; y >= 0 && !endPoint; y--) {
194
- for (let x = w - 1; x >= 0; x--) {
195
- const p = at(x, y);
196
- if (p[0] !== 255 || p[1] !== 0 || p[2] !== 0)
197
- continue;
198
- let nx = x - 1;
199
- while (nx >= 0 && eqRGB(at(nx, y), p))
200
- nx--;
201
- if (nx < 0)
202
- continue;
203
- const a = at(nx, y);
204
- let nx2 = nx - 1;
205
- while (nx2 >= 0 && eqRGB(at(nx2, y), a))
206
- nx2--;
207
- if (nx2 < 0)
208
- continue;
209
- const b = at(nx2, y);
210
- const isRgbReverse = a[0] === 0 &&
211
- a[1] === 255 &&
212
- a[2] === 0 &&
213
- b[0] === 0 &&
214
- b[1] === 0 &&
215
- b[2] === 255;
216
- if (isRgbReverse) {
217
- endPoint = { x, y, type: 'bgr' };
218
- break;
160
+ };
161
+ const findPattern = (startX, startY, dirX, dirY, pattern) => {
162
+ for (let y = startY; y >= 0 && y < h; y += dirY) {
163
+ for (let x = startX; x >= 0 && x < w; x += dirX) {
164
+ const p = at(x, y);
165
+ if (p[0] !== 255 || p[1] !== 0 || p[2] !== 0)
166
+ continue;
167
+ let nx = x + dirX;
168
+ while (nx >= 0 && nx < w && eqRGB(at(nx, y), p))
169
+ nx += dirX;
170
+ if (nx < 0 || nx >= w)
171
+ continue;
172
+ const a = at(nx, y);
173
+ let nx2 = nx + dirX;
174
+ while (nx2 >= 0 && nx2 < w && eqRGB(at(nx2, y), a))
175
+ nx2 += dirX;
176
+ if (nx2 < 0 || nx2 >= w)
177
+ continue;
178
+ const b = at(nx2, y);
179
+ if (a[0] === pattern[0][0] &&
180
+ a[1] === pattern[0][1] &&
181
+ a[2] === pattern[0][2] &&
182
+ b[0] === pattern[1][0] &&
183
+ b[1] === pattern[1][1] &&
184
+ b[2] === pattern[1][2]) {
185
+ return { x, y };
186
+ }
219
187
  }
220
188
  }
221
- }
222
- if (!startPoint)
223
- throw new Error('Start pattern (RGB) not found');
224
- if (!endPoint)
225
- throw new Error('End pattern (BGR) not found');
226
- const sx1 = Math.min(startPoint.x, endPoint.x);
227
- const sy1 = Math.min(startPoint.y, endPoint.y);
228
- const sx2 = Math.max(startPoint.x, endPoint.x);
229
- const sy2 = Math.max(startPoint.y, endPoint.y);
230
- const cropW = sx2 - sx1 + 1;
231
- const cropH = sy2 - sy1 + 1;
189
+ return null;
190
+ };
191
+ const startPoint = findPattern(0, 0, 1, 1, [
192
+ [0, 255, 0],
193
+ [0, 0, 255],
194
+ ]);
195
+ const endPoint = findPattern(w - 1, h - 1, -1, -1, [
196
+ [0, 255, 0],
197
+ [0, 0, 255],
198
+ ]);
199
+ if (!startPoint || !endPoint)
200
+ throw new Error('Patterns not found');
201
+ const sx1 = Math.min(startPoint.x, endPoint.x), sy1 = Math.min(startPoint.y, endPoint.y);
202
+ const sx2 = Math.max(startPoint.x, endPoint.x), sy2 = Math.max(startPoint.y, endPoint.y);
203
+ const cropW = sx2 - sx1 + 1, cropH = sy2 - sy1 + 1;
232
204
  if (cropW <= 0 || cropH <= 0)
233
205
  throw new Error('Invalid crop dimensions');
234
206
  const cropped = await sharp(doubledBuffer)
235
207
  .extract({ left: sx1, top: sy1, width: cropW, height: cropH })
236
208
  .png()
237
209
  .toBuffer();
238
- const { data: cdata, info: cinfo } = await sharp(cropped)
239
- .ensureAlpha()
240
- .raw()
241
- .toBuffer({ resolveWithObject: true });
242
- const cw = cinfo.width;
243
- const ch = cinfo.height;
244
- function cat(x, y) {
245
- const i = idxFor(x, y, cw);
246
- return [cdata[i], cdata[i + 1], cdata[i + 2], cdata[i + 3]];
247
- }
248
- function eq(a, b) {
249
- return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
250
- }
251
- function lineEq(l1, l2) {
252
- if (l1.length !== l2.length)
253
- return false;
254
- for (let i = 0; i < l1.length; i++)
255
- if (!eq(l1[i], l2[i]))
256
- return false;
257
- return true;
258
- }
259
- const newWidth = cw;
260
- const newHeight = ch + 1;
210
+ const { data: cdata, info: cinfo } = await loadRaw(cropped);
211
+ const cw = cinfo.width, ch = cinfo.height;
212
+ const newWidth = cw, newHeight = ch + 1;
261
213
  const out = Buffer.alloc(newWidth * newHeight * 4, 0);
262
214
  for (let i = 0; i < out.length; i += 4)
263
215
  out[i + 3] = 255;
264
216
  for (let y = 0; y < ch; y++) {
265
217
  for (let x = 0; x < cw; x++) {
266
- const srcI = ((y * cw + x) * 4) | 0;
267
- const dstI = ((y * newWidth + x) * 4) | 0;
218
+ const srcI = (y * cw + x) * 4;
219
+ const dstI = (y * newWidth + x) * 4;
268
220
  out[dstI] = cdata[srcI];
269
221
  out[dstI + 1] = cdata[srcI + 1];
270
222
  out[dstI + 2] = cdata[srcI + 2];
271
223
  out[dstI + 3] = cdata[srcI + 3];
272
224
  }
273
225
  }
274
- const targetY = ch - 1;
275
- for (let x = 0; x < cw; x++) {
276
- const i = ((targetY * newWidth + x) * 4) | 0;
277
- out[i] = 0;
278
- out[i + 1] = 0;
279
- out[i + 2] = 0;
280
- out[i + 3] = 255;
281
- }
282
- const lastY = ch;
283
226
  for (let x = 0; x < newWidth; x++) {
284
- const i = ((lastY * newWidth + x) * 4) | 0;
285
- out[i] = 0;
286
- out[i + 1] = 0;
287
- out[i + 2] = 0;
227
+ const i = ((ch - 1) * newWidth + x) * 4;
228
+ out[i] = out[i + 1] = out[i + 2] = 0;
288
229
  out[i + 3] = 255;
230
+ const j = (ch * newWidth + x) * 4;
231
+ out[j] = out[j + 1] = out[j + 2] = 0;
232
+ out[j + 3] = 255;
289
233
  }
290
234
  if (newWidth >= 3) {
291
235
  const bgrStart = newWidth - 3;
292
- let i = ((lastY * newWidth + bgrStart) * 4) | 0;
293
- out[i] = 0;
294
- out[i + 1] = 0;
295
- out[i + 2] = 255;
296
- out[i + 3] = 255;
297
- i = ((lastY * newWidth + bgrStart + 1) * 4) | 0;
298
- out[i] = 0;
299
- out[i + 1] = 255;
300
- out[i + 2] = 0;
301
- out[i + 3] = 255;
302
- i = ((lastY * newWidth + bgrStart + 2) * 4) | 0;
303
- out[i] = 255;
304
- out[i + 1] = 0;
305
- out[i + 2] = 0;
306
- out[i + 3] = 255;
236
+ const bgr = [
237
+ [0, 0, 255],
238
+ [0, 255, 0],
239
+ [255, 0, 0],
240
+ ];
241
+ for (let k = 0; k < 3; k++) {
242
+ const i = (ch * newWidth + bgrStart + k) * 4;
243
+ out[i] = bgr[k][0];
244
+ out[i + 1] = bgr[k][1];
245
+ out[i + 2] = bgr[k][2];
246
+ out[i + 3] = 255;
247
+ }
307
248
  }
308
- function getPixel(x, y) {
309
- const i = ((y * newWidth + x) * 4) | 0;
249
+ const getPixel = (x, y) => {
250
+ const i = (y * newWidth + x) * 4;
310
251
  return [out[i], out[i + 1], out[i + 2], out[i + 3]];
311
- }
252
+ };
312
253
  const compressedLines = [];
313
254
  for (let y = 0; y < newHeight; y++) {
314
255
  const line = [];
315
- let x = 0;
316
- while (x < newWidth) {
317
- const current = getPixel(x, y);
318
- if (current[0] === 0 && current[1] === 0 && current[2] === 0) {
319
- x++;
320
- continue;
321
- }
322
- line.push(current);
323
- let nx = x + 1;
324
- while (nx < newWidth && eq(getPixel(nx, y), current))
325
- nx++;
326
- x = nx;
327
- }
328
- if (line.length === 0)
329
- continue;
330
- if (compressedLines.length === 0 ||
331
- !lineEq(compressedLines[compressedLines.length - 1], line))
256
+ for (let x = 0; x < newWidth; x++)
257
+ line.push(getPixel(x, y));
258
+ const isAllBlack = line.every((p) => p[0] === 0 && p[1] === 0 && p[2] === 0 && p[3] === 255);
259
+ if (!isAllBlack &&
260
+ (compressedLines.length === 0 ||
261
+ !line.every((p, i) => p.every((v, j) => v === compressedLines[compressedLines.length - 1][i][j])))) {
332
262
  compressedLines.push(line);
263
+ }
333
264
  }
334
265
  if (compressedLines.length === 0) {
335
266
  return sharp({
@@ -343,49 +274,116 @@ export async function cropAndReconstitute(input, debugDir) {
343
274
  .png()
344
275
  .toBuffer();
345
276
  }
346
- const finalWidth = Math.max(...compressedLines.map((l) => l.length));
347
- const finalHeight = compressedLines.length;
348
- const finalOut = Buffer.alloc(finalWidth * finalHeight * 4, 0);
277
+ let finalWidth = newWidth, finalHeight = compressedLines.length;
278
+ let finalOut = Buffer.alloc(finalWidth * finalHeight * 4, 0);
349
279
  for (let i = 0; i < finalOut.length; i += 4)
350
280
  finalOut[i + 3] = 255;
351
- for (let y = 0; y < compressedLines.length; y++) {
352
- const line = compressedLines[y];
353
- const isLastLine = y === compressedLines.length - 1;
354
- const startX = isLastLine ? finalWidth - line.length : 0;
355
- for (let x = 0; x < line.length; x++) {
356
- const i = ((y * finalWidth + startX + x) * 4) | 0;
357
- finalOut[i] = line[x][0];
358
- finalOut[i + 1] = line[x][1];
359
- finalOut[i + 2] = line[x][2];
360
- finalOut[i + 3] = line[x][3] === 0 ? 255 : line[x][3];
281
+ for (let y = 0; y < finalHeight; y++) {
282
+ for (let x = 0; x < finalWidth; x++) {
283
+ const i = (y * finalWidth + x) * 4;
284
+ finalOut[i] = compressedLines[y][x][0];
285
+ finalOut[i + 1] = compressedLines[y][x][1];
286
+ finalOut[i + 2] = compressedLines[y][x][2];
287
+ finalOut[i + 3] = compressedLines[y][x][3] || 255;
361
288
  }
362
289
  }
363
- if (finalHeight >= 2) {
364
- const secondLastY = finalHeight - 2;
290
+ if (finalHeight >= 1 && finalWidth >= 3) {
291
+ const lastY = finalHeight - 1;
292
+ for (let k = 0; k < 3; k++) {
293
+ const i = (lastY * finalWidth + finalWidth - 3 + k) * 4;
294
+ finalOut[i] = finalOut[i + 1] = finalOut[i + 2] = 0;
295
+ finalOut[i + 3] = 255;
296
+ }
297
+ }
298
+ if (finalWidth >= 2) {
299
+ const kept = [];
365
300
  for (let x = 0; x < finalWidth; x++) {
366
- const i = ((secondLastY * finalWidth + x) * 4) | 0;
367
- const r = finalOut[i];
368
- const g = finalOut[i + 1];
369
- const b = finalOut[i + 2];
370
- if ((r === 255 && g === 0 && b === 0) ||
371
- (r === 0 && g === 255 && b === 0) ||
372
- (r === 0 && g === 0 && b === 255)) {
373
- finalOut[i] = 0;
374
- finalOut[i + 1] = 0;
375
- finalOut[i + 2] = 0;
301
+ if (kept.length === 0) {
302
+ kept.push(x);
303
+ continue;
304
+ }
305
+ const prevX = kept[kept.length - 1];
306
+ let same = true;
307
+ for (let y = 0; y < finalHeight; y++) {
308
+ const ia = (y * finalWidth + prevX) * 4, ib = (y * finalWidth + x) * 4;
309
+ if (finalOut[ia] !== finalOut[ib] ||
310
+ finalOut[ia + 1] !== finalOut[ib + 1] ||
311
+ finalOut[ia + 2] !== finalOut[ib + 2] ||
312
+ finalOut[ia + 3] !== finalOut[ib + 3]) {
313
+ same = false;
314
+ break;
315
+ }
316
+ }
317
+ if (!same)
318
+ kept.push(x);
319
+ }
320
+ if (kept.length !== finalWidth) {
321
+ const newFinalWidth = kept.length;
322
+ const newOut = Buffer.alloc(newFinalWidth * finalHeight * 4, 0);
323
+ for (let i = 0; i < newOut.length; i += 4)
324
+ newOut[i + 3] = 255;
325
+ for (let nx = 0; nx < kept.length; nx++) {
326
+ const sx = kept[nx];
327
+ for (let y = 0; y < finalHeight; y++) {
328
+ const srcI = (y * finalWidth + sx) * 4, dstI = (y * newFinalWidth + nx) * 4;
329
+ newOut[dstI] = finalOut[srcI];
330
+ newOut[dstI + 1] = finalOut[srcI + 1];
331
+ newOut[dstI + 2] = finalOut[srcI + 2];
332
+ newOut[dstI + 3] = finalOut[srcI + 3];
333
+ }
334
+ }
335
+ finalOut = newOut;
336
+ finalWidth = newFinalWidth;
337
+ }
338
+ }
339
+ if (finalHeight >= 2 && finalWidth >= 3) {
340
+ const secondLastY = finalHeight - 2;
341
+ const bgrSeq = [
342
+ [0, 0, 255],
343
+ [0, 255, 0],
344
+ [255, 0, 0],
345
+ ];
346
+ let hasBGR = true;
347
+ for (let k = 0; k < 3; k++) {
348
+ const i = (secondLastY * finalWidth + finalWidth - 3 + k) * 4;
349
+ if (finalOut[i] !== bgrSeq[k][0] ||
350
+ finalOut[i + 1] !== bgrSeq[k][1] ||
351
+ finalOut[i + 2] !== bgrSeq[k][2]) {
352
+ hasBGR = false;
353
+ break;
354
+ }
355
+ }
356
+ if (hasBGR) {
357
+ for (let k = 0; k < 3; k++) {
358
+ const i = (secondLastY * finalWidth + finalWidth - 3 + k) * 4;
359
+ finalOut[i] = finalOut[i + 1] = finalOut[i + 2] = 0;
376
360
  finalOut[i + 3] = 255;
377
361
  }
378
362
  }
379
363
  }
380
- const resultBuffer = await sharp(finalOut, {
364
+ if (finalHeight >= 1 && finalWidth >= 1) {
365
+ const lastYFinal = finalHeight - 1;
366
+ const bgrSeq = [
367
+ [0, 0, 255],
368
+ [0, 255, 0],
369
+ [255, 0, 0],
370
+ ];
371
+ for (let k = 0; k < 3; k++) {
372
+ const sx = finalWidth - 3 + k;
373
+ if (sx >= 0) {
374
+ const i = (lastYFinal * finalWidth + sx) * 4;
375
+ finalOut[i] = bgrSeq[k][0];
376
+ finalOut[i + 1] = bgrSeq[k][1];
377
+ finalOut[i + 2] = bgrSeq[k][2];
378
+ finalOut[i + 3] = 255;
379
+ }
380
+ }
381
+ }
382
+ return sharp(finalOut, {
381
383
  raw: { width: finalWidth, height: finalHeight, channels: 4 },
382
384
  })
383
385
  .png()
384
386
  .toBuffer();
385
- if (debugDir) {
386
- await sharp(resultBuffer).toFile(join(debugDir, 'reconstructed.png'));
387
- }
388
- return resultBuffer;
389
387
  }
390
388
  /**
391
389
  * Encode a Buffer into a PNG wrapper. Supports optional compression and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roxify",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
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",