quadqr-js 1.5.2 → 1.5.4
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 +3 -3
- package/SPECIFICATION.md +2 -2
- package/dist/camera-scanner-worker.js +3 -2
- package/dist/esm/camera-scanner-worker.js +3 -2
- package/dist/esm/quadqr.js +30 -14
- package/dist/quadqr.js +30 -14
- package/dist/quadqr.min.js +30 -14
- package/docs/API.md +3 -3
- package/docs/CLI.md +167 -167
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -258,7 +258,7 @@ Compression modes are `none`, `auto`, `smart`, `brotli`, `deflate`, and `lz`. `a
|
|
|
258
258
|
|
|
259
259
|
Signing can also be composed with Secure Payload. QuadQR compresses if requested, signs the normal payload with the private key, then encrypts the protected bytes with AES-256-GCM. A verifier supplies the trusted public key separately, or resolves it from `keyId`.
|
|
260
260
|
|
|
261
|
-
The renderer supports an explicit `mode: "print"`. Print mode
|
|
261
|
+
The renderer supports an explicit `mode: "print"`. Print mode uses darker print-safe RGB defaults and prefers Classic solid modules, while `quietZone` behaves exactly as it does in screen mode. Four modules remains the recommended default. `getPrintGuidance()` converts a chosen physical size into module millimeters/pixels so print layouts can be checked before production testing.
|
|
262
262
|
|
|
263
263
|
Centered logos support `size: "auto"`, which estimates a conservative ECC-aware ratio from code utilization and rendering choices. `findMaxSafeLogoSize()` can additionally probe ImageData output and empirically search for the largest size that still decodes.
|
|
264
264
|
|
|
@@ -1104,7 +1104,7 @@ Scans one frame from an HTML video element. By default, if the video is displaye
|
|
|
1104
1104
|
|
|
1105
1105
|
### `startCameraScanner(video, options?)`
|
|
1106
1106
|
|
|
1107
|
-
Starts a reusable live-camera scanning loop. On supported browsers it requests continuous focus/exposure/white-balance camera modes and scans the CSS-visible preview crop. Modern browsers use a **dual-worker camera engine**: a lightweight fresh-frame worker continuously runs normal finder/geometry/decode attempts, while an independent recovery worker retains the complete high-resolution, Auto Color, precise-alignment, perspective, multi-frame, ECC, and damaged-code recovery stack. A slow recovery attempt therefore cannot prevent the fast worker from inspecting a newer camera frame. Finder detection remains JavaScript; optional WASM accelerates grayscale/binary preprocessing and CRC beneath the same detector. The scheduler uses `requestVideoFrameCallback()` when available and does not queue stale fast-path frames. Normal camera acquisition requests an environment camera around 1280×720 and crops/resizes the visible preview to a 640 px working bitmap **before** transferring it to the worker. Once a candidate validates structure, Spectrum ECC, and CRC, scanning returns immediately. If the fast worker misses, full recovery runs concurrently on a fresh frame at up to 960 px. Strong finder evidence dispatches recovery quickly
|
|
1107
|
+
Starts a reusable live-camera scanning loop. On supported browsers it requests continuous focus/exposure/white-balance camera modes and scans the CSS-visible preview crop. Modern browsers use a **dual-worker camera engine**: a lightweight fresh-frame worker continuously runs normal finder/geometry/decode attempts, while an independent recovery worker retains the complete high-resolution, Auto Color, precise-alignment, perspective, multi-frame, ECC, and damaged-code recovery stack. A slow recovery attempt therefore cannot prevent the fast worker from inspecting a newer camera frame. Finder detection remains JavaScript; optional WASM accelerates grayscale/binary preprocessing and CRC beneath the same detector. The scheduler uses `requestVideoFrameCallback()` when available and does not queue stale fast-path frames. Normal camera acquisition requests an environment camera around 1280×720 and crops/resizes the visible preview to a 640 px working bitmap **before** transferring it to the worker. Once a candidate validates structure, Spectrum ECC, and CRC, scanning returns immediately. If the fast worker misses, full recovery runs concurrently on a fresh frame at up to 960 px. Strong finder evidence dispatches recovery quickly. Finder-less frames remain on the lightweight fresh-frame detector only, so pointing the camera at an empty scene never starts Auto Color or deeper recovery work. QuadQR Auto Color crop profiles, center-weighted histograms, threshold bracketing, precise alignment, projective recovery, QR-region enhancement, multi-frame confidence fusion, and soft-decision Spectrum ECC are unchanged. `cameraHighResolutionMaxDimension` defaults to 960. The optional `onDiagnostic(event)` callback exposes whether an event came from the fast or recovery worker, finder candidates, active locator method, crop/geometry/version hypothesis, recovery method, timing, and scan dimensions. `onResult(result, frame)` receives the exact frame that decoded, including enhanced recovery pixels when applicable, so UIs can keep the frozen frame and finder overlay aligned.
|
|
1108
1108
|
|
|
1109
1109
|
### `getVersionInfo(version, options?)`
|
|
1110
1110
|
|
|
@@ -1281,4 +1281,4 @@ AGPL v3.0. See `LICENSE`.
|
|
|
1281
1281
|
|
|
1282
1282
|
**Experimental / research project**
|
|
1283
1283
|
|
|
1284
|
-
QuadQR is actively evolving. Format details may change between versions until the wire format is considered stable.
|
|
1284
|
+
QuadQR is actively evolving. Format details may change between versions until the wire format is considered stable.
|
package/SPECIFICATION.md
CHANGED
|
@@ -253,9 +253,9 @@ inset
|
|
|
253
253
|
|
|
254
254
|
### Print mode
|
|
255
255
|
|
|
256
|
-
`mode: "print"` applies
|
|
256
|
+
`mode: "print"` applies print-oriented defaults:
|
|
257
257
|
|
|
258
|
-
-
|
|
258
|
+
- caller-controlled quiet-zone sizing, identical to screen mode (4 modules recommended by default);
|
|
259
259
|
- print-safe darker RGB primaries;
|
|
260
260
|
- Classic solid-module rendering by default;
|
|
261
261
|
- physical-size guidance through `getPrintGuidance()`.
|
|
@@ -326,9 +326,10 @@ function processFrame(bitmap, source, frameNumber) {
|
|
|
326
326
|
let allowFinderRecovery = false;
|
|
327
327
|
let allowAutoEnhance = false;
|
|
328
328
|
try {
|
|
329
|
-
|
|
329
|
+
const fastPipeline = scanOptions.cameraPipelineMode === "fast";
|
|
330
|
+
allowFinderRecovery = !fastPipeline && scanOptions.finderRecovery !== false &&
|
|
330
331
|
missStreak > 0 && ((missStreak - 1) % cameraFinderRecoveryEvery === 0);
|
|
331
|
-
allowAutoEnhance = scanOptions.autoEnhanceRecovery !== false &&
|
|
332
|
+
allowAutoEnhance = !fastPipeline && scanOptions.autoEnhanceRecovery !== false &&
|
|
332
333
|
missStreak > 0 && ((missStreak - 1) % cameraAutoEnhanceEvery === 0);
|
|
333
334
|
const method = allowAutoEnhance
|
|
334
335
|
? "progressive-color-recovery"
|
|
@@ -326,9 +326,10 @@ function processFrame(bitmap, source, frameNumber) {
|
|
|
326
326
|
let allowFinderRecovery = false;
|
|
327
327
|
let allowAutoEnhance = false;
|
|
328
328
|
try {
|
|
329
|
-
|
|
329
|
+
const fastPipeline = scanOptions.cameraPipelineMode === "fast";
|
|
330
|
+
allowFinderRecovery = !fastPipeline && scanOptions.finderRecovery !== false &&
|
|
330
331
|
missStreak > 0 && ((missStreak - 1) % cameraFinderRecoveryEvery === 0);
|
|
331
|
-
allowAutoEnhance = scanOptions.autoEnhanceRecovery !== false &&
|
|
332
|
+
allowAutoEnhance = !fastPipeline && scanOptions.autoEnhanceRecovery !== false &&
|
|
332
333
|
missStreak > 0 && ((missStreak - 1) % cameraAutoEnhanceEvery === 0);
|
|
333
334
|
const method = allowAutoEnhance
|
|
334
335
|
? "progressive-color-recovery"
|
package/dist/esm/quadqr.js
CHANGED
|
@@ -3056,10 +3056,7 @@ function svgLogoHref(source) {
|
|
|
3056
3056
|
|
|
3057
3057
|
function resolveRenderSizing(options, matrixSize) {
|
|
3058
3058
|
const mode = normalizeRenderMode(options.mode ?? options.renderMode ?? RENDER_MODES.SCREEN);
|
|
3059
|
-
const
|
|
3060
|
-
const quietZone = mode === RENDER_MODES.PRINT && options.allowUnsafePrintQuietZone !== true
|
|
3061
|
-
? Math.max(4, requestedQuietZone)
|
|
3062
|
-
: requestedQuietZone;
|
|
3059
|
+
const quietZone = Math.max(0, Math.floor(options.quietZone ?? 4));
|
|
3063
3060
|
const totalModules = matrixSize + quietZone * 2;
|
|
3064
3061
|
const hasImageSize = options.imageSize !== undefined && options.imageSize !== null;
|
|
3065
3062
|
const hasModuleSize = options.moduleSize !== undefined && options.moduleSize !== null;
|
|
@@ -6422,6 +6419,12 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
6422
6419
|
const fastWorkerOptions = {
|
|
6423
6420
|
...options,
|
|
6424
6421
|
cameraPipelineMode: "fast",
|
|
6422
|
+
// The fresh-frame worker is intentionally detection/decode only. Recovery
|
|
6423
|
+
// must never become more expensive merely because the camera has been
|
|
6424
|
+
// looking at an empty scene for a while. The parallel recovery worker is
|
|
6425
|
+
// armed only after finder/geometry evidence says a QuadQR candidate is in
|
|
6426
|
+
// view.
|
|
6427
|
+
finderRecovery: false,
|
|
6425
6428
|
cameraHighResolutionRecovery: false,
|
|
6426
6429
|
cameraAutoColorRecovery: false,
|
|
6427
6430
|
autoEnhanceRecovery: false,
|
|
@@ -6464,7 +6467,7 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
6464
6467
|
);
|
|
6465
6468
|
const recoveryStrongFinderInterval = Math.max(80, Number(options.cameraRecoveryStrongFinderInterval ?? 120));
|
|
6466
6469
|
const recoveryWeakFinderInterval = Math.max(recoveryStrongFinderInterval, Number(options.cameraRecoveryWeakFinderInterval ?? 260));
|
|
6467
|
-
const
|
|
6470
|
+
const recoveryWeakFinderFrames = Math.max(2, Math.round(options.cameraRecoveryWeakFinderFrames ?? 2));
|
|
6468
6471
|
const useVideoFrameCallback = options.useVideoFrameCallback !== false &&
|
|
6469
6472
|
typeof video.requestVideoFrameCallback === "function";
|
|
6470
6473
|
|
|
@@ -6481,6 +6484,7 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
6481
6484
|
let frameNumber = 0;
|
|
6482
6485
|
let requestToken = 0;
|
|
6483
6486
|
let recoveryToken = 0;
|
|
6487
|
+
let weakFinderStreak = 0;
|
|
6484
6488
|
|
|
6485
6489
|
const diagnosticsEnabled = typeof options.onDiagnostic === "function";
|
|
6486
6490
|
const emitDiagnostic = (event) => {
|
|
@@ -6532,7 +6536,7 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
6532
6536
|
emitDiagnostic({
|
|
6533
6537
|
type: "camera-ready",
|
|
6534
6538
|
method: "camera-dual-worker",
|
|
6535
|
-
message: `Camera ready · ${settings.width ?? video.videoWidth}×${settings.height ?? video.videoHeight} · fast fresh-frame scanner + parallel recovery`,
|
|
6539
|
+
message: `Camera ready · ${settings.width ?? video.videoWidth}×${settings.height ?? video.videoHeight} · fast fresh-frame scanner + candidate-gated parallel recovery`,
|
|
6536
6540
|
camera: {
|
|
6537
6541
|
width: settings.width ?? video.videoWidth,
|
|
6538
6542
|
height: settings.height ?? video.videoHeight,
|
|
@@ -6616,9 +6620,7 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
6616
6620
|
method: "parallel-full-recovery",
|
|
6617
6621
|
frame: triggerFrame,
|
|
6618
6622
|
finderCount,
|
|
6619
|
-
message: finderCount
|
|
6620
|
-
? `Fast frame saw ${finderCount} finder${finderCount === 1 ? "" : "s"} · full recovery running in parallel`
|
|
6621
|
-
: "Periodic full recovery running in parallel while fast fresh-frame scanning continues"
|
|
6623
|
+
message: `QuadQR candidate detected (${finderCount} finder${finderCount === 1 ? "" : "s"}) · full recovery running in parallel`
|
|
6622
6624
|
});
|
|
6623
6625
|
|
|
6624
6626
|
const recoveryPayload = { bitmap, source: captured.source, frame: triggerFrame };
|
|
@@ -6661,12 +6663,26 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
6661
6663
|
const maybeDispatchRecovery = (workerResult, triggerFrame) => {
|
|
6662
6664
|
if (stopped || recoveryBusy) return;
|
|
6663
6665
|
const finderCount = maximumFinderCount(workerResult);
|
|
6666
|
+
|
|
6667
|
+
// Do not run Auto Color, high-resolution, multi-frame, or damaged-code
|
|
6668
|
+
// recovery just because time has passed. An empty scene stays on the cheap
|
|
6669
|
+
// fresh-frame detector forever. Two or more finders are strong evidence and
|
|
6670
|
+
// arm recovery immediately. One finder is treated as weak evidence and must
|
|
6671
|
+
// persist across consecutive fresh frames before recovery is allowed.
|
|
6672
|
+
let minimumInterval = null;
|
|
6673
|
+
if (finderCount >= 2) {
|
|
6674
|
+
weakFinderStreak = 0;
|
|
6675
|
+
minimumInterval = recoveryStrongFinderInterval;
|
|
6676
|
+
} else if (finderCount === 1) {
|
|
6677
|
+
weakFinderStreak++;
|
|
6678
|
+
if (weakFinderStreak < recoveryWeakFinderFrames) return;
|
|
6679
|
+
minimumInterval = recoveryWeakFinderInterval;
|
|
6680
|
+
} else {
|
|
6681
|
+
weakFinderStreak = 0;
|
|
6682
|
+
return;
|
|
6683
|
+
}
|
|
6684
|
+
|
|
6664
6685
|
const elapsed = nowMs() - lastRecoveryStartedAt;
|
|
6665
|
-
const minimumInterval = finderCount >= 2
|
|
6666
|
-
? recoveryStrongFinderInterval
|
|
6667
|
-
: finderCount === 1
|
|
6668
|
-
? recoveryWeakFinderInterval
|
|
6669
|
-
: recoveryNoFinderInterval;
|
|
6670
6686
|
if (elapsed >= minimumInterval) void runRecovery(triggerFrame, finderCount);
|
|
6671
6687
|
};
|
|
6672
6688
|
|
package/dist/quadqr.js
CHANGED
|
@@ -9241,10 +9241,7 @@ function svgLogoHref(source) {
|
|
|
9241
9241
|
|
|
9242
9242
|
function resolveRenderSizing(options, matrixSize) {
|
|
9243
9243
|
const mode = normalizeRenderMode(options.mode ?? options.renderMode ?? RENDER_MODES.SCREEN);
|
|
9244
|
-
const
|
|
9245
|
-
const quietZone = mode === RENDER_MODES.PRINT && options.allowUnsafePrintQuietZone !== true
|
|
9246
|
-
? Math.max(4, requestedQuietZone)
|
|
9247
|
-
: requestedQuietZone;
|
|
9244
|
+
const quietZone = Math.max(0, Math.floor(options.quietZone ?? 4));
|
|
9248
9245
|
const totalModules = matrixSize + quietZone * 2;
|
|
9249
9246
|
const hasImageSize = options.imageSize !== undefined && options.imageSize !== null;
|
|
9250
9247
|
const hasModuleSize = options.moduleSize !== undefined && options.moduleSize !== null;
|
|
@@ -12607,6 +12604,12 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
12607
12604
|
const fastWorkerOptions = {
|
|
12608
12605
|
...options,
|
|
12609
12606
|
cameraPipelineMode: "fast",
|
|
12607
|
+
// The fresh-frame worker is intentionally detection/decode only. Recovery
|
|
12608
|
+
// must never become more expensive merely because the camera has been
|
|
12609
|
+
// looking at an empty scene for a while. The parallel recovery worker is
|
|
12610
|
+
// armed only after finder/geometry evidence says a QuadQR candidate is in
|
|
12611
|
+
// view.
|
|
12612
|
+
finderRecovery: false,
|
|
12610
12613
|
cameraHighResolutionRecovery: false,
|
|
12611
12614
|
cameraAutoColorRecovery: false,
|
|
12612
12615
|
autoEnhanceRecovery: false,
|
|
@@ -12649,7 +12652,7 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
12649
12652
|
);
|
|
12650
12653
|
const recoveryStrongFinderInterval = Math.max(80, Number(options.cameraRecoveryStrongFinderInterval ?? 120));
|
|
12651
12654
|
const recoveryWeakFinderInterval = Math.max(recoveryStrongFinderInterval, Number(options.cameraRecoveryWeakFinderInterval ?? 260));
|
|
12652
|
-
const
|
|
12655
|
+
const recoveryWeakFinderFrames = Math.max(2, Math.round(options.cameraRecoveryWeakFinderFrames ?? 2));
|
|
12653
12656
|
const useVideoFrameCallback = options.useVideoFrameCallback !== false &&
|
|
12654
12657
|
typeof video.requestVideoFrameCallback === "function";
|
|
12655
12658
|
|
|
@@ -12666,6 +12669,7 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
12666
12669
|
let frameNumber = 0;
|
|
12667
12670
|
let requestToken = 0;
|
|
12668
12671
|
let recoveryToken = 0;
|
|
12672
|
+
let weakFinderStreak = 0;
|
|
12669
12673
|
|
|
12670
12674
|
const diagnosticsEnabled = typeof options.onDiagnostic === "function";
|
|
12671
12675
|
const emitDiagnostic = (event) => {
|
|
@@ -12717,7 +12721,7 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
12717
12721
|
emitDiagnostic({
|
|
12718
12722
|
type: "camera-ready",
|
|
12719
12723
|
method: "camera-dual-worker",
|
|
12720
|
-
message: `Camera ready · ${settings.width ?? video.videoWidth}×${settings.height ?? video.videoHeight} · fast fresh-frame scanner + parallel recovery`,
|
|
12724
|
+
message: `Camera ready · ${settings.width ?? video.videoWidth}×${settings.height ?? video.videoHeight} · fast fresh-frame scanner + candidate-gated parallel recovery`,
|
|
12721
12725
|
camera: {
|
|
12722
12726
|
width: settings.width ?? video.videoWidth,
|
|
12723
12727
|
height: settings.height ?? video.videoHeight,
|
|
@@ -12801,9 +12805,7 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
12801
12805
|
method: "parallel-full-recovery",
|
|
12802
12806
|
frame: triggerFrame,
|
|
12803
12807
|
finderCount,
|
|
12804
|
-
message: finderCount
|
|
12805
|
-
? `Fast frame saw ${finderCount} finder${finderCount === 1 ? "" : "s"} · full recovery running in parallel`
|
|
12806
|
-
: "Periodic full recovery running in parallel while fast fresh-frame scanning continues"
|
|
12808
|
+
message: `QuadQR candidate detected (${finderCount} finder${finderCount === 1 ? "" : "s"}) · full recovery running in parallel`
|
|
12807
12809
|
});
|
|
12808
12810
|
|
|
12809
12811
|
const recoveryPayload = { bitmap, source: captured.source, frame: triggerFrame };
|
|
@@ -12846,12 +12848,26 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
12846
12848
|
const maybeDispatchRecovery = (workerResult, triggerFrame) => {
|
|
12847
12849
|
if (stopped || recoveryBusy) return;
|
|
12848
12850
|
const finderCount = maximumFinderCount(workerResult);
|
|
12851
|
+
|
|
12852
|
+
// Do not run Auto Color, high-resolution, multi-frame, or damaged-code
|
|
12853
|
+
// recovery just because time has passed. An empty scene stays on the cheap
|
|
12854
|
+
// fresh-frame detector forever. Two or more finders are strong evidence and
|
|
12855
|
+
// arm recovery immediately. One finder is treated as weak evidence and must
|
|
12856
|
+
// persist across consecutive fresh frames before recovery is allowed.
|
|
12857
|
+
let minimumInterval = null;
|
|
12858
|
+
if (finderCount >= 2) {
|
|
12859
|
+
weakFinderStreak = 0;
|
|
12860
|
+
minimumInterval = recoveryStrongFinderInterval;
|
|
12861
|
+
} else if (finderCount === 1) {
|
|
12862
|
+
weakFinderStreak++;
|
|
12863
|
+
if (weakFinderStreak < recoveryWeakFinderFrames) return;
|
|
12864
|
+
minimumInterval = recoveryWeakFinderInterval;
|
|
12865
|
+
} else {
|
|
12866
|
+
weakFinderStreak = 0;
|
|
12867
|
+
return;
|
|
12868
|
+
}
|
|
12869
|
+
|
|
12849
12870
|
const elapsed = nowMs() - lastRecoveryStartedAt;
|
|
12850
|
-
const minimumInterval = finderCount >= 2
|
|
12851
|
-
? recoveryStrongFinderInterval
|
|
12852
|
-
: finderCount === 1
|
|
12853
|
-
? recoveryWeakFinderInterval
|
|
12854
|
-
: recoveryNoFinderInterval;
|
|
12855
12871
|
if (elapsed >= minimumInterval) void runRecovery(triggerFrame, finderCount);
|
|
12856
12872
|
};
|
|
12857
12873
|
|
package/dist/quadqr.min.js
CHANGED
|
@@ -9071,10 +9071,7 @@ function svgLogoHref(source) {
|
|
|
9071
9071
|
|
|
9072
9072
|
function resolveRenderSizing(options, matrixSize) {
|
|
9073
9073
|
const mode = normalizeRenderMode(options.mode ?? options.renderMode ?? RENDER_MODES.SCREEN);
|
|
9074
|
-
const
|
|
9075
|
-
const quietZone = mode === RENDER_MODES.PRINT && options.allowUnsafePrintQuietZone !== true
|
|
9076
|
-
? Math.max(4, requestedQuietZone)
|
|
9077
|
-
: requestedQuietZone;
|
|
9074
|
+
const quietZone = Math.max(0, Math.floor(options.quietZone ?? 4));
|
|
9078
9075
|
const totalModules = matrixSize + quietZone * 2;
|
|
9079
9076
|
const hasImageSize = options.imageSize !== undefined && options.imageSize !== null;
|
|
9080
9077
|
const hasModuleSize = options.moduleSize !== undefined && options.moduleSize !== null;
|
|
@@ -12428,6 +12425,12 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
12428
12425
|
const fastWorkerOptions = {
|
|
12429
12426
|
...options,
|
|
12430
12427
|
cameraPipelineMode: "fast",
|
|
12428
|
+
// The fresh-frame worker is intentionally detection/decode only. Recovery
|
|
12429
|
+
// must never become more expensive merely because the camera has been
|
|
12430
|
+
// looking at an empty scene for a while. The parallel recovery worker is
|
|
12431
|
+
// armed only after finder/geometry evidence says a QuadQR candidate is in
|
|
12432
|
+
// view.
|
|
12433
|
+
finderRecovery: false,
|
|
12431
12434
|
cameraHighResolutionRecovery: false,
|
|
12432
12435
|
cameraAutoColorRecovery: false,
|
|
12433
12436
|
autoEnhanceRecovery: false,
|
|
@@ -12470,7 +12473,7 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
12470
12473
|
);
|
|
12471
12474
|
const recoveryStrongFinderInterval = Math.max(80, Number(options.cameraRecoveryStrongFinderInterval ?? 120));
|
|
12472
12475
|
const recoveryWeakFinderInterval = Math.max(recoveryStrongFinderInterval, Number(options.cameraRecoveryWeakFinderInterval ?? 260));
|
|
12473
|
-
const
|
|
12476
|
+
const recoveryWeakFinderFrames = Math.max(2, Math.round(options.cameraRecoveryWeakFinderFrames ?? 2));
|
|
12474
12477
|
const useVideoFrameCallback = options.useVideoFrameCallback !== false &&
|
|
12475
12478
|
typeof video.requestVideoFrameCallback === "function";
|
|
12476
12479
|
|
|
@@ -12487,6 +12490,7 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
12487
12490
|
let frameNumber = 0;
|
|
12488
12491
|
let requestToken = 0;
|
|
12489
12492
|
let recoveryToken = 0;
|
|
12493
|
+
let weakFinderStreak = 0;
|
|
12490
12494
|
|
|
12491
12495
|
const diagnosticsEnabled = typeof options.onDiagnostic === "function";
|
|
12492
12496
|
const emitDiagnostic = (event) => {
|
|
@@ -12538,7 +12542,7 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
12538
12542
|
emitDiagnostic({
|
|
12539
12543
|
type: "camera-ready",
|
|
12540
12544
|
method: "camera-dual-worker",
|
|
12541
|
-
message: `Camera ready · ${settings.width ?? video.videoWidth}×${settings.height ?? video.videoHeight} · fast fresh-frame scanner + parallel recovery`,
|
|
12545
|
+
message: `Camera ready · ${settings.width ?? video.videoWidth}×${settings.height ?? video.videoHeight} · fast fresh-frame scanner + candidate-gated parallel recovery`,
|
|
12542
12546
|
camera: {
|
|
12543
12547
|
width: settings.width ?? video.videoWidth,
|
|
12544
12548
|
height: settings.height ?? video.videoHeight,
|
|
@@ -12622,9 +12626,7 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
12622
12626
|
method: "parallel-full-recovery",
|
|
12623
12627
|
frame: triggerFrame,
|
|
12624
12628
|
finderCount,
|
|
12625
|
-
message: finderCount
|
|
12626
|
-
? `Fast frame saw ${finderCount} finder${finderCount === 1 ? "" : "s"} · full recovery running in parallel`
|
|
12627
|
-
: "Periodic full recovery running in parallel while fast fresh-frame scanning continues"
|
|
12629
|
+
message: `QuadQR candidate detected (${finderCount} finder${finderCount === 1 ? "" : "s"}) · full recovery running in parallel`
|
|
12628
12630
|
});
|
|
12629
12631
|
|
|
12630
12632
|
const recoveryPayload = { bitmap, source: captured.source, frame: triggerFrame };
|
|
@@ -12667,12 +12669,26 @@ async function startCameraScannerWorker(video, options = {}) {
|
|
|
12667
12669
|
const maybeDispatchRecovery = (workerResult, triggerFrame) => {
|
|
12668
12670
|
if (stopped || recoveryBusy) return;
|
|
12669
12671
|
const finderCount = maximumFinderCount(workerResult);
|
|
12672
|
+
|
|
12673
|
+
// Do not run Auto Color, high-resolution, multi-frame, or damaged-code
|
|
12674
|
+
// recovery just because time has passed. An empty scene stays on the cheap
|
|
12675
|
+
// fresh-frame detector forever. Two or more finders are strong evidence and
|
|
12676
|
+
// arm recovery immediately. One finder is treated as weak evidence and must
|
|
12677
|
+
// persist across consecutive fresh frames before recovery is allowed.
|
|
12678
|
+
let minimumInterval = null;
|
|
12679
|
+
if (finderCount >= 2) {
|
|
12680
|
+
weakFinderStreak = 0;
|
|
12681
|
+
minimumInterval = recoveryStrongFinderInterval;
|
|
12682
|
+
} else if (finderCount === 1) {
|
|
12683
|
+
weakFinderStreak++;
|
|
12684
|
+
if (weakFinderStreak < recoveryWeakFinderFrames) return;
|
|
12685
|
+
minimumInterval = recoveryWeakFinderInterval;
|
|
12686
|
+
} else {
|
|
12687
|
+
weakFinderStreak = 0;
|
|
12688
|
+
return;
|
|
12689
|
+
}
|
|
12690
|
+
|
|
12670
12691
|
const elapsed = nowMs() - lastRecoveryStartedAt;
|
|
12671
|
-
const minimumInterval = finderCount >= 2
|
|
12672
|
-
? recoveryStrongFinderInterval
|
|
12673
|
-
: finderCount === 1
|
|
12674
|
-
? recoveryWeakFinderInterval
|
|
12675
|
-
: recoveryNoFinderInterval;
|
|
12676
12692
|
if (elapsed >= minimumInterval) void runRecovery(triggerFrame, finderCount);
|
|
12677
12693
|
};
|
|
12678
12694
|
|
package/docs/API.md
CHANGED
|
@@ -209,7 +209,7 @@ const result = scanVideoFrame(video);
|
|
|
209
209
|
|
|
210
210
|
### `startCameraScanner(video, options?)`
|
|
211
211
|
|
|
212
|
-
Starts live camera scanning. On browsers that expose camera controls, QuadQR requests continuous autofocus, exposure, and white balance. Modern browsers use two module Web Workers by default: a **fast fresh-frame worker** for normal finder/geometry/decode attempts and a separate **full-recovery worker** for the same perspective, color, high-resolution, ECC, damaged-code, and multi-frame recovery stack used by the main scanner. The recovery worker is lazy and independent, so a difficult old frame cannot block the fast worker from inspecting a newer camera frame. Finder detection itself is JavaScript; WASM only accelerates deterministic grayscale/binary preprocessing and CRC. The loop uses `requestVideoFrameCallback()` when available, drops stale fast-path frames, and measures `scanInterval` from scan start. Worker mode defaults to a 33 ms minimum cadence; automatic main-thread fallback uses 80 ms. The default camera request is approximately 1280×720, while the CSS-visible preview crop is resized to a 640 px working bitmap before crossing the worker boundary. Finder acquisition uses the streaming 1:1:3:1:1 RGB-value pass, direct cross-checks, local-threshold fallback, and directional module-size/version estimation. Once a geometry candidate validates structure, ECC, and CRC, it returns immediately. A miss with strong finder evidence dispatches parallel full recovery quickly
|
|
212
|
+
Starts live camera scanning. On browsers that expose camera controls, QuadQR requests continuous autofocus, exposure, and white balance. Modern browsers use two module Web Workers by default: a **fast fresh-frame worker** for normal finder/geometry/decode attempts and a separate **full-recovery worker** for the same perspective, color, high-resolution, ECC, damaged-code, and multi-frame recovery stack used by the main scanner. The recovery worker is lazy and independent, so a difficult old frame cannot block the fast worker from inspecting a newer camera frame. Finder detection itself is JavaScript; WASM only accelerates deterministic grayscale/binary preprocessing and CRC. The loop uses `requestVideoFrameCallback()` when available, drops stale fast-path frames, and measures `scanInterval` from scan start. Worker mode defaults to a 33 ms minimum cadence; automatic main-thread fallback uses 80 ms. The default camera request is approximately 1280×720, while the CSS-visible preview crop is resized to a 640 px working bitmap before crossing the worker boundary. Finder acquisition uses the streaming 1:1:3:1:1 RGB-value pass, direct cross-checks, local-threshold fallback, and directional module-size/version estimation. Once a geometry candidate validates structure, ECC, and CRC, it returns immediately. A miss with strong finder evidence dispatches parallel full recovery quickly. One finder must persist across consecutive fresh frames before recovery is armed, while zero-finder frames stay on the lightweight detector only. That recovery can use up to 960 px and retains the full QuadQR Auto Color crop sequence, center-weighted color recovery, threshold bracketing, precise alignment, perspective recovery, affine cross-channel calibration, QR-region enhancement, multi-frame confidence fusion, erasure decoding, and soft-decision Spectrum ECC.
|
|
213
213
|
|
|
214
214
|
```js
|
|
215
215
|
const scanner = await startCameraScanner(video, {
|
|
@@ -235,7 +235,7 @@ const scanner = await startCameraScanner(video, {
|
|
|
235
235
|
cameraHighResolutionEvery: 2,
|
|
236
236
|
cameraRecoveryStrongFinderInterval: 120,
|
|
237
237
|
cameraRecoveryWeakFinderInterval: 260,
|
|
238
|
-
|
|
238
|
+
cameraRecoveryWeakFinderFrames: 2,
|
|
239
239
|
onResult(result) {
|
|
240
240
|
console.log(result);
|
|
241
241
|
},
|
|
@@ -531,7 +531,7 @@ All render APIs accept:
|
|
|
531
531
|
{ mode: "screen" | "print" }
|
|
532
532
|
```
|
|
533
533
|
|
|
534
|
-
Print mode uses a darker print-safe palette
|
|
534
|
+
Print mode uses a darker print-safe palette and forces Classic modules by default. `quietZone` has the same behavior in print and screen modes, including support for values below the recommended four-module default.
|
|
535
535
|
|
|
536
536
|
### `getPrintGuidance(codeOrMatrix, options?)`
|
|
537
537
|
|
package/docs/CLI.md
CHANGED
|
@@ -1,167 +1,167 @@
|
|
|
1
|
-
# Command Line Interface
|
|
2
|
-
|
|
3
|
-
The npm package includes the `quadqr` executable and can be used directly through `npx`.
|
|
4
|
-
|
|
5
|
-
## Encode text
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npx quadqr-js encode "Hello QuadQR" -o hello.png
|
|
9
|
-
npx quadqr-js encode "Hello QuadQR" -o hello.svg
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
Optional encoding controls:
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
npx quadqr-js encode "Hello" --ecc M --version auto --image-size 720 --quiet-zone 4 -o hello.png
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Use the print-safe rendering profile when the symbol is intended for physical output:
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
npx quadqr-js encode "Print me" --print -o print.svg
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Print mode
|
|
25
|
-
|
|
26
|
-
## Compression
|
|
27
|
-
|
|
28
|
-
Compression works directly with normal text payloads:
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
npx quadqr-js encode "repeat repeat repeat repeat" \
|
|
32
|
-
--compression auto \
|
|
33
|
-
-o compressed.png
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Compression modes are `none`, `auto`, `smart`, `brotli`, `deflate`, and `lz`. `auto` performs one balanced comparison using LZ level 6, DEFLATE level 6, and Brotli quality 6. `smart` is CPU-heavy: it starts with the same pass and only escalates to stronger DEFLATE/Brotli levels when a smaller QuadQR version is realistically reachable. If envelope overhead would erase the gain, Auto/Smart leave the original payload untouched. No separate payload mode is required.
|
|
37
|
-
|
|
38
|
-
Explicit codecs can select a level:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
npx quadqr-js encode "structured payload" --compression lz --compression-level 9 -o lz.png
|
|
42
|
-
npx quadqr-js encode "structured payload" --compression deflate --compression-level 9 -o deflate.png
|
|
43
|
-
npx quadqr-js encode "structured payload" --compression brotli --compression-level 11 -o brotli.png
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
LZ and DEFLATE accept levels `1..9` and default to 6. Brotli accepts qualities `0..11` and defaults to 11. `--compression-level` is ignored by Auto/Smart because those modes manage their own staged levels.
|
|
47
|
-
|
|
48
|
-
## Signed QuadQR
|
|
49
|
-
|
|
50
|
-
Generate an Ed25519 signing-key bundle:
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
npx quadqr-js signkeygen -o quadqr-signing-key.json
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Keep this file secret because it contains the private signing key. Encode a signed payload with:
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
npx quadqr-js encode "Offline-verifiable ticket" \
|
|
60
|
-
--sign-key quadqr-signing-key.json \
|
|
61
|
-
-o signed.png
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
The generated key bundle contains both keys plus a compact `keyId`. Only the private key signs. The public key is **not embedded** in the QuadQR by default and should be distributed separately to trusted scanners or stored in a trusted-key registry.
|
|
65
|
-
|
|
66
|
-
To override the identifier stored in the symbol:
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
npx quadqr-js encode "Offline-verifiable ticket" \
|
|
70
|
-
--sign-key quadqr-signing-key.json \
|
|
71
|
-
--key-id event-main-2026 \
|
|
72
|
-
-o signed.png
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Signing can be combined with password or raw-key encryption:
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
npx quadqr-js encode "Signed and private" \
|
|
79
|
-
--sign-key quadqr-signing-key.json \
|
|
80
|
-
--password "my-password" \
|
|
81
|
-
-o signed-secure.png
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## Decode an image
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
npx quadqr-js decode hello.png
|
|
88
|
-
npx quadqr-js decode signed.png --verify-key quadqr-signing-key.json
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
For an unencrypted text payload, the decoded text is printed to stdout. To verify a signed symbol against a trusted public key, pass the signing bundle with `--verify-key`.
|
|
92
|
-
|
|
93
|
-
Add scanner diagnostics without changing the normal stdout payload:
|
|
94
|
-
|
|
95
|
-
```bash
|
|
96
|
-
npx quadqr-js decode hello.png --debug
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Diagnostics are written to stderr and include confidence, geometry/color confidence, ECC utilization, signing state, and the scanner diagnostics object when available.
|
|
100
|
-
|
|
101
|
-
## Password-protected payloads
|
|
102
|
-
|
|
103
|
-
Encode:
|
|
104
|
-
|
|
105
|
-
```bash
|
|
106
|
-
npx quadqr-js encode "Private data" --password "my-password" -o secure.png
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Decode:
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
npx quadqr-js decode secure.png --password "my-password"
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
If an encrypted symbol is decoded without a credential, the CLI reports that decryption is required instead of exposing plaintext.
|
|
116
|
-
|
|
117
|
-
## Raw 256-bit key mode
|
|
118
|
-
|
|
119
|
-
Generate a random 256-bit encryption key:
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
npx quadqr-js keygen
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
The output is a 64-character hexadecimal key. Store it securely and do not place it inside the same QuadQR symbol.
|
|
126
|
-
|
|
127
|
-
Encode using the key:
|
|
128
|
-
|
|
129
|
-
```bash
|
|
130
|
-
npx quadqr-js encode "Application secret" --key <64-hex-key> -o secure-key.png
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
Decode using the key:
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
npx quadqr-js decode secure-key.png --key <64-hex-key>
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
## Options
|
|
140
|
-
|
|
141
|
-
| Option | Purpose |
|
|
142
|
-
| --- | --- |
|
|
143
|
-
| `-o, --output <file>` | Output PNG/SVG path, or signing-key JSON path for `signkeygen` |
|
|
144
|
-
| `--ecc <L|M|Q|H>` | QuadQR ECC profile. Default: `M` |
|
|
145
|
-
| `--version <auto|1..40>` | Symbol version. Default: `auto` |
|
|
146
|
-
| `--compression <mode>` | `none`, `auto`, `smart`, `brotli`, `deflate`, or `lz`. Default: `auto` |
|
|
147
|
-
| `--compression-level <n>` | Explicit LZ/DEFLATE `1..9` or Brotli `0..11` encoder level |
|
|
148
|
-
| `--high-density` | Enable experimental Triangle16 High Density Mode |
|
|
149
|
-
| `--sign-key <file>` | Sign using a key bundle generated by `signkeygen` |
|
|
150
|
-
| `--key-id <id>` | Override the signing key ID stored in the symbol |
|
|
151
|
-
| `--embed-public-key` | Explicit compatibility mode that embeds the public key |
|
|
152
|
-
| `--verify-key <file>` | Verify a signed symbol with a trusted Ed25519 key bundle |
|
|
153
|
-
| `--password <text>` | Password-mode encryption/decryption |
|
|
154
|
-
| `--key <hex>` | Raw 256-bit key encryption/decryption |
|
|
155
|
-
| `--print` | Use the print-safe render profile |
|
|
156
|
-
| `--image-size <px>` | Exact square output size in pixels. Default: `720` |
|
|
157
|
-
| `--module-size <px>` | Legacy pixels-per-module sizing. Used when `--image-size` is omitted |
|
|
158
|
-
| `--quiet-zone <modules>` | Quiet-zone size in modules. Default: `4` |
|
|
159
|
-
| `--debug` | Emit scanner diagnostics to stderr when decoding |
|
|
160
|
-
| `-h, --help` | Show CLI help |
|
|
161
|
-
|
|
162
|
-
Password mode and raw-key mode are mutually exclusive for a single operation.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
## High Density Mode (Experimental)
|
|
166
|
-
|
|
167
|
-
Use `--high-density` to enable the experimental Triangle16 layout with 16 states and 4 raw bits per body cell. Decode is automatic; no matching decode flag is required.
|
|
1
|
+
# Command Line Interface
|
|
2
|
+
|
|
3
|
+
The npm package includes the `quadqr` executable and can be used directly through `npx`.
|
|
4
|
+
|
|
5
|
+
## Encode text
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx quadqr-js encode "Hello QuadQR" -o hello.png
|
|
9
|
+
npx quadqr-js encode "Hello QuadQR" -o hello.svg
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Optional encoding controls:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx quadqr-js encode "Hello" --ecc M --version auto --image-size 720 --quiet-zone 4 -o hello.png
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Use the print-safe rendering profile when the symbol is intended for physical output:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx quadqr-js encode "Print me" --print -o print.svg
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Print mode uses the print-safe palette and Classic rendering defaults. `--quiet-zone` is honored unchanged, just as it is in screen mode; four modules remains recommended.
|
|
25
|
+
|
|
26
|
+
## Compression
|
|
27
|
+
|
|
28
|
+
Compression works directly with normal text payloads:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx quadqr-js encode "repeat repeat repeat repeat" \
|
|
32
|
+
--compression auto \
|
|
33
|
+
-o compressed.png
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Compression modes are `none`, `auto`, `smart`, `brotli`, `deflate`, and `lz`. `auto` performs one balanced comparison using LZ level 6, DEFLATE level 6, and Brotli quality 6. `smart` is CPU-heavy: it starts with the same pass and only escalates to stronger DEFLATE/Brotli levels when a smaller QuadQR version is realistically reachable. If envelope overhead would erase the gain, Auto/Smart leave the original payload untouched. No separate payload mode is required.
|
|
37
|
+
|
|
38
|
+
Explicit codecs can select a level:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx quadqr-js encode "structured payload" --compression lz --compression-level 9 -o lz.png
|
|
42
|
+
npx quadqr-js encode "structured payload" --compression deflate --compression-level 9 -o deflate.png
|
|
43
|
+
npx quadqr-js encode "structured payload" --compression brotli --compression-level 11 -o brotli.png
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
LZ and DEFLATE accept levels `1..9` and default to 6. Brotli accepts qualities `0..11` and defaults to 11. `--compression-level` is ignored by Auto/Smart because those modes manage their own staged levels.
|
|
47
|
+
|
|
48
|
+
## Signed QuadQR
|
|
49
|
+
|
|
50
|
+
Generate an Ed25519 signing-key bundle:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx quadqr-js signkeygen -o quadqr-signing-key.json
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Keep this file secret because it contains the private signing key. Encode a signed payload with:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx quadqr-js encode "Offline-verifiable ticket" \
|
|
60
|
+
--sign-key quadqr-signing-key.json \
|
|
61
|
+
-o signed.png
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The generated key bundle contains both keys plus a compact `keyId`. Only the private key signs. The public key is **not embedded** in the QuadQR by default and should be distributed separately to trusted scanners or stored in a trusted-key registry.
|
|
65
|
+
|
|
66
|
+
To override the identifier stored in the symbol:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx quadqr-js encode "Offline-verifiable ticket" \
|
|
70
|
+
--sign-key quadqr-signing-key.json \
|
|
71
|
+
--key-id event-main-2026 \
|
|
72
|
+
-o signed.png
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Signing can be combined with password or raw-key encryption:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx quadqr-js encode "Signed and private" \
|
|
79
|
+
--sign-key quadqr-signing-key.json \
|
|
80
|
+
--password "my-password" \
|
|
81
|
+
-o signed-secure.png
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Decode an image
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npx quadqr-js decode hello.png
|
|
88
|
+
npx quadqr-js decode signed.png --verify-key quadqr-signing-key.json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
For an unencrypted text payload, the decoded text is printed to stdout. To verify a signed symbol against a trusted public key, pass the signing bundle with `--verify-key`.
|
|
92
|
+
|
|
93
|
+
Add scanner diagnostics without changing the normal stdout payload:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npx quadqr-js decode hello.png --debug
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Diagnostics are written to stderr and include confidence, geometry/color confidence, ECC utilization, signing state, and the scanner diagnostics object when available.
|
|
100
|
+
|
|
101
|
+
## Password-protected payloads
|
|
102
|
+
|
|
103
|
+
Encode:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npx quadqr-js encode "Private data" --password "my-password" -o secure.png
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Decode:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npx quadqr-js decode secure.png --password "my-password"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
If an encrypted symbol is decoded without a credential, the CLI reports that decryption is required instead of exposing plaintext.
|
|
116
|
+
|
|
117
|
+
## Raw 256-bit key mode
|
|
118
|
+
|
|
119
|
+
Generate a random 256-bit encryption key:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npx quadqr-js keygen
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The output is a 64-character hexadecimal key. Store it securely and do not place it inside the same QuadQR symbol.
|
|
126
|
+
|
|
127
|
+
Encode using the key:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx quadqr-js encode "Application secret" --key <64-hex-key> -o secure-key.png
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Decode using the key:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npx quadqr-js decode secure-key.png --key <64-hex-key>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Options
|
|
140
|
+
|
|
141
|
+
| Option | Purpose |
|
|
142
|
+
| --- | --- |
|
|
143
|
+
| `-o, --output <file>` | Output PNG/SVG path, or signing-key JSON path for `signkeygen` |
|
|
144
|
+
| `--ecc <L|M|Q|H>` | QuadQR ECC profile. Default: `M` |
|
|
145
|
+
| `--version <auto|1..40>` | Symbol version. Default: `auto` |
|
|
146
|
+
| `--compression <mode>` | `none`, `auto`, `smart`, `brotli`, `deflate`, or `lz`. Default: `auto` |
|
|
147
|
+
| `--compression-level <n>` | Explicit LZ/DEFLATE `1..9` or Brotli `0..11` encoder level |
|
|
148
|
+
| `--high-density` | Enable experimental Triangle16 High Density Mode |
|
|
149
|
+
| `--sign-key <file>` | Sign using a key bundle generated by `signkeygen` |
|
|
150
|
+
| `--key-id <id>` | Override the signing key ID stored in the symbol |
|
|
151
|
+
| `--embed-public-key` | Explicit compatibility mode that embeds the public key |
|
|
152
|
+
| `--verify-key <file>` | Verify a signed symbol with a trusted Ed25519 key bundle |
|
|
153
|
+
| `--password <text>` | Password-mode encryption/decryption |
|
|
154
|
+
| `--key <hex>` | Raw 256-bit key encryption/decryption |
|
|
155
|
+
| `--print` | Use the print-safe render profile |
|
|
156
|
+
| `--image-size <px>` | Exact square output size in pixels. Default: `720` |
|
|
157
|
+
| `--module-size <px>` | Legacy pixels-per-module sizing. Used when `--image-size` is omitted |
|
|
158
|
+
| `--quiet-zone <modules>` | Quiet-zone size in modules. Default: `4` |
|
|
159
|
+
| `--debug` | Emit scanner diagnostics to stderr when decoding |
|
|
160
|
+
| `-h, --help` | Show CLI help |
|
|
161
|
+
|
|
162
|
+
Password mode and raw-key mode are mutually exclusive for a single operation.
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
## High Density Mode (Experimental)
|
|
166
|
+
|
|
167
|
+
Use `--high-density` to enable the experimental Triangle16 layout with 16 states and 4 raw bits per body cell. Decode is automatic; no matching decode flag is required.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quadqr-js",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.4",
|
|
4
4
|
"description": "QuadQR: experimental RGBW matrix code with optional experimental High Density Mode, Spectrum ECC 2.0, multi-frame camera scanning, advanced calibration, Reliability Lab, and 3D perspective recovery.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|