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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Experimental custom symbology. It is not ISO QR Code.
6
6
 
7
- Only this RGBW format is implemented. Earlier RGB/ternary formats are intentionally not supported.
7
+ Normal mode uses the default 4-state RGBW data cells. An optional experimental **High Density Mode** uses the 16-state Triangle16 physical layout for payload/body cells. Earlier RGB/ternary formats are intentionally not supported.
8
8
 
9
9
  ## Matrix sizes
10
10
 
@@ -33,6 +33,17 @@ Four data states:
33
33
 
34
34
  Structural white and data white intentionally share the same visible/internal value. Reserved-position geometry distinguishes their roles.
35
35
 
36
+ ### High Density Mode (Triangle16)
37
+
38
+ High Density Mode is identified by header flag bit 6 and implemented with Triangle16 cells. Each non-reserved payload module is split by a fixed `/` diagonal into an upper-left triangle and a lower-right triangle. Each triangle independently uses the RGBW 2-bit alphabet. The packed internal value is:
39
+
40
+ ```text
41
+ cell = (upperLeftColor << 2) | lowerRightColor
42
+ range = 0..15
43
+ ```
44
+
45
+ Therefore Triangle16 has 16 visible states and carries 4 raw bits per body data cell. Same-color pairs such as R/R or B/B render as a visually solid module. Structural modules never use Triangle16 packing.
46
+
36
47
  ## Finder structures
37
48
 
38
49
  Three 7×7 black/white finder structures are placed at top-left, top-right, and bottom-left, with white separator cells where they fit inside the matrix.
@@ -99,6 +110,18 @@ Therefore:
99
110
  1 RGBW data cell = exactly 2 raw bits
100
111
  ```
101
112
 
113
+ For Triangle16 ECC/body bytes:
114
+
115
+ ```text
116
+ bits 7..4 -> Triangle16 cell 0
117
+ bits 3..0 -> Triangle16 cell 1
118
+
119
+ 1 byte = exactly 2 Triangle16 body cells
120
+ 1 Triangle16 body cell = exactly 4 raw bits
121
+ ```
122
+
123
+ The protected header remains encoded as four solid-color RGBW-equivalent cells per byte even when Triangle16 is selected. Internally those solid header cells are represented as R/R, G/G, B/B, or W/W. This keeps the bootstrap/header substantially easier to recover from blur and perspective distortion.
124
+
102
125
  ## Masks
103
126
 
104
127
  Four masks are defined. Each returns a 2-bit value 0..3:
@@ -117,7 +140,7 @@ visible = raw XOR mask
117
140
  raw = visible XOR mask
118
141
  ```
119
142
 
120
- The encoder evaluates all four masks using run-length and four-state balance penalties. The mask ID is not serialized. The decoder tries all four and accepts only a path whose protected header, ECC, and CRC validate.
143
+ The encoder evaluates all four masks using run-length and color-balance penalties. In Triangle16 body cells, two deterministic 2-bit masks are packed into one 4-bit XOR mask; protected solid-color header cells use the normal RGBW mask on both halves so they remain solid. The mask ID is not serialized. The decoder tries all four and accepts only a path whose protected header, ECC, and CRC validate.
121
144
 
122
145
  ## Header
123
146
 
@@ -167,7 +190,10 @@ Flags for both header forms:
167
190
  bit 0 UTF-8 text flag
168
191
  bits 1..2 ECC profile id
169
192
  bit 3 Secure Payload envelope flag
170
- bits 4..7 reserved
193
+ bit 4 internal payload-extension metadata present
194
+ bit 5 signed-payload hint
195
+ bit 6 High Density Mode flag
196
+ bit 7 reserved
171
197
  ```
172
198
 
173
199
  ECC ids:
@@ -335,13 +361,13 @@ The decoder also tries legacy physical order as a fallback for older QuadQR matr
335
361
 
336
362
  Unused data positions are filled with deterministic pseudo-random values in the range 0..3. Padding is not semantically decoded.
337
363
 
338
- ## Confidence-aware error/erasure decoding
364
+ ## Spectrum ECC 2.0 confidence-aware and soft decoding
339
365
 
340
- For image/camera scans, classification retains more than the winning RGBW state. Each sampled module also receives a confidence score derived from the separation between its nearest and second-nearest calibrated palette states.
366
+ For image/camera scans, classification retains more than the winning RGBW/Triangle16 state. Each sampled data cell receives a confidence score and a bounded second hypothesis derived from the calibrated palette classification. Triangle16 also incorporates the measured disagreement between its three interior samples per triangle so edge bleed or sub-module geometry instability lowers confidence instead of becoming false certainty.
341
367
 
342
- One GF(256) symbol corresponds to four 2-bit data cells. The symbol confidence is the minimum confidence of those four constituent cells because an error in any one cell changes the reconstructed byte.
368
+ In normal RGBW mode, one GF(256) byte symbol corresponds to four 2-bit cells. In High Density Mode, a body byte corresponds to two 4-bit Triangle16 cells, while the protected header remains four solid RGBW-equivalent cells per byte. Symbol confidence is the minimum confidence of its constituent cells because an error in any constituent cell changes the reconstructed byte.
343
369
 
344
- Decoding first attempts normal hard-decision Reed-Solomon correction. If that fails, low-confidence byte positions are progressively promoted to known erasures and the decoder retries error/erasure RS correction. Valid correction requires syndrome verification and the complete QuadQR payload still must pass CRC-32.
370
+ Decoding first attempts normal hard-decision Reed-Solomon correction. If that fails, low-confidence byte positions are progressively promoted to known erasures and the decoder retries error/erasure RS correction. If that still fails and scanner second hypotheses are available, Spectrum ECC 2.0 performs a bounded Chase-style search over the least-confident cells, trying single alternate substitutions and then a small pair set. Every candidate is passed through the unchanged RS and CRC validation path.
345
371
 
346
372
  The RS budget follows the usual error/erasure relationship:
347
373
 
@@ -365,11 +391,13 @@ RGB frame
365
391
  -> distributed alignment-grid validation
366
392
  -> projective module sampling
367
393
  -> observed black/white/R/G/B calibration
368
- -> nearest calibrated RGBW classification + confidence
394
+ -> observed-palette / white-balance / affine calibration attempts
395
+ -> RGBW or Triangle16 classification + confidence + second hypothesis
369
396
  -> four-state XOR unmasking
370
397
  -> reverse spectral-spatial permutation
371
398
  -> protected header GF(256) hard RS decode
372
399
  -> confidence-guided error/erasure retry when needed
400
+ -> bounded second-hypothesis soft retry when needed
373
401
  -> body block deinterleaving
374
402
  -> body GF(256) error/erasure correction
375
403
  -> CRC-32 verification
@@ -398,4 +426,4 @@ bit 4 = internal payload-extension metadata present
398
426
  bit 5 = signed-payload hint
399
427
  ```
400
428
 
401
- These bits are not a public payload-type mode. Applications continue to encode normal UTF-8 text or arbitrary bytes. Compression and Ed25519 signing use a compact internal extension envelope only when metadata is required. The envelope, print profile, diagnostics, and scanability test model are specified in [`SPECIFICATION.md`](./SPECIFICATION.md). None of these features changes RGBW cell mapping or Spectrum ECC.
429
+ These bits are not a public payload-type mode. Applications continue to encode normal UTF-8 text or arbitrary bytes. Compression 3.0 (fast Auto, CPU-heavy Smart, explicit LZ/DEFLATE/Brotli levels with legacy LZ wire compatibility) and Ed25519 signing use a compact internal extension envelope only when metadata is required. The envelope, print profile, diagnostics, and scanability test model are specified in [`SPECIFICATION.md`](./SPECIFICATION.md). None of these features changes RGBW cell mapping or Spectrum ECC.
package/README.md CHANGED
@@ -5,13 +5,13 @@
5
5
  </p>
6
6
 
7
7
  <p align="center">
8
- <strong>Four visible states. Two bits per data cell. A modern experiment in high-density matrix codes.</strong>
8
+ <strong>Normal RGBW mode by default, with an optional experimental High Density Mode using Triangle16 split cells.</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
12
  <a href="https://akanshsirohi.github.io/QuadQR/demo/"><strong>Try the Live Demo</strong></a>
13
13
  ·
14
- <a href="https://akanshsirohi.github.io/QuadQR/docs-site/"><strong>Documentation Site</strong></a>
14
+ <a href="https://akanshsirohi.github.io/QuadQR/documentation/"><strong>Documentation Site</strong></a>
15
15
  ·
16
16
  <a href="https://www.npmjs.com/package/quadqr-js">npm</a>
17
17
  ·
@@ -19,16 +19,14 @@
19
19
  ·
20
20
  <a href="#getting-started">Use the Library</a>
21
21
  ·
22
- <a href="docs/README.md">Markdown Docs</a>
22
+ <a href="https://akanshsirohi.github.io/QuadQR/documentation/format.html">Format Reference</a>
23
23
  ·
24
- <a href="FORMAT.md">Matrix Format</a>
25
- ·
26
- <a href="SPECIFICATION.md">Technical Specification</a>
24
+ <a href="https://akanshsirohi.github.io/QuadQR/documentation/specification.html">Technical Specification</a>
27
25
  </p>
28
26
 
29
- **QuadQR** is an experimental open-source 2D matrix code that uses four visible data states instead of the two states used by a traditional black-and-white QR module.
27
+ **QuadQR** is an experimental open-source 2D matrix code that uses RGBW color states instead of the two states used by a traditional black-and-white QR module. Normal mode stores **2 bits per RGBW data cell**. This experimental branch also includes an optional **High Density Mode**, implemented with Triangle16 split cells, that stores **4 raw bits per body cell**. High Density Mode is disabled by default.
30
28
 
31
- Each QuadQR data cell represents exactly **2 bits**:
29
+ Default RGBW mapping:
32
30
 
33
31
  | Color | Bits |
34
32
  |---|---|
@@ -39,6 +37,28 @@ Each QuadQR data cell represents exactly **2 bits**:
39
37
 
40
38
  That gives QuadQR a four-symbol alphabet and a raw density of **2 bits per data cell**.
41
39
 
40
+ ### Experimental High Density Mode
41
+
42
+ When `highDensity: true` is enabled, Triangle16 splits each payload cell along one fixed `/` diagonal. The upper-left and lower-right triangles independently use Red, Green, Blue, or White:
43
+
44
+ ```text
45
+ 4 colors × 4 colors = 16 states
46
+ log2(16) = 4 bits per data cell
47
+ ```
48
+
49
+ The protected bootstrap/header deliberately remains solid-color even in High Density Mode, while the ECC-protected body uses the full 16-state alphabet. This sacrifices a small amount of theoretical capacity to make mode detection and damaged-camera recovery more reliable. Finder, timing, alignment, calibration, ECC, CRC, and matrix dimensions remain unchanged.
50
+
51
+ ```js
52
+ const code = encodeText("High-density QuadQR", {
53
+ ecc: "M",
54
+ highDensity: true
55
+ });
56
+ ```
57
+
58
+ Image and camera scanning automatically detect High Density Mode, so a separate scanner mode is not required. High Density Mode is experimental and should be stress-tested at the intended physical size and camera distance.
59
+
60
+ See the [High Density Mode documentation](https://akanshsirohi.github.io/QuadQR/documentation/high-density.html) for the physical cell mapping, protected-header strategy, scanner sampling rules, and reliability caveats.
61
+
42
62
  QuadQR keeps the parts that make QR-like codes practical, such as a square matrix, finder patterns, timing structures, error correction, masking, perspective recovery, and camera scanning, while experimenting with a higher-density color-based data layer.
43
63
 
44
64
  > **Important:** QuadQR is an experimental custom format. It is **not ISO QR Code**, and normal QR scanner apps cannot decode it.
@@ -50,12 +70,12 @@ Below are QuadQR symbols generated with different error-correction profiles. The
50
70
  <table>
51
71
  <tr>
52
72
  <td align="center">
53
- <img src="assets/quadqr-v2-L.png?raw=true" alt="QuadQR example using ECC profile L" width="320"><br>
54
- <sub><strong>ECC L</strong> · Higher payload capacity</sub>
73
+ <img src="assets/quadqr-normal.png?raw=true" alt="QuadQR example using ECC profile L" width="320"><br>
74
+ <sub><strong>QuadQR</strong> · Normal (v3)</sub>
55
75
  </td>
56
76
  <td align="center">
57
- <img src="assets/quadqr-v2-M.png?raw=true" alt="QuadQR example using ECC profile M" width="320"><br>
58
- <sub><strong>ECC M</strong> · More error-correction redundancy</sub>
77
+ <img src="assets/quadqr-high-density.png?raw=true" alt="QuadQR example using ECC profile M" width="320"><br>
78
+ <sub><strong>QuadQR</strong> · Higher Data Density (v2)</sub>
59
79
  </td>
60
80
  </tr>
61
81
  </table>
@@ -131,17 +151,18 @@ So at the raw data-cell level:
131
151
  | Format | States per data cell | Raw information |
132
152
  |---|---:|---:|
133
153
  | Binary QR | 2 | 1 bit |
134
- | QuadQR | 4 | 2 bits |
154
+ | QuadQR RGBW | 4 | 2 bits |
155
+ | QuadQR High Density Mode (Triangle16) | 16 | 4 bits |
135
156
 
136
157
  This is a **2× raw symbol-density advantage**.
137
158
 
138
- ### Spectrum ECC: QuadQR-specific reliability without capacity loss
159
+ ### Spectrum ECC 2.0: confidence-aware + soft decoding
139
160
 
140
- QuadQR now uses a second idea that is possible because the scanner already measures calibrated color rather than only black/white state: **confidence-aware Reed-Solomon recovery**.
161
+ QuadQR uses the fact that a color scanner knows more than only the winning state. For each sampled data cell it now retains the selected state, a confidence score, and a bounded second hypothesis. Triangle16 does the same after classifying both color regions.
141
162
 
142
- For every sampled RGBW data module, the scanner keeps both the selected state and a confidence score based on the distance to the nearest and second-nearest calibrated color states. Four module confidences are combined into the confidence of their GF(256) byte symbol.
163
+ The first recovery layer is still confidence-aware GF(256) Reed-Solomon: if normal hard-decision decoding fails, the least-confident byte symbols can be promoted to **known erasures**, allowing the existing parity budget to be spent more efficiently.
143
164
 
144
- If normal hard-decision Reed-Solomon decoding fails, the least-confident byte symbols can be promoted to **known erasures**. Reed-Solomon can spend parity more efficiently on known erasure locations than on completely unknown errors. CRC-32 remains the final acceptance check.
165
+ **Spectrum ECC 2.0** adds a bounded soft-decision fallback. If hard decoding and erasure decoding still fail, the decoder tries the second hypothesis for a small number of the least-confident data cells, first singly and then in tightly bounded pairs. Every candidate must still pass the normal Reed-Solomon checks and final CRC-32, so soft decoding does not relax integrity validation or add parity overhead.
145
166
 
146
167
  QuadQR also applies a deterministic **spectral-spatial interleaver** after ECC. Neighboring logical codeword cells are scattered across distant physical data positions, so a scratch, glare patch, shadow, or localized print defect tends to affect many different RS symbols instead of destroying a contiguous run. The permutation is reversible and consumes **zero extra data cells**.
147
168
 
@@ -200,6 +221,24 @@ const compressed = encodeText("repeated repeated repeated", {
200
221
  ecc: "M"
201
222
  });
202
223
 
224
+ // Smart is CPU-heavy and only escalates when stronger compression can
225
+ // realistically reduce the physical QuadQR version.
226
+ const smart = encodeText(largeStructuredText, { compression: "smart" });
227
+
228
+ // Explicit codecs can choose their encoder level.
229
+ const lz = encodeText(largeStructuredText, {
230
+ compression: "lz",
231
+ compressionLevel: 9
232
+ });
233
+ const brotli = encodeText("hello ".repeat(1000), {
234
+ compression: "brotli",
235
+ compressionLevel: 11
236
+ });
237
+ const deflate = encodeText(largeStructuredText, {
238
+ compression: "deflate",
239
+ compressionLevel: 9
240
+ });
241
+
203
242
  const keys = await generateSigningKeyPair();
204
243
  const signed = await encodeSignedText("verified offline", {
205
244
  compression: "auto",
@@ -215,7 +254,7 @@ console.log(verified.signatureVerified); // true
215
254
  console.log(verified.signatureTrusted); // true
216
255
  ```
217
256
 
218
- Compression modes are `none`, `auto`, and `lz`. `auto` keeps the original payload untouched when compression would not save space. Ed25519 signing stores the signature plus an optional compact `keyId`; the public verification key stays outside the QuadQR by default. Applications do not need to choose or maintain content types.
257
+ Compression modes are `none`, `auto`, `smart`, `brotli`, `deflate`, and `lz`. `auto` is the fast default: it compares LZ level 6, DEFLATE level 6, and Brotli quality 6 once, including envelope overhead, and keeps the smallest final representation. `smart` is an opt-in CPU-heavy mode. It starts with the same balanced 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; LZ stays at its default level in Auto/Smart. Explicit `lz` accepts `compressionLevel: 1..9` with default 6, explicit `deflate` accepts `1..9` with default 6, and explicit `brotli` accepts `0..11` with default 11. Compression level is an encoder-only setting and is not stored in the symbol because the decoder does not need it. All codecs are synchronous and bundled with QuadQR, so the same compression path works in browsers and server-side Node.js without `node:zlib`, `CompressionStream`, or a runtime dependency. The demo keeps these CPU-heavy operations in module Web Workers so the browser UI remains responsive. See the [compression documentation](https://akanshsirohi.github.io/QuadQR/documentation/compression.html) for the exact Smart escalation policy and level API. Ed25519 signing stores the signature plus an optional compact `keyId`; the public verification key stays outside the QuadQR by default. Applications do not need to choose or maintain content types.
219
258
 
220
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`.
221
260
 
@@ -227,7 +266,7 @@ Scanner results include normalized diagnostics such as `confidence`, `geometryCo
227
266
 
228
267
  For regression and demo testing, `runImageStressTest()` / `assessScanability()` apply deterministic blur, brightness, exposure, shadow, contrast, perspective, JPEG-like artifacts, and downscaling. The browser demo exposes the same tools as an interactive stress-test lab and shows an overall scanability rating.
229
268
 
230
- The interoperability details are documented in [`SPECIFICATION.md`](./SPECIFICATION.md).
269
+ The interoperability details are covered in the [technical specification](https://akanshsirohi.github.io/QuadQR/documentation/specification.html).
231
270
 
232
271
  ---
233
272
 
@@ -280,7 +319,7 @@ Therefore:
280
319
 
281
320
  > The capacity benchmark is a same-dimension and same-label comparison, not yet an equal-damage-tolerance comparison.
282
321
 
283
- The raw QuadQR data alphabet is exactly **2 bits per data cell**. Ratios approaching ~3× in the current usable-payload benchmark are caused by differences in total structural and ECC overhead between the two formats, not because a QuadQR cell contains 3 bits.
322
+ The published baseline benchmark uses **normal RGBW mode at exactly 2 bits per data cell**. Ratios approaching ~3× in that RGBW usable-payload benchmark are caused by differences in total structural and ECC overhead between the two formats, not because an RGBW QuadQR cell contains 3 bits. The experimental **Triangle16** profile is a separate 4-bit/body-cell mode and should be benchmarked independently because its real-world advantage depends on camera resolution, perspective, blur, resizing, and print quality.
284
323
 
285
324
  A future goal is to add **equal-reliability benchmarking**, where QuadQR and standard QR are compared after calibrating both to similar real-world damage recovery.
286
325
 
@@ -442,7 +481,7 @@ White → (255, 255, 255)
442
481
 
443
482
  Real camera input is not expected to match those exact values.
444
483
 
445
- QuadQR includes calibration and nearest-color classification so the scanner can work with observed colors after lighting, camera processing, perspective changes, and other image transformations. The clean-frame path stays fast: QuadQR tries the normal detected geometry and observed palette first. Only after that fails does it progressively try stronger recovery, including white balancing, spatial normalization, Auto Tone / Auto Contrast / Auto Color-style enhancement, and bounded sub-module geometry refinement. For live video, QuadQR scans the CSS-visible `object-fit: cover` camera region instead of the hidden full sensor frame, so the code keeps the same apparent size/resolution the user sees in the guide. If finder geometry is already strong but color decoding fails, a QR-only rectified pixel enhancement retry is performed immediately; whole-frame enhancement remains reserved for harder locator failures.
484
+ QuadQR includes calibration and nearest-color classification so the scanner can work with observed colors after lighting, camera processing, perspective changes, and other image transformations. The clean-frame path stays fast: QuadQR tries the normal detected geometry and observed palette first. Dense versions can refine an imperfect four-point homography with reliable secondary alignment markers already present in the matrix, without reserving any new cells. If a steep angle leaves exactly two strong finder patterns, a bounded looser third-finder pass runs before heavier color recovery. Only after geometry/color decoding still fails does QuadQR progressively try stronger recovery, including white balancing, a 3×4 affine color-calibration model learned from the known black/white/R/G/B references, spatial normalization, Auto Tone / Auto Contrast / QuadQR Auto Color-style enhancement, and bounded sub-module geometry refinement. For live video, QuadQR scans the CSS-visible `object-fit: cover` camera region instead of the hidden full sensor frame, so the code keeps the same apparent size/resolution the user sees in the guide. When a dense frame already exposes at least two finders, the camera scanner can also retry the visible ROI at up to 1600 px before expensive color recovery. If finder geometry is already strong but color decoding fails, a QR-only rectified pixel enhancement retry is performed immediately; whole-frame enhancement remains reserved for harder locator failures.
446
485
 
447
486
  ---
448
487
 
@@ -504,25 +543,27 @@ version hypothesis
504
543
 
505
544
  primary alignment search
506
545
 
507
- homography / perspective correction
546
+ initial homography / perspective correction
508
547
 
509
548
  distributed alignment-grid validation
549
+ ↓ (when geometry is plausible but imperfect)
550
+ secondary alignment multi-point homography refinement
510
551
 
511
552
  module-grid reconstruction
512
553
 
513
554
  fast observed-RGB decode attempt
514
555
  ↓ (only if needed)
515
- white balance + spatial normalization
556
+ white balance + affine cross-channel calibration + spatial normalization
516
557
  ↓ (only if still needed)
517
- Auto Tone / Auto Contrast / Auto Color-style recovery
558
+ Auto Tone / Auto Contrast / QuadQR Auto Color-style recovery
518
559
  ↓ (only if still needed)
519
560
  sub-module geometry refinement
520
561
 
521
562
  RGB + structural black/white calibration
522
563
 
523
- nearest-color classification + confidence scoring
564
+ RGBW/Triangle16 classification + confidence + second hypothesis
524
565
 
525
- four-state unmasking
566
+ unmasking
526
567
 
527
568
  reverse spectral-spatial permutation
528
569
 
@@ -530,6 +571,8 @@ protected header Reed-Solomon hard decode
530
571
 
531
572
  confidence-guided erasure retry when needed
532
573
 
574
+ bounded Spectrum ECC 2.0 soft-hypothesis retry when needed
575
+
533
576
  body deinterleaving + error/erasure Reed-Solomon decode
534
577
 
535
578
  CRC-32 verification
@@ -619,7 +662,7 @@ Logo overlays intentionally consume some ECC margin because they cover encoded c
619
662
 
620
663
  **Live demo:** https://akanshsirohi.github.io/QuadQR/demo/
621
664
 
622
- The browser demo runs directly on GitHub Pages and is split into separate views so the interface does not become overloaded. The generator keeps only payload, version, and ECC visible by default; optional capabilities are grouped into independent advanced accordions.
665
+ The browser demo runs directly on GitHub Pages and is split into separate views so the interface does not become overloaded. The generator keeps only payload, version, and ECC visible by default; optional capabilities are grouped into independent advanced accordions. CPU-heavy encoding/compression, rendered-image verification, uploaded-image scanning, scanability tests, Reliability Lab runs, perspective sweeps, and codec benchmarks are dispatched to module Web Workers. Generation shows explicit progress instead of freezing the browser, repeated UI changes are coalesced, and render-only changes reuse the already encoded matrix instead of recompressing the payload.
623
666
 
624
667
  ### Generator & Image Scanner
625
668
 
@@ -739,7 +782,7 @@ console.log(result.text);
739
782
  The `quadqr-js` package can be loaded directly from npm-backed CDNs:
740
783
 
741
784
  ```html
742
- <script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.1.0/dist/quadqr.min.js"></script>
785
+ <script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.4.1/dist/quadqr.min.js"></script>
743
786
  <script>
744
787
  const code = QuadQR.encodeText("Hello from a script tag");
745
788
  </script>
@@ -773,7 +816,7 @@ npx quadqr-js encode "Private data" --password "my-password" -o secure.png
773
816
  npx quadqr-js decode secure.png --password "my-password"
774
817
  ```
775
818
 
776
- See [`docs/CLI.md`](docs/CLI.md) for all CLI options, including compression, raw 256-bit key mode, signing, print mode, and scanner diagnostics.
819
+ See the [CLI reference](https://akanshsirohi.github.io/QuadQR/documentation/cli.html) for all CLI options, including compression, raw 256-bit key mode, signing, print mode, and scanner diagnostics.
777
820
 
778
821
  ### Run from source
779
822
 
@@ -871,25 +914,29 @@ dist/
871
914
 
872
915
  demo/
873
916
  index.html Interactive generator, image scanner, camera scanner, benchmark
874
- app.js
917
+ app.js Responsive UI + worker orchestration
918
+ compute-worker.js Background encode/scan/reliability/benchmark tasks
875
919
  styles.css
876
920
 
877
- docs-site/
878
- index.html Standalone documentation website
879
- app.js
880
- styles.css
921
+ documentation/
922
+ index.html Documentation home
923
+ getting-started.html
924
+ browser.html
925
+ node.html
926
+ scanning.html
927
+ compression.html
928
+ security.html
929
+ high-density.html
930
+ reliability.html
931
+ api.html
932
+ cli.html
933
+ wasm.html
934
+ format.html
935
+ specification.html
936
+ app.js Navigation, search, copy, and theme behavior
937
+ search-index.js Client-side documentation search index
938
+ styles.css Responsive documentation UI
881
939
 
882
- docs/
883
- README.md Markdown documentation index
884
- GETTING_STARTED.md
885
- API.md
886
- BROWSER_CDN.md
887
- NODE.md
888
- SECURITY.md
889
- CLI.md
890
- WASM.md
891
-
892
- types/ TypeScript declarations for JavaScript consumers
893
940
  bin/ `quadqr` CLI (`npx quadqr-js`)
894
941
  scripts/ Build, benchmark, and local server scripts
895
942
  tests/ Codec and package distribution tests
@@ -1034,7 +1081,7 @@ Returns a standalone SVG string using the same exact `imageSize`, render styles,
1034
1081
 
1035
1082
  ### `scanImageData(imageData, options?)`
1036
1083
 
1037
- Runs the complete perspective-aware and color-aware image scanner. The scanner first tries the normal detected geometry with the observed RGB palette, preserving the fast path for clean images. Only after that fails does it progressively fall back to per-channel white balancing, spatial black/white normalization, tighter centre sampling, a cheap module-grid Auto Tone / Auto Contrast / Auto Color-style recovery, a rectified QR-region pixel enhancement pass, and finally bounded sub-module geometry micro-refinement. If locator detection itself is weakened by a flat/yellow frame, a full-image enhancement retry is also available. RGBW confidence values are carried into Reed-Solomon so ambiguous cells can be treated as erasures when ordinary hard-decision ECC is insufficient.
1084
+ Runs the complete perspective-aware and color-aware image scanner. The scanner first tries the normal detected geometry with the observed RGB palette, preserving the fast path for clean images. Dense versions use distributed alignment markers to refine a plausible but imperfect projective solution, and a two-finder recovery pass can rescue a third locator that has been stretched by perspective. Only after that fails does it progressively fall back to per-channel white balancing, spatial black/white normalization, tighter centre sampling, a cheap module-grid Auto Tone / Auto Contrast / QuadQR Auto Color-style recovery, a rectified QR-region pixel enhancement pass, and finally bounded sub-module geometry micro-refinement. If locator detection itself is weakened by a flat/yellow frame, a full-image enhancement retry is also available. RGBW confidence values are carried into Reed-Solomon so ambiguous cells can be treated as erasures when ordinary hard-decision ECC is insufficient.
1038
1085
 
1039
1086
  ### `scanFile(file, options?)`
1040
1087
 
@@ -1046,7 +1093,7 @@ Scans one frame from an HTML video element. By default, if the video is displaye
1046
1093
 
1047
1094
  ### `startCameraScanner(video, options?)`
1048
1095
 
1049
- 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. A normal frame always gets the fast RGB-value finder pass first. If that fails, the **same captured frame** enters a QR-guide recovery path: QuadQR progressively crops away 8%, 16%, and 22% of the surrounding camera frame (then tries the full frame as a final fallback), applies the Photoshop-style Auto Color correction inside that code-centric region, and runs finder detection again. This matters because a live preview can contain dark room pixels, browser chrome, a monitor bezel, or other content that completely changes global Auto Color/Otsu statistics even though a manually cropped screenshot scans instantly. Normal scanning stays unchanged and fast because these recovery crops run only after a miss. Finder-only recovery also tries multiple center-weighted Auto Color histograms before threshold bracketing. `cameraAutoColorEvery` defaults to 1 so the same-frame Auto Color recovery is attempted immediately after each fast miss. Multi-frame voting remains enabled by default with a four-frame history. The optional `onDiagnostic(event)` callback exposes finder candidates, active locator method, crop/geometry/version hypothesis, recovery method, timing, and scan dimensions. `onResult(result, frame)` receives the exact raw decoded camera frame and, when Auto Color was used, the enhanced recovery pixels and their crop rectangle, so UIs can keep the frozen frame and finder overlay aligned.
1096
+ 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. A normal frame always gets the fast RGB-value finder pass first. If a miss still exposes at least two strong finder patterns, QuadQR retries the visible camera ROI at up to 1600 px by default so dense symbols retain more pixels per module. This high-resolution retry is bounded and does not run on empty frames. If it still fails, the **same captured frame** enters a QR-guide recovery path: QuadQR progressively crops away 8%, 16%, and 22% of the surrounding camera frame (then tries the full frame as a final fallback), applies the QuadQR-specific QuadQR Auto Color correction inside that code-centric region, and runs finder detection again. This matters because a live preview can contain dark room pixels, browser chrome, a monitor bezel, or other content that completely changes global QuadQR Auto Color/Otsu statistics even though a manually cropped screenshot scans instantly. Normal scanning stays unchanged and fast because these recovery paths run only after a miss. Finder-only recovery also tries multiple center-weighted QuadQR Auto Color histograms before threshold bracketing. `cameraHighResolutionMaxDimension` defaults to 1600, `cameraHighResolutionEvery` defaults to 2, and `cameraAutoColorEvery` defaults to 1. Multi-frame confidence fusion remains enabled by default with a four-frame history. Frames are kept only when their high-confidence data cells are consistent with the current tracked symbol. Per-cell evidence is weighted by confidence, frame quality, and recency, while second hypotheses are retained for Spectrum ECC 2.0. The optional `onDiagnostic(event)` callback exposes finder candidates, active locator method, crop/geometry/version hypothesis, recovery method, timing, and scan dimensions. `onResult(result, frame)` receives the exact raw decoded camera frame and, when QuadQR Auto Color was used, the enhanced recovery pixels and their crop rectangle, so UIs can keep the frozen frame and finder overlay aligned.
1050
1097
 
1051
1098
  ### `getVersionInfo(version, options?)`
1052
1099
 
@@ -1087,7 +1134,10 @@ The current test suite covers areas including:
1087
1134
  - color-cast scanning;
1088
1135
  - dirty-camera stress scanning with strong yellow cast, haze, blue-channel suppression, and blur;
1089
1136
  - low-contrast warm-camera regression where normal scanning fails but progressive Auto Tone / Contrast / Color recovery succeeds;
1090
- - multi-frame classification voting;
1137
+ - multi-frame confidence fusion and tracked-symbol consistency;
1138
+ - Spectrum ECC 2.0 bounded soft-decision recovery;
1139
+ - affine cross-channel color calibration;
1140
+ - multi-point Triangle16 region sampling with instability-aware confidence;
1091
1141
  - benchmark reference data;
1092
1142
  - timed codec round trips;
1093
1143
  - password-mode secure round trips and wrong-password rejection;
package/SPECIFICATION.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Status
4
4
 
5
- QuadQR is an experimental four-state RGBW matrix symbology. It is not ISO/IEC QR Code and is not intended to be decoded by standard QR readers.
5
+ QuadQR is an experimental RGBW matrix symbology. Normal mode uses 4-state RGBW cells, while the optional **High Density Mode** is experimental and uses the 16-state Triangle16 physical layout. It is not ISO/IEC QR Code and is not intended to be decoded by standard QR readers.
6
6
 
7
7
  The physical matrix format remains **QuadQR Format v5**. Normal application data is always treated simply as UTF-8 text or arbitrary bytes. Compression, signatures, encryption, rendering, and diagnostics are optional features layered around that stable matrix codec.
8
8
 
@@ -22,7 +22,7 @@ Application bytes / UTF-8 text
22
22
  ├─ CRC-32
23
23
  ├─ GF(256) Reed-Solomon Spectrum ECC
24
24
  ├─ spectral-spatial interleaving
25
- └─ RGBW matrix
25
+ └─ normal RGBW or High Density Triangle16 matrix
26
26
  ```
27
27
 
28
28
  There is deliberately **no public payload-type registry**. Applications do not select URL, JSON, contact, Wi-Fi, or other semantic types. They encode text or bytes and interpret that data themselves.
@@ -40,6 +40,10 @@ Data modules use exactly four states:
40
40
 
41
41
  Structural black is separate from the data alphabet. One encoded byte maps to exactly four RGBW data cells.
42
42
 
43
+ ### High Density Mode
44
+
45
+ When `highDensity: true` is selected, each payload module has a fixed `/` diagonal and two independently classified RGBW regions. This creates 16 states and 4 raw bits per body cell. The protected header stays solid-color at 2 bits per cell for robust bootstrap recovery, then header flag bit 6 tells the decoder that the ECC/body stream uses Triangle16 packing. Scanner sampling uses three interior anchors per triangle, robust aggregation, and a spatial-instability penalty while excluding the diagonal boundary.
46
+
43
47
  ## 3. Matrix sizing
44
48
 
45
49
  ```text
@@ -59,7 +63,8 @@ bits 1..2 ECC profile id
59
63
  bit 3 Secure Payload v1 envelope
60
64
  bit 4 internal payload-extension metadata present
61
65
  bit 5 signed-payload hint
62
- bits 6..7 reserved
66
+ bit 6 High Density Mode flag
67
+ bit 7 reserved
63
68
  ```
64
69
 
65
70
  Bit 4 is an implementation/interoperability hint used only when compression or signing requires metadata. It is not a user-selectable payload mode.
@@ -82,7 +87,7 @@ Version 2+ parity profiles:
82
87
  | Q | 36 | 18 |
83
88
  | H | 48 | 24 |
84
89
 
85
- The decoder retains per-cell color confidence. Low-confidence bytes can be promoted to known erasures, allowing Reed-Solomon recovery to use the existing parity budget more efficiently.
90
+ The decoder retains per-cell color confidence and one bounded alternate hypothesis from image/camera classification. Low-confidence bytes can first be promoted to known erasures, allowing Reed-Solomon recovery to use the existing parity budget more efficiently. If hard and error/erasure decoding still fail, Spectrum ECC 2.0 may try the alternate state for a bounded set of the least-confident cells. Candidate search is limited to single substitutions and a small pair set by default, and every successful path must still satisfy Reed-Solomon verification and CRC-32.
86
91
 
87
92
  ## 6. Internal payload extension envelope
88
93
 
@@ -99,12 +104,12 @@ Fixed header size: **16 bytes**.
99
104
  | Offset | Size | Field |
100
105
  |---:|---:|---|
101
106
  | 0 | 4 | ASCII magic `QPX1` |
102
- | 4 | 1 | Extension version (`2`; decoder also accepts legacy `1`) |
107
+ | 4 | 1 | Extension version (`3`; decoder also accepts legacy `1` and `2`) |
103
108
  | 5 | 1 | Flags |
104
109
  | 6 | 1 | Compression ID |
105
110
  | 7 | 1 | Signature algorithm ID |
106
111
  | 8 | 4 | Original application payload length, big-endian |
107
- | 12 | 1 | Signing key-ID length (v2); legacy signer-label length in v1 |
112
+ | 12 | 1 | Signing key-ID length (v2+); legacy signer-label length in v1 |
108
113
  | 13 | 1 | Optional embedded public-key length |
109
114
  | 14 | 1 | Signature length |
110
115
  | 15 | 1 | Reserved (`0`) |
@@ -131,25 +136,40 @@ Compression IDs:
131
136
 
132
137
  ```text
133
138
  0 = none
134
- 1 = QuadQR portable LZ
139
+ 1 = QuadQR portable LZ (legacy)
140
+ 2 = QuadQR portable raw DEFLATE
141
+ 3 = bundled Brotli
135
142
  ```
136
143
 
137
- The portable LZ stream is an LZSS-style format with groups of up to eight tokens. Each group begins with one flag byte. A flag bit of `0` means a one-byte literal. A flag bit of `1` means a two-byte back-reference:
144
+ Compression ID `1` is the original LZSS-style stream with groups of up to eight tokens. Each group begins with one flag byte. A flag bit of `0` means a one-byte literal. A flag bit of `1` means a two-byte back-reference:
138
145
 
139
146
  ```text
140
147
  12-bit offset: 1..4095 bytes
141
148
  4-bit length: stored value + 3, therefore 3..18 bytes
142
149
  ```
143
150
 
151
+ Compression ID `1` keeps the original QuadQR LZSS-style stream. Encoder levels `1..9` control candidate-history depth and bounded lazy-match effort; level 6 preserves the historical 32-candidate search depth. The 4095-byte window, 3..18 byte back-reference format, and decoder remain unchanged, and the level is not serialized.
152
+
153
+ Compression ID `2` is a raw RFC 1951 DEFLATE stream. QuadQR's portable encoder uses a deterministic fixed-Huffman block, a 32 KiB LZ77 window, distances up to 32768 bytes, and matches up to 258 bytes. Encoder levels `1..9` control candidate-chain depth and lazy-match effort; the level is not serialized and does not change decoder behavior. The QuadQR decoder accepts the stored/fixed block forms emitted by the library.
154
+
155
+ Compression ID `3` is a standard Brotli stream produced and decoded by QuadQR's bundled synchronous JavaScript codec. It uses no Node `zlib`, browser `CompressionStream`, DOM API, native addon, or network-loaded codec at runtime. Brotli is especially effective for repetitive text and structured payloads, while DEFLATE and legacy LZ remain available for deterministic selection and backward compatibility.
156
+
144
157
  Public compression modes are:
145
158
 
146
159
  ```text
147
160
  none
148
161
  auto
162
+ smart
163
+ brotli
164
+ deflate
149
165
  lz
150
166
  ```
151
167
 
152
- `compression: "auto"` uses compression only when it meaningfully reduces payload size. When it does not help and the payload is not signed, QuadQR stores the original payload directly with **no extension-envelope overhead**.
168
+ `compression: "auto"` is the fast balanced policy. It evaluates LZ level 6, DEFLATE level 6, and Brotli quality 6 exactly once and chooses the smallest complete stored representation. For unsigned payloads that comparison includes the 16-byte extension-envelope cost, so Auto remains zero-overhead when compression is not useful.
169
+
170
+ `compression: "smart"` is the opt-in CPU-heavy policy. It begins with the Auto candidates, computes the resulting Format v5 QuadQR version using the selected ECC/profile/version bounds, and only escalates when the next smaller matrix is plausibly reachable. The strong stage tests DEFLATE 8 and Brotli 9. If the symbol remains close to a smaller-version boundary, the maximum stage tests DEFLATE 9 and Brotli 11. With an explicitly requested fixed version, Smart does not chase a smaller matrix, but it may escalate when the balanced result does not fit and stronger compression can plausibly make that requested version fit. Signed and secure pipelines include their fixed envelope overhead when evaluating those version boundaries.
171
+
172
+ Explicit `compression: "lz"` and `compression: "deflate"` accept `compressionLevel` `1..9` and default to 6. Explicit `compression: "brotli"` accepts `compressionLevel` `0..11` and defaults to 11. Compression levels are encoder-only parameters and are intentionally not stored in the extension envelope because LZ, RFC 1951, and Brotli decoders do not require them. Auto and Smart use LZ level 6; Smart's staged escalation remains focused on DEFLATE/Brotli.
153
173
 
154
174
  Compression occurs before signing, encryption, and Format v5 ECC.
155
175
 
@@ -157,7 +177,7 @@ Compression occurs before signing, encryption, and Format v5 ECC.
157
177
 
158
178
  Signature algorithm ID `1` is **Ed25519**.
159
179
 
160
- Signed payloads in extension v2 store:
180
+ Signed payloads in extension v2/v3 store:
161
181
 
162
182
  ```text
163
183
  optional compact key ID
package/bin/quadqr.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  import { savePNG, saveSVG, scanFile } from "../dist/node.js";
16
16
 
17
17
  function help() {
18
- console.log(`QuadQR CLI\n\nUsage:\n quadqr encode <text> [-o file.png|file.svg] [--ecc M] [--version auto|1..40]\n quadqr encode <text> [--compression auto]\n quadqr encode <text> --sign-key signing-key.json [--key-id issuer-main]\n quadqr encode <text> --password <password> [-o file.png|file.svg]\n quadqr decode <file.png> [--password <password> | --key <64-hex-key>] [--verify-key signing-key.json] [--debug]\n quadqr keygen\n quadqr signkeygen [-o signing-key.json]\n\nOptions:\n -o, --output <file> Output PNG/SVG path, or signing-key JSON for signkeygen\n --ecc <L|M|Q|H> ECC profile (default: M)\n --version <auto|1..40> Symbol version (default: auto)\n --compression <mode> none|auto|lz (default: auto)\n --sign-key <file> Sign using a signkeygen JSON bundle\n --key-id <id> Override the signing key ID stored in the QuadQR\n --embed-public-key Also embed the public key for untrusted/self-contained checks\n --verify-key <file> Verify a signed QuadQR with a trusted key bundle\n --password <text> Encrypt/decrypt with password mode\n --key <hex> Encrypt/decrypt with raw 256-bit key mode\n --print Use the print-safe render profile\n --image-size <px> Exact square output size (default: 720)\n --module-size <px> Legacy pixels-per-module sizing\n --quiet-zone <modules> Quiet zone in modules (default: 4)\n --debug Emit scanner diagnostics to stderr on decode\n -h, --help Show help\n`);
18
+ console.log(`QuadQR CLI\n\nUsage:\n quadqr encode <text> [-o file.png|file.svg] [--ecc M] [--version auto|1..40] [--high-density]\n quadqr encode <text> [--compression auto]\n quadqr encode <text> --sign-key signing-key.json [--key-id issuer-main]\n quadqr encode <text> --password <password> [-o file.png|file.svg]\n quadqr decode <file.png> [--password <password> | --key <64-hex-key>] [--verify-key signing-key.json] [--debug]\n quadqr keygen\n quadqr signkeygen [-o signing-key.json]\n\nOptions:\n -o, --output <file> Output PNG/SVG path, or signing-key JSON for signkeygen\n --ecc <L|M|Q|H> ECC profile (default: M)\n --version <auto|1..40> Symbol version (default: auto)\n --compression <mode> none|auto|smart|brotli|deflate|lz (default: auto)\n --compression-level <n> Explicit LZ/DEFLATE 1..9 or Brotli 0..11 level\n --high-density Enable experimental Triangle16 High Density Mode\n --sign-key <file> Sign using a signkeygen JSON bundle\n --key-id <id> Override the signing key ID stored in the QuadQR\n --embed-public-key Also embed the public key for untrusted/self-contained checks\n --verify-key <file> Verify a signed QuadQR with a trusted key bundle\n --password <text> Encrypt/decrypt with password mode\n --key <hex> Encrypt/decrypt with raw 256-bit key mode\n --print Use the print-safe render profile\n --image-size <px> Exact square output size (default: 720)\n --module-size <px> Legacy pixels-per-module sizing\n --quiet-zone <modules> Quiet zone in modules (default: 4)\n --debug Emit scanner diagnostics to stderr on decode\n -h, --help Show help\n`);
19
19
  }
20
20
 
21
21
  function parse(argv) {
@@ -27,9 +27,11 @@ function parse(argv) {
27
27
  else if (token === "-o" || token === "--output") flags.output = argv[++i];
28
28
  else if (token === "--ecc") flags.ecc = argv[++i];
29
29
  else if (token === "--version") flags.version = argv[++i];
30
+ else if (token === "--high-density") flags.highDensity = true;
30
31
  else if (token === "--password") flags.password = argv[++i];
31
32
  else if (token === "--key") flags.key = argv[++i];
32
33
  else if (token === "--compression") flags.compression = argv[++i];
34
+ else if (token === "--compression-level") flags.compressionLevel = Number(argv[++i]);
33
35
  else if (token === "--sign-key") flags.signKey = argv[++i];
34
36
  else if (token === "--key-id") flags.keyId = argv[++i];
35
37
  else if (token === "--embed-public-key") flags.embedPublicKey = true;
@@ -101,7 +103,9 @@ async function main() {
101
103
 
102
104
  const options = {
103
105
  ecc: flags.ecc || "M",
106
+ highDensity: Boolean(flags.highDensity),
104
107
  compression: flags.compression || "auto",
108
+ ...(Number.isFinite(flags.compressionLevel) ? { compressionLevel: flags.compressionLevel } : {}),
105
109
  ...(flags.version && flags.version !== "auto" ? { version: Number(flags.version) } : {})
106
110
  };
107
111
  const signingBundle = flags.signKey ? await readSigningBundle(flags.signKey) : null;
@@ -138,7 +142,7 @@ async function main() {
138
142
  const saved = output.toLowerCase().endsWith(".svg")
139
143
  ? await saveSVG(code, output, renderOptions)
140
144
  : await savePNG(code, output, renderOptions);
141
- console.log(`Saved ${output} (${saved.bytes} bytes, v${code.version}, ${code.size}x${code.size}, ECC ${code.eccLevel}).`);
145
+ console.log(`Saved ${output} (${saved.bytes} bytes, v${code.version}, ${code.size}x${code.size}, ${code.highDensity ? "High Density experimental" : "Normal RGBW"}, ECC ${code.eccLevel}${code.compressed ? `, ${code.compression}${code.compressionLevel != null ? ` level ${code.compressionLevel}` : ""}` : ""}).`);
142
146
  return;
143
147
  }
144
148