quadqr-js 1.2.0 → 1.4.2
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/FORMAT.md +37 -9
- package/README.md +98 -48
- package/SPECIFICATION.md +30 -10
- package/bin/quadqr.js +6 -2
- package/dist/esm/benchmark.js +71 -15
- package/dist/esm/brotli.js +2210 -0
- package/dist/esm/deflate.js +381 -0
- package/dist/esm/quadqr.js +1927 -343
- package/dist/esm/security.js +19 -0
- package/dist/esm/vision.js +660 -44
- package/dist/quadqr.js +5292 -489
- package/dist/quadqr.min.js +4987 -468
- package/docs/API.md +49 -12
- package/docs/BROWSER_CDN.md +9 -5
- package/docs/CLI.md +19 -2
- package/docs/COMPRESSION.md +170 -0
- package/docs/GETTING_STARTED.md +12 -2
- package/docs/HIGH_DENSITY_MODE.md +100 -0
- package/docs/NODE.md +22 -0
- package/docs/README.md +8 -5
- package/docs/RELIABILITY_LAB.md +64 -0
- package/docs/TRIANGLE16.md +98 -0
- package/docs/WASM.md +1 -1
- package/package.json +11 -10
- package/types/benchmark.d.ts +0 -8
- package/types/index.d.ts +0 -375
- package/types/node.d.ts +0 -11
package/docs/API.md
CHANGED
|
@@ -11,12 +11,15 @@ import { encodeText } from "quadqr-js";
|
|
|
11
11
|
|
|
12
12
|
const code = encodeText("Hello", {
|
|
13
13
|
ecc: "M",
|
|
14
|
-
version: 5
|
|
14
|
+
version: 5,
|
|
15
|
+
highDensity: true // optional experimental 4-bit cells
|
|
15
16
|
});
|
|
16
17
|
```
|
|
17
18
|
|
|
18
19
|
If `version` is omitted, QuadQR selects the smallest version that fits.
|
|
19
20
|
|
|
21
|
+
`highDensity` is a boolean. It defaults to `false`. Set `highDensity: true` to enable the experimental Triangle16 layout with two RGBW triangles, 16 states, and 4 raw bits per body cell. The protected header remains solid-color in High Density Mode. Image and camera scanners auto-detect it.
|
|
22
|
+
|
|
20
23
|
### `encodeBytes(bytes, options?)`
|
|
21
24
|
|
|
22
25
|
Encodes arbitrary bytes.
|
|
@@ -176,7 +179,7 @@ const svg = renderToSVG(code, {
|
|
|
176
179
|
|
|
177
180
|
### `scanImageData(imageData, options?)`
|
|
178
181
|
|
|
179
|
-
Scans an ImageData-like RGBA object. Clean frames use the normal observed-RGB path first. Difficult frames then get bounded fallback attempts using per-channel white balancing, spatial black/white normalization, tighter centre sampling, module-grid Auto Tone / Auto Contrast / Auto Color recovery, a rectified QR-region pixel enhancement pass, and sub-module geometry micro-refinement before the scan is rejected.
|
|
182
|
+
Scans an ImageData-like RGBA object. The scanner automatically attempts both normal RGBW center sampling and Triangle16 dual-region sampling when geometry is found. When normal projective geometry is locatable but too coarse for half-cell Triangle16 regions, one bounded precise-alignment recovery pass refines the alignment center at sub-module resolution. Clean frames use the normal observed-RGB path first. Difficult frames then get bounded fallback attempts using per-channel white balancing, spatial black/white normalization, tighter centre sampling, QuadQR module-grid Auto Tone / Auto Contrast / Auto Color recovery, a rectified QR-region pixel enhancement pass, and sub-module geometry micro-refinement before the scan is rejected. Dense versions also use their distributed alignment markers to refine a noisy four-point projective solution when the initial alignment-grid score is plausible but imperfect. If exactly two strong finder patterns survive a steep angle, a bounded perspective-tolerant third-finder recovery pass is attempted before heavier color recovery.
|
|
180
183
|
|
|
181
184
|
```js
|
|
182
185
|
const result = scanImageData({
|
|
@@ -186,7 +189,7 @@ const result = scanImageData({
|
|
|
186
189
|
});
|
|
187
190
|
```
|
|
188
191
|
|
|
189
|
-
Useful scanner options include `sampleRadius`, `robustSampleRadius`, `adaptiveSampling`, `spatialColorNormalization`, `autoEnhanceRecovery`, `rectifiedAutoEnhanceRecovery`, `rectifiedRecoveryModuleSize`, `
|
|
192
|
+
Useful scanner options include `sampleRadius`, `robustSampleRadius`, `adaptiveSampling`, `spatialColorNormalization`, `autoEnhanceRecovery`, `rectifiedAutoEnhanceRecovery`, `rectifiedRecoveryModuleSize`, `geometryRefinement`, `alignmentRefinement`, `structureTolerance`, `maxErasureConfidence`, `softDecoding`, `softDecodeConfidence`, `softDecodeMaxCells`, and `softDecodePairCells`. Spectrum ECC 2.0 soft decoding is bounded and only runs after ordinary hard/error-erasure decoding fails.
|
|
190
193
|
|
|
191
194
|
### Browser `scanFile(file, options?)`
|
|
192
195
|
|
|
@@ -206,13 +209,15 @@ const result = scanVideoFrame(video);
|
|
|
206
209
|
|
|
207
210
|
### `startCameraScanner(video, options?)`
|
|
208
211
|
|
|
209
|
-
Starts live camera scanning. On browsers that expose camera controls, QuadQR requests continuous autofocus, exposure, and white balance. It scans the CSS-visible preview region by default. Finder detection uses a QuadQR-specific RGB value channel (`max(R,G,B)`) on the fast pass so saturated blue/red/green data cells are not mistaken for structural black. If that
|
|
212
|
+
Starts live camera scanning. On browsers that expose camera controls, QuadQR requests continuous autofocus, exposure, and white balance. It scans the CSS-visible preview region by default. Finder detection uses a QuadQR-specific RGB value channel (`max(R,G,B)`) on the fast pass so saturated blue/red/green data cells are not mistaken for structural black. If a miss still contains at least two strong finder patterns, the scanner can retry the visible ROI at up to 1600 px before heavier recovery, which preserves more pixels per module for dense versions. If that does not decode, the **same captured frame** is retried through QuadQR code-centric Auto Color recovery. The default recovery sequence crops 8%, 16%, and 22% from the camera-frame edges, then falls back to the full frame. This prevents dark room pixels, browser UI, or monitor bezels outside the guide from controlling QuadQR Auto Color and Otsu thresholds. Each crop uses the QuadQR-specific per-channel shadow/highlight correction with a neutral mid-high highlight target (190 by default). Finder-only recovery also tries multiple center-weighted QuadQR Auto Color histogram windows before raw threshold bracketing. The normal fast path is untouched; these extra passes run only after a miss. If geometry is found but color decoding fails, the captured ROI is retried with stronger color/geometry recovery, including affine cross-channel calibration. Consecutive failed frames can then be combined with tracked-symbol **confidence fusion**: cell evidence is weighted by source confidence, frame quality, and recency, and second hypotheses are carried forward into Spectrum ECC 2.0.
|
|
210
213
|
|
|
211
214
|
```js
|
|
212
215
|
const scanner = await startCameraScanner(video, {
|
|
213
216
|
scanInterval: 120,
|
|
214
217
|
multiFrame: true,
|
|
215
218
|
multiFrameWindow: 4,
|
|
219
|
+
multiFrameMinFrames: 2,
|
|
220
|
+
softDecoding: true,
|
|
216
221
|
cameraAutoColorEvery: 1,
|
|
217
222
|
cameraAutoColorCropInsets: [0.08, 0.16, 0.22, 0],
|
|
218
223
|
cameraAutoColorHighlightPercentile: 0.95,
|
|
@@ -220,6 +225,9 @@ const scanner = await startCameraScanner(video, {
|
|
|
220
225
|
cameraAutoColorAnalysisInset: 0.10,
|
|
221
226
|
cameraAutoEnhanceEvery: 2,
|
|
222
227
|
cameraFinderRecoveryEvery: 2,
|
|
228
|
+
cameraHighResolutionRecovery: true,
|
|
229
|
+
cameraHighResolutionMaxDimension: 1600,
|
|
230
|
+
cameraHighResolutionEvery: 2,
|
|
223
231
|
onResult(result) {
|
|
224
232
|
console.log(result);
|
|
225
233
|
},
|
|
@@ -404,17 +412,38 @@ const code = QuadQR.encodeText("hello hello hello", {
|
|
|
404
412
|
});
|
|
405
413
|
```
|
|
406
414
|
|
|
407
|
-
`compression` may be `none`, `auto`, or `lz`.
|
|
415
|
+
`compression` may be `none`, `auto`, `smart`, `brotli`, `deflate`, or `lz`.
|
|
408
416
|
|
|
409
417
|
- `none` stores the payload directly.
|
|
410
|
-
- `auto`
|
|
411
|
-
- `
|
|
418
|
+
- `auto` is the fast balanced mode: one pass each of LZ level 6, DEFLATE level 6, and Brotli quality 6, followed by a complete stored-size comparison including envelope overhead.
|
|
419
|
+
- `smart` is the CPU-heavy mode. It starts with the Auto pass, checks the resulting QuadQR version, and only escalates to DEFLATE 8 / Brotli 9 and then DEFLATE 9 / Brotli 11 when a smaller physical version is realistically reachable.
|
|
420
|
+
- `brotli` always stores the payload using QuadQR's bundled synchronous Brotli codec.
|
|
421
|
+
- `deflate` always stores the payload using QuadQR's synchronous pure-JavaScript raw DEFLATE codec.
|
|
422
|
+
- `lz` always stores the payload through the original portable LZSS-style compressor for backward compatibility.
|
|
423
|
+
|
|
424
|
+
Explicit LZ, DEFLATE, and Brotli support `compressionLevel`:
|
|
425
|
+
|
|
426
|
+
```js
|
|
427
|
+
encodeText(text, { compression: "lz", compressionLevel: 9 }); // 1..9, default 6
|
|
428
|
+
encodeText(text, { compression: "deflate", compressionLevel: 9 }); // 1..9, default 6
|
|
429
|
+
encodeText(text, { compression: "brotli", compressionLevel: 11 }); // 0..11, default 11
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
`lzLevel`, `deflateLevel`, and `brotliQuality` are accepted as algorithm-specific aliases. Levels affect encoder effort only and are not serialized because the decoder does not need them. Generated code objects expose `compressionLevel`, `compressionStrategy`, and Smart-mode diagnostics; scanned symbols only need the stored compression algorithm ID.
|
|
412
433
|
|
|
413
434
|
There is no public content-type registry. Text remains text and byte arrays remain byte arrays.
|
|
414
435
|
|
|
415
|
-
### `compressPayload(input)` / `decompressPayload(input, expectedLength?)`
|
|
436
|
+
### `compressPayload(input, options?)` / `decompressPayload(input, expectedLength?)`
|
|
437
|
+
|
|
438
|
+
Legacy portable synchronous LZSS-style compression helpers. `options.level` accepts integers `1..9` and defaults to 6. Higher levels walk deeper candidate history and may use bounded lazy matching; level 6 preserves the historical QuadQR search depth. The LZ wire format and decoder are unchanged at every level.
|
|
416
439
|
|
|
417
|
-
|
|
440
|
+
### `compressDeflatePayload(input, options?)` / `decompressDeflatePayload(input, expectedLength?)`
|
|
441
|
+
|
|
442
|
+
Raw-DEFLATE helpers. `options.level` accepts integers `1..9` and defaults to 6. Higher levels spend more CPU on deeper LZ77 candidate search and lazy matching. The compressor emits standard RFC 1951 fixed-Huffman blocks with a 32 KiB match window and matches up to 258 bytes. The implementation is pure JavaScript and synchronous, so the same core works in browsers, Node.js servers, workers, and other JavaScript runtimes without `node:zlib`, `CompressionStream`, DOM APIs, or native dependencies.
|
|
443
|
+
|
|
444
|
+
### `compressBrotliPayload(input, options?)` / `decompressBrotliPayload(input, expectedLength?)`
|
|
445
|
+
|
|
446
|
+
Bundled synchronous Brotli helpers. `compressBrotliPayload()` accepts an optional `{ quality }` setting from 0 through 11 for direct codec use. The generated stream is standard Brotli and the decoder accepts standard Brotli streams. QuadQR does not require `node:zlib`, browser `CompressionStream`, DOM APIs, a native addon, or a runtime dependency for this path.
|
|
418
447
|
|
|
419
448
|
## Binary convenience APIs
|
|
420
449
|
|
|
@@ -530,9 +559,9 @@ Use `scanImageData(image, { debug: true })` for detailed geometry/sampling infor
|
|
|
530
559
|
|
|
531
560
|
## Scanability and stress testing
|
|
532
561
|
|
|
533
|
-
### `applyStressDistortion(imageData, type, severity?)`
|
|
562
|
+
### `applyStressDistortion(imageData, type, severity?, options?)`
|
|
534
563
|
|
|
535
|
-
Deterministically applies a selected synthetic distortion.
|
|
564
|
+
Deterministically applies a selected synthetic distortion. In addition to the original blur/exposure/shadow/resampling profiles, `perspective-3d` accepts `pitchDegrees`, `yawDegrees`, and `rollDegrees` for camera-angle testing.
|
|
536
565
|
|
|
537
566
|
### `runImageStressTest(imageData, expected?, options?)`
|
|
538
567
|
|
|
@@ -542,4 +571,12 @@ Runs the standard torture profiles and returns a 0–100 score plus per-scenario
|
|
|
542
571
|
|
|
543
572
|
Renders a code and runs the standard test suite. It returns `Excellent`, `Good`, `Risky`, or `Likely unscannable` together with recommendations.
|
|
544
573
|
|
|
545
|
-
|
|
574
|
+
### `runReliabilityLab(imageData, expected?, options?)`
|
|
575
|
+
|
|
576
|
+
Runs the broader Reliability Lab suite. `options.suite` can be `quick`, `full`, or `extreme`. Results include overall score, per-scenario CRC verification, category scores, confidence, RS corrections, and timing.
|
|
577
|
+
|
|
578
|
+
### `runPerspectiveSweep(imageData, expected?, options?)`
|
|
579
|
+
|
|
580
|
+
Sweeps `yaw`, `pitch`, or `roll`/Z rotation across a list of angles and reports the largest tested angle that decoded successfully.
|
|
581
|
+
|
|
582
|
+
Synthetic scores are regression aids and do not replace physical camera/print testing. See [`RELIABILITY_LAB.md`](./RELIABILITY_LAB.md).
|
package/docs/BROWSER_CDN.md
CHANGED
|
@@ -54,6 +54,10 @@ const compressed = encodeText("repeat repeat repeat repeat", {
|
|
|
54
54
|
compression: "auto"
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
+
const smallest = encodeText("structured payload ".repeat(500), {
|
|
58
|
+
compression: "smart"
|
|
59
|
+
});
|
|
60
|
+
|
|
57
61
|
const keys = await generateSigningKeyPair();
|
|
58
62
|
const signed = await encodeSignedText("ticket", {
|
|
59
63
|
compression: "auto",
|
|
@@ -72,7 +76,7 @@ const report = assessScanability(compressed, { imageSize: 480 });
|
|
|
72
76
|
console.log(report.score, report.rating);
|
|
73
77
|
```
|
|
74
78
|
|
|
75
|
-
Compression and signing
|
|
79
|
+
Compression uses bundled synchronous Brotli/DEFLATE/LZ implementations with explicit codec levels, fast Auto, and CPU-heavy Smart modes and signing uses internal metadata only when required. The repository demo keeps heavy encoding, verification, stress testing, perspective sweeps, image scanning, and benchmarks off the UI thread with module Web Workers, while the public library API itself remains synchronous where documented. There is no public content-type mode to configure. The private key signs, while the public verification key stays outside the QuadQR by default. Use the stored `keyId` to select an application-trusted public key. Scanability testing is deterministic synthetic regression testing and should be supplemented with real devices and print samples.
|
|
76
80
|
|
|
77
81
|
## Scan an uploaded image
|
|
78
82
|
|
|
@@ -133,7 +137,7 @@ The global browser build exposes `window.QuadQR` / `globalThis.QuadQR`.
|
|
|
133
137
|
|
|
134
138
|
```html
|
|
135
139
|
<canvas id="qr"></canvas>
|
|
136
|
-
<script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.1
|
|
140
|
+
<script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.4.1/dist/quadqr.min.js"></script>
|
|
137
141
|
<script>
|
|
138
142
|
const code = QuadQR.encodeText("Hello CDN");
|
|
139
143
|
|
|
@@ -147,7 +151,7 @@ The global browser build exposes `window.QuadQR` / `globalThis.QuadQR`.
|
|
|
147
151
|
## unpkg
|
|
148
152
|
|
|
149
153
|
```html
|
|
150
|
-
<script src="https://unpkg.com/quadqr-js@1.1
|
|
154
|
+
<script src="https://unpkg.com/quadqr-js@1.4.1/dist/quadqr.min.js"></script>
|
|
151
155
|
```
|
|
152
156
|
|
|
153
157
|
## Direct CDN ESM
|
|
@@ -157,7 +161,7 @@ The global browser build exposes `window.QuadQR` / `globalThis.QuadQR`.
|
|
|
157
161
|
import {
|
|
158
162
|
encodeText,
|
|
159
163
|
renderToCanvas
|
|
160
|
-
} from "https://cdn.jsdelivr.net/npm/quadqr-js@1.1
|
|
164
|
+
} from "https://cdn.jsdelivr.net/npm/quadqr-js@1.4.1/dist/browser.js";
|
|
161
165
|
|
|
162
166
|
const code = encodeText("ES module CDN");
|
|
163
167
|
renderToCanvas(code, document.querySelector("#qr"));
|
|
@@ -184,4 +188,4 @@ WASM is optional. The normal JavaScript implementation remains available if it c
|
|
|
184
188
|
|
|
185
189
|
## Production recommendation
|
|
186
190
|
|
|
187
|
-
Pin a concrete package version such as `@1.1
|
|
191
|
+
Pin a concrete package version such as `@1.4.1` in CDN URLs so an existing site does not silently change when a newer package version becomes available.
|
package/docs/CLI.md
CHANGED
|
@@ -33,7 +33,17 @@ npx quadqr-js encode "repeat repeat repeat repeat" \
|
|
|
33
33
|
-o compressed.png
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
Compression modes are `none`, `auto`, and `lz`. `auto`
|
|
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.
|
|
37
47
|
|
|
38
48
|
## Signed QuadQR
|
|
39
49
|
|
|
@@ -133,7 +143,9 @@ npx quadqr-js decode secure-key.png --key <64-hex-key>
|
|
|
133
143
|
| `-o, --output <file>` | Output PNG/SVG path, or signing-key JSON path for `signkeygen` |
|
|
134
144
|
| `--ecc <L|M|Q|H>` | QuadQR ECC profile. Default: `M` |
|
|
135
145
|
| `--version <auto|1..40>` | Symbol version. Default: `auto` |
|
|
136
|
-
| `--compression <mode>` | `none`, `auto`, or `lz`. 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 |
|
|
137
149
|
| `--sign-key <file>` | Sign using a key bundle generated by `signkeygen` |
|
|
138
150
|
| `--key-id <id>` | Override the signing key ID stored in the symbol |
|
|
139
151
|
| `--embed-public-key` | Explicit compatibility mode that embeds the public key |
|
|
@@ -148,3 +160,8 @@ npx quadqr-js decode secure-key.png --key <64-hex-key>
|
|
|
148
160
|
| `-h, --help` | Show CLI help |
|
|
149
161
|
|
|
150
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.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Compression 3.0
|
|
2
|
+
|
|
3
|
+
QuadQR keeps compression as an internal transport detail around normal text or byte payloads. It does not introduce a payload type system, and the decompressor does not need to know which compression level was used.
|
|
4
|
+
|
|
5
|
+
## Modes
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
encodeText(text, { compression: "none" });
|
|
9
|
+
encodeText(text, { compression: "auto" });
|
|
10
|
+
encodeText(text, { compression: "smart" });
|
|
11
|
+
encodeText(text, { compression: "brotli" });
|
|
12
|
+
encodeText(text, { compression: "deflate" });
|
|
13
|
+
encodeText(text, { compression: "lz" });
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- `none` stores the application payload directly.
|
|
17
|
+
- `auto` is the fast balanced mode. It performs one pass with LZ level 6, DEFLATE level 6, and Brotli quality 6, compares the complete stored sizes, and keeps the smallest result.
|
|
18
|
+
- `smart` is the opt-in CPU-heavy mode. It starts with the same balanced candidates as `auto`, checks the resulting QuadQR version, and only spends more CPU on stronger DEFLATE/Brotli passes when a smaller physical QuadQR is realistically reachable.
|
|
19
|
+
- `brotli` forces the bundled Brotli codec.
|
|
20
|
+
- `deflate` forces the portable raw-DEFLATE codec.
|
|
21
|
+
- `lz` forces the original QuadQR LZSS-style stream for compatibility and testing.
|
|
22
|
+
|
|
23
|
+
For unsigned payloads, `auto` and `smart` include the 16-byte internal extension-envelope cost when deciding whether compression helps. If compression would not make the complete stored representation smaller, the original payload is kept with no compression envelope. Signed payloads already require the extension envelope, so their comparison includes the fixed signing metadata automatically. Secure payload planning also includes the fixed encryption-envelope overhead when Smart evaluates version boundaries.
|
|
24
|
+
|
|
25
|
+
## Auto versus Smart
|
|
26
|
+
|
|
27
|
+
`auto` intentionally avoids expensive maximum-quality passes:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
Raw
|
|
31
|
+
LZ level 6
|
|
32
|
+
DEFLATE level 6
|
|
33
|
+
Brotli quality 6
|
|
34
|
+
↓
|
|
35
|
+
smallest final representation
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`smart` begins the same way, then considers the next smaller QuadQR version. When that boundary is close enough to be plausible, it escalates in stages:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
Balanced pass
|
|
42
|
+
LZ 6
|
|
43
|
+
DEFLATE 6
|
|
44
|
+
Brotli 6
|
|
45
|
+
↓
|
|
46
|
+
Is a smaller QuadQR version realistically reachable?
|
|
47
|
+
↓ yes
|
|
48
|
+
Strong pass
|
|
49
|
+
DEFLATE 8
|
|
50
|
+
Brotli 9
|
|
51
|
+
↓
|
|
52
|
+
Still close to another/same smaller-version boundary?
|
|
53
|
+
↓ yes
|
|
54
|
+
Maximum pass
|
|
55
|
+
DEFLATE 9
|
|
56
|
+
Brotli 11
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Smart is deliberately CPU-heavy and should be used when minimizing the physical matrix matters more than generation time. If an exact `version` is requested and the balanced pass does not fit, Smart may also escalate to stronger levels when that can plausibly make the requested version fit. The current plausibility gates are intentionally conservative: the strong stage is considered when the next boundary is within 30% of the current stored size or 192 bytes, and the maximum stage when it is within 16% or 96 bytes. The demo runs Smart in a Web Worker so the browser UI remains responsive.
|
|
60
|
+
|
|
61
|
+
The generated code object includes generation-only diagnostics such as `compressionLevel`, `compressionStrategy`, and `smartCompression`. Compression level is not stored in the QuadQR because LZ, DEFLATE, and Brotli decoders do not need it.
|
|
62
|
+
|
|
63
|
+
## Explicit compression levels
|
|
64
|
+
|
|
65
|
+
Use the generic `compressionLevel` option when you explicitly select LZ, DEFLATE, or Brotli:
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
const lz = encodeText(text, {
|
|
69
|
+
compression: "lz",
|
|
70
|
+
compressionLevel: 9
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const deflated = encodeText(text, {
|
|
74
|
+
compression: "deflate",
|
|
75
|
+
compressionLevel: 9
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const brotlied = encodeText(text, {
|
|
79
|
+
compression: "brotli",
|
|
80
|
+
compressionLevel: 11
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Ranges and defaults:
|
|
85
|
+
|
|
86
|
+
| Algorithm | Range | Explicit default |
|
|
87
|
+
| --- | ---: | ---: |
|
|
88
|
+
| Legacy LZ | 1..9 | 6 |
|
|
89
|
+
| DEFLATE | 1..9 | 6 |
|
|
90
|
+
| Brotli | 0..11 | 11 |
|
|
91
|
+
|
|
92
|
+
Algorithm-specific aliases are also accepted: `lzLevel`, `deflateLevel`, and `brotliQuality`. `compressionLevel` is the recommended public option.
|
|
93
|
+
|
|
94
|
+
Levels only affect encoder CPU/search effort and output size. They do not change the compression ID or decoder behavior.
|
|
95
|
+
|
|
96
|
+
## Legacy LZ profile
|
|
97
|
+
|
|
98
|
+
Compression ID `1` keeps the original QuadQR LZSS-style wire format. Levels `1..9` only control how much encoder work is spent walking candidate history and, at stronger levels, bounded lazy-match lookahead. Level 6 preserves the historical 32-candidate QuadQR search depth, so existing callers that do not pass a level keep the same default behavior.
|
|
99
|
+
|
|
100
|
+
The direct helpers are:
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
compressPayload(bytes, { level: 9 });
|
|
104
|
+
decompressPayload(compressedBytes, originalLength);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The LZ window remains 4095 bytes and each back-reference still represents a 3..18 byte match. The compressed stream format is identical at every level, so the level is never serialized and old/new LZ decoders remain compatible. Auto and Smart use LZ level 6; Smart does not escalate LZ because its additional staged CPU budget is reserved for the stronger DEFLATE/Brotli candidates.
|
|
108
|
+
|
|
109
|
+
## Brotli profile
|
|
110
|
+
|
|
111
|
+
Compression ID `3` is a standard Brotli stream. QuadQR vendors the codec into the library so Brotli compression and decompression are synchronous and available in browser ESM, the classic browser bundle, Web Workers, and server-side Node.js without a runtime package or native binding.
|
|
112
|
+
|
|
113
|
+
The direct helpers are:
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
compressBrotliPayload(bytes, { quality: 9 });
|
|
117
|
+
decompressBrotliPayload(compressedBytes, originalLength);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Brotli quality accepts integers `0..11`. Explicit `compression: "brotli"` defaults to quality 11 for backward compatibility. `auto` starts at quality 6. `smart` starts at 6 and may test 9 and 11 when a smaller QuadQR version is plausible.
|
|
121
|
+
|
|
122
|
+
## Portable DEFLATE profile
|
|
123
|
+
|
|
124
|
+
Compression ID `2` is a raw RFC 1951 DEFLATE stream. QuadQR's bundled encoder uses a fixed-Huffman block with level-dependent LZ77 match-search effort. It supports:
|
|
125
|
+
|
|
126
|
+
- a 32 KiB LZ77 window;
|
|
127
|
+
- match distances up to 32768 bytes;
|
|
128
|
+
- match lengths up to 258 bytes;
|
|
129
|
+
- DEFLATE levels `1..9`;
|
|
130
|
+
- progressively deeper candidate search and lazy matching at stronger levels;
|
|
131
|
+
- deterministic synchronous output;
|
|
132
|
+
- no runtime dependency.
|
|
133
|
+
|
|
134
|
+
The direct helpers are:
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
compressDeflatePayload(bytes, { level: 9 });
|
|
138
|
+
decompressDeflatePayload(compressedBytes, originalLength);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The stream remains standard raw DEFLATE at every level. The level is not serialized.
|
|
142
|
+
|
|
143
|
+
## Node.js and browser portability
|
|
144
|
+
|
|
145
|
+
All payload compression paths are bundled JavaScript. The library does not import `node:zlib`, use browser `CompressionStream`, depend on DOM APIs, or fetch a codec at runtime.
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
import { encodeText, decodeMatrix } from "quadqr-js";
|
|
149
|
+
|
|
150
|
+
const code = encodeText("hello ".repeat(1000), {
|
|
151
|
+
compression: "smart"
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const decoded = decodeMatrix(code.matrix);
|
|
155
|
+
console.log(code.compression, code.compressionLevel);
|
|
156
|
+
console.log(decoded.compression); // decoder only needs the algorithm ID
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Compression IDs and compatibility
|
|
160
|
+
|
|
161
|
+
The internal envelope keeps the stable IDs:
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
0 = none
|
|
165
|
+
1 = legacy LZ
|
|
166
|
+
2 = raw DEFLATE
|
|
167
|
+
3 = Brotli
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Compression level is intentionally not part of the envelope. Existing LZ, DEFLATE, and Brotli decoders remain compatible with streams produced at any supported level.
|
package/docs/GETTING_STARTED.md
CHANGED
|
@@ -163,7 +163,7 @@ PNG generation/decoding and SVG generation are built into the Node.js adapter.
|
|
|
163
163
|
|
|
164
164
|
```html
|
|
165
165
|
<canvas id="qr"></canvas>
|
|
166
|
-
<script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.1
|
|
166
|
+
<script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.4.1/dist/quadqr.min.js"></script>
|
|
167
167
|
<script>
|
|
168
168
|
const code = QuadQR.encodeText("No build step");
|
|
169
169
|
QuadQR.renderToCanvas(code, document.querySelector("#qr"));
|
|
@@ -201,7 +201,17 @@ const code = encodeText("repeat repeat repeat repeat", {
|
|
|
201
201
|
});
|
|
202
202
|
```
|
|
203
203
|
|
|
204
|
-
`auto` keeps the
|
|
204
|
+
`auto` is the fast default: it compares LZ level 6, DEFLATE level 6, and Brotli quality 6 once, then keeps the smallest complete representation. `smart` is the CPU-heavy option; it starts with the same pass and only tries DEFLATE 8/9 and Brotli 9/11 when stronger compression can realistically reduce the QuadQR version.
|
|
205
|
+
|
|
206
|
+
When you explicitly choose a codec, `compressionLevel` controls encoder effort:
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
encodeText(text, { compression: "lz", compressionLevel: 9 }); // 1..9
|
|
210
|
+
encodeText(text, { compression: "deflate", compressionLevel: 9 }); // 1..9
|
|
211
|
+
encodeText(text, { compression: "brotli", compressionLevel: 11 }); // 0..11
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
LZ defaults to level 6, DEFLATE defaults to level 6, and Brotli defaults to quality 11. The level is not stored in the symbol because decoding does not depend on it.
|
|
205
215
|
|
|
206
216
|
For offline integrity verification:
|
|
207
217
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# High Density Mode (Experimental)
|
|
2
|
+
|
|
3
|
+
High Density Mode is an experimental QuadQR option implemented with the Triangle16 cell layout. It keeps the existing QuadQR geometry, finder patterns, timing pattern, alignment patterns, calibration, Spectrum ECC, CRC, security envelope, compression, and signing behavior, but changes how payload data cells are represented.
|
|
4
|
+
|
|
5
|
+
## Physical cell
|
|
6
|
+
|
|
7
|
+
A Triangle16 payload cell is split by one fixed `/` diagonal:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
+---------+
|
|
11
|
+
| AAAAAA /|
|
|
12
|
+
| AAAAA /B|
|
|
13
|
+
| AAAA /BB|
|
|
14
|
+
| AAA /BBB|
|
|
15
|
+
| AA /BBBB|
|
|
16
|
+
| A /BBBBB|
|
|
17
|
+
| /BBBBBBB|
|
|
18
|
+
+---------+
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`A` is the upper-left triangle and `B` is the lower-right triangle. Each triangle independently uses the existing RGBW alphabet:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
R = 00
|
|
25
|
+
G = 01
|
|
26
|
+
B = 10
|
|
27
|
+
W = 11
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The pair therefore has 16 states:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
R/R R/G R/B R/W
|
|
34
|
+
G/R G/G G/B G/W
|
|
35
|
+
B/R B/G B/B B/W
|
|
36
|
+
W/R W/G W/B W/W
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The first triangle carries the high 2 bits and the second triangle carries the low 2 bits. One Triangle16 body cell therefore carries 4 raw bits, so one byte occupies two body cells instead of four RGBW cells.
|
|
40
|
+
|
|
41
|
+
## Protected header stays solid
|
|
42
|
+
|
|
43
|
+
The protected bootstrap/header intentionally does not use mixed-color triangles. Its normal RGBW header cells are represented as same-color pairs such as `R/R`, `G/G`, `B/B`, and `W/W`.
|
|
44
|
+
|
|
45
|
+
This uses four cells per protected header byte, exactly like RGBW, but makes it much easier for the decoder to recover the mode flag when the image is blurred, skewed, resized, or color-shifted. Header flag bit 6 declares Triangle16 for the ECC-protected body.
|
|
46
|
+
|
|
47
|
+
## Scanner sampling
|
|
48
|
+
|
|
49
|
+
The scanner does not sample the diagonal or the exact module center. Each triangle now uses a small **three-anchor sampling cluster** positioned safely inside its region. The cluster is robustly aggregated, and the variation between the three anchors is retained as a spatial-stability signal. This is less sensitive to blur, resampling, small homography offsets, and diagonal color bleed than relying on one point per triangle.
|
|
50
|
+
|
|
51
|
+
Each triangle is classified independently against the calibrated RGBW palette. The cell confidence combines the weaker color-classification confidence with the measured within-triangle sample stability. The scanner also retains the most plausible alternate Triangle16 state by changing the less reliable of the two regions first. Those confidence and alternate-state signals feed Spectrum ECC 2.0 for erasure and bounded soft-decision recovery.
|
|
52
|
+
|
|
53
|
+
The normal image and camera scanner automatically detects High Density Mode and attempts Triangle16 dual-region sampling when needed. No separate scan mode is required. If ordinary finder/alignment geometry is good enough to locate the symbol but not precise enough to decode the half-cell regions, the scanner performs one bounded **precise-alignment recovery** pass. That pass uses denser alignment-pattern probes and a finer sub-module search, then retries the same dual-triangle classifier.
|
|
54
|
+
|
|
55
|
+
## Rendering rules
|
|
56
|
+
|
|
57
|
+
High Density Mode uses exact hard-edged Triangle16 payload cells. Decorative payload styles are intentionally bypassed in this experimental mode because rounded, inset, soft, or depth effects can reduce the usable sampling area or contaminate the diagonal boundary. Structural modules keep the normal QuadQR rendering behavior.
|
|
58
|
+
|
|
59
|
+
PNG/ImageData, Canvas, SVG, browser, and Node rendering all support Triangle16.
|
|
60
|
+
|
|
61
|
+
## API
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
import { encodeText } from "quadqr-js";
|
|
65
|
+
|
|
66
|
+
const code = encodeText("High-density QuadQR", {
|
|
67
|
+
ecc: "M",
|
|
68
|
+
highDensity: true
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
High Density Mode is disabled by default. Existing callers remain on normal RGBW mode unless `highDensity: true` is explicitly supplied.
|
|
73
|
+
|
|
74
|
+
## Capacity
|
|
75
|
+
|
|
76
|
+
Raw body density is:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
RGBW 4 states 2 bits/body cell
|
|
80
|
+
Triangle16 16 states 4 bits/body cell
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The protected header remains RGBW-equivalent, and ECC/CRC overhead is unchanged at the byte level, so usable payload capacity is close to but not exactly 2x for a fixed matrix version.
|
|
84
|
+
|
|
85
|
+
Use `getVersionInfo(version, { ecc, highDensity: true })` or the demo capacity calculator for the exact value.
|
|
86
|
+
|
|
87
|
+
## Reliability caveat
|
|
88
|
+
|
|
89
|
+
Triangle16 doubles raw body bits per cell, but it also halves the spatial area available to each independently classified color. Its meaningful real-world metric is not only bits per matrix cell. It is reliable payload bytes at a fixed physical size, camera distance, angle, lighting condition, resize/compression pipeline, and print quality.
|
|
90
|
+
|
|
91
|
+
The branch should therefore be stress-tested before treating High Density Mode as stable. Important cases include:
|
|
92
|
+
|
|
93
|
+
- perspective and partial finder degradation;
|
|
94
|
+
- defocus and motion blur;
|
|
95
|
+
- low camera pixel coverage per module;
|
|
96
|
+
- JPEG compression and repeated image resizing;
|
|
97
|
+
- shadows, glare, white-balance shifts, and saturation changes;
|
|
98
|
+
- cheap printing, ink spread, paper tint, and camera recapture.
|
|
99
|
+
|
|
100
|
+
If full 16-state reliability becomes the limiting factor, a restricted triangle alphabet can be explored later without changing the basic physical-cell experiment.
|
package/docs/NODE.md
CHANGED
|
@@ -68,6 +68,28 @@ const result = await QuadQRNode.scanBuffer(pngBuffer);
|
|
|
68
68
|
|
|
69
69
|
PNG generation and decoding do not require external native dependencies.
|
|
70
70
|
|
|
71
|
+
## Server-side compression
|
|
72
|
+
|
|
73
|
+
Compression uses the same synchronous JavaScript codec on Node.js as it does in the browser. No DOM API, browser `CompressionStream`, or Node `zlib` call is required by the QuadQR payload codec.
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
import { encodeText, decodeMatrix } from "quadqr-js";
|
|
77
|
+
|
|
78
|
+
const code = encodeText("hello ".repeat(1000), {
|
|
79
|
+
compression: "auto"
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const smallest = encodeText("structured payload ".repeat(500), {
|
|
83
|
+
compression: "smart"
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const decoded = decodeMatrix(code.matrix);
|
|
87
|
+
console.log(decoded.compression); // usually "brotli" for repetitive data
|
|
88
|
+
console.log(decoded.text);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`compression: "auto"` performs one balanced comparison using LZ level 6, DEFLATE level 6, and Brotli quality 6. `compression: "smart"` is the opt-in CPU-heavy mode and escalates to stronger levels only when a smaller QuadQR version is realistically reachable. Explicit `lz` and `deflate` accept `compressionLevel: 1..9` (default 6), while explicit `brotli` accepts `compressionLevel: 0..11` (default 11). `lzLevel`, `deflateLevel`, and `brotliQuality` are available as algorithm-specific aliases.
|
|
92
|
+
|
|
71
93
|
## Other image formats
|
|
72
94
|
|
|
73
95
|
For JPEG, WebP, or AVIF input, the Node adapter can use `sharp` when the consuming application has it installed:
|
package/docs/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# QuadQR Documentation
|
|
2
2
|
|
|
3
|
-
QuadQR is an experimental
|
|
3
|
+
QuadQR is an experimental RGBW matrix symbology. Default RGBW cells carry two bits using red, green, blue, or white. The experimental High Density Mode splits payload cells into two RGBW triangles and carries four raw bits per body data cell. The JavaScript library supports encoding, canvas/RGBA/SVG rendering, adjustable quiet zones, optional centered logos with transparent or cleared backgrounds, matrix decoding, image and camera scanning, Spectrum ECC, optional authenticated encryption, Node.js PNG/SVG workflows, CDN usage, a CLI, and optional prebuilt WebAssembly acceleration.
|
|
4
4
|
|
|
5
5
|
## Live links
|
|
6
6
|
|
|
7
|
-
- [Documentation Site](https://akanshsirohi.github.io/QuadQR/
|
|
7
|
+
- [Documentation Site](https://akanshsirohi.github.io/QuadQR/documentation/)
|
|
8
8
|
- [Interactive Demo](https://akanshsirohi.github.io/QuadQR/demo/)
|
|
9
9
|
- [quadqr-js on npm](https://www.npmjs.com/package/quadqr-js)
|
|
10
10
|
- [GitHub Repository](https://github.com/akanshsirohi/QuadQR)
|
|
@@ -13,6 +13,9 @@ QuadQR is an experimental four-state RGBW matrix symbology. Each data cell carri
|
|
|
13
13
|
|
|
14
14
|
- [Getting Started](./GETTING_STARTED.md)
|
|
15
15
|
- [API Reference](./API.md)
|
|
16
|
+
- [Compression 3.0](./COMPRESSION.md)
|
|
17
|
+
- [High Density Mode (Experimental)](./HIGH_DENSITY_MODE.md)
|
|
18
|
+
- [Reliability Lab](./RELIABILITY_LAB.md)
|
|
16
19
|
- [Browser and CDN](./BROWSER_CDN.md)
|
|
17
20
|
- [Node.js](./NODE.md)
|
|
18
21
|
- [CLI](./CLI.md)
|
|
@@ -45,8 +48,8 @@ QuadQR is licensed under AGPL-3.0. See [`LICENSE`](../LICENSE).
|
|
|
45
48
|
|
|
46
49
|
The current library also includes:
|
|
47
50
|
|
|
48
|
-
- normal text/byte payloads with optional internal
|
|
49
|
-
- portable
|
|
51
|
+
- normal text/byte payloads with optional internal Compression 3.0;
|
|
52
|
+
- bundled synchronous Brotli + portable DEFLATE + level-aware legacy LZ compatibility;
|
|
50
53
|
- first-class binary `Uint8Array` APIs;
|
|
51
54
|
- Ed25519 signed QuadQR payloads and trusted-key verification;
|
|
52
55
|
- signed + AES-256-GCM encrypted composition;
|
|
@@ -54,6 +57,6 @@ The current library also includes:
|
|
|
54
57
|
- ECC-aware automatic logo sizing;
|
|
55
58
|
- normalized scanner confidence and detailed debug mode;
|
|
56
59
|
- deterministic scanability/torture testing;
|
|
57
|
-
-
|
|
60
|
+
- a dedicated browser Reliability Lab with 3D perspective sweeps, plus the capacity calculator.
|
|
58
61
|
|
|
59
62
|
See [`../SPECIFICATION.md`](../SPECIFICATION.md) for the layering and interoperability rules and [`API.md`](./API.md) for the public APIs.
|