quadqr-js 1.0.1 → 1.0.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/README.md CHANGED
@@ -1,1058 +1,1069 @@
1
- # QuadQR (QQR)
2
-
3
- <p align="center">
4
- <img src="assets/banner.png?raw=true" alt="QuadQR banner">
5
- </p>
6
-
7
- <p align="center">
8
- <strong>Four visible states. Two bits per data cell. A modern experiment in high-density matrix codes.</strong>
9
- </p>
10
-
11
- <p align="center">
12
- <a href="https://akanshsirohi.github.io/QuadQR/demo/"><strong>Try the Live Demo</strong></a>
13
- ·
14
- <a href="https://akanshsirohi.github.io/QuadQR/docs-site/"><strong>Documentation Site</strong></a>
15
- ·
16
- <a href="https://www.npmjs.com/package/quadqr-js">npm</a>
17
- ·
18
- <a href="#current-benchmark">Benchmark</a>
19
- ·
20
- <a href="#getting-started">Use the Library</a>
21
- ·
22
- <a href="docs/README.md">Markdown Docs</a>
23
- ·
24
- <a href="FORMAT.md">Format Specification</a>
25
- </p>
26
-
27
- **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.
28
-
29
- Each QuadQR data cell represents exactly **2 bits**:
30
-
31
- | Color | Bits |
32
- |---|---|
33
- | Red | `00` |
34
- | Green | `01` |
35
- | Blue | `10` |
36
- | White | `11` |
37
-
38
- That gives QuadQR a four-symbol alphabet and a raw density of **2 bits per data cell**.
39
-
40
- 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.
41
-
42
- > **Important:** QuadQR is an experimental custom format. It is **not ISO QR Code**, and normal QR scanner apps cannot decode it.
43
-
44
- ## Examples
45
-
46
- Below are QuadQR symbols generated with different error-correction profiles. They use the same four-state RGBW data alphabet while varying the amount of space dedicated to Reed-Solomon protection.
47
-
48
- <table>
49
- <tr>
50
- <td align="center">
51
- <img src="assets/quadqr-v2-L.png?raw=true" alt="QuadQR example using ECC profile L" width="320"><br>
52
- <sub><strong>ECC L</strong> · Higher payload capacity</sub>
53
- </td>
54
- <td align="center">
55
- <img src="assets/quadqr-v2-M.png?raw=true" alt="QuadQR example using ECC profile M" width="320"><br>
56
- <sub><strong>ECC M</strong> · More error-correction redundancy</sub>
57
- </td>
58
- </tr>
59
- </table>
60
-
61
- <p align="center">
62
- <a href="https://akanshsirohi.github.io/QuadQR/demo/"><strong>Generate, scan, and benchmark QuadQR in your browser →</strong></a>
63
- </p>
64
-
65
- ---
66
-
67
- ## Why I built QuadQR
68
-
69
- I have always found QR codes fascinating. They are compact, practical, extremely well optimized, and they have continued to work reliably for decades.
70
-
71
- But one thing caught my attention: the basic visual data representation is still binary. A module is essentially one of two states, black or white, representing `0` or `1`.
72
-
73
- That made me wonder:
74
-
75
- > If modern cameras, displays, image processing, and computing are much more capable today, can a QR-like matrix code reliably use more than two visible states and store more information in the same area?
76
-
77
- I started researching the idea and working through the math. Four reliably distinguishable states are especially interesting because:
78
-
79
- ```text
80
- log2(4) = 2 bits
81
- ```
82
-
83
- So a four-state module can represent exactly two bits without requiring fractional-bit packing.
84
-
85
- That eventually became QuadQR.
86
-
87
- The current format uses **Red, Green, Blue, and White**, giving every data cell four possible states while keeping a square module grid that is easy to generate, sample, correct for perspective, and scan.
88
-
89
- This project is an experiment in exploring how far a QR-inspired design can be pushed with modern hardware and software.
90
-
91
- ### Development note
92
-
93
- I designed the format, researched the approach, iterated on the encoding structure, and built the project as an independent experiment. **OpenAI Codex** helped significantly with some of the more complex implementation work, including parts of the codec, error correction, image-processing logic, testing, and optimization.
94
-
95
- ---
96
-
97
- ## What makes QuadQR different?
98
-
99
- A traditional QR data module has two possible states:
100
-
101
- ```text
102
- Black
103
- White
104
- ```
105
-
106
- That gives:
107
-
108
- ```text
109
- log2(2) = 1 bit per module
110
- ```
111
-
112
- QuadQR uses four data states:
113
-
114
- ```text
115
- Red
116
- Green
117
- Blue
118
- White
119
- ```
120
-
121
- That gives:
122
-
123
- ```text
124
- log2(4) = 2 bits per data cell
125
- ```
126
-
127
- So at the raw data-cell level:
128
-
129
- | Format | States per data cell | Raw information |
130
- |---|---:|---:|
131
- | Binary QR | 2 | 1 bit |
132
- | QuadQR | 4 | 2 bits |
133
-
134
- This is a **2× raw symbol-density advantage**.
135
-
136
- ### Spectrum ECC: QuadQR-specific reliability without capacity loss
137
-
138
- 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**.
139
-
140
- 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.
141
-
142
- 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.
143
-
144
- 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**.
145
-
146
- These features do **not** change the RGBW alphabet, module count, ECC parity count, or payload-capacity calculation. They improve how the existing redundancy is used.
147
-
148
- However, raw cell density is not the same thing as final user-payload capacity. Finder patterns, timing structures, calibration cells, headers, CRC, masking, error correction, and other reserved cells all consume space.
149
-
150
- That is why QuadQR includes its own benchmark instead of assuming that every completed symbol will contain exactly twice the payload of a standard QR code.
151
-
152
- ---
153
-
154
- ## Secure Payload v1
155
-
156
- QuadQR now includes an **optional authenticated-encryption layer**. It sits above the matrix codec, so normal QuadQR symbols keep the same RGBW mapping, Spectrum ECC, scanner, and capacity behavior.
157
-
158
- Two security modes are supported:
159
-
160
- | Mode | Key source | Best suited for |
161
- |---|---|---|
162
- | Password | PBKDF2-HMAC-SHA-256 derives a 256-bit key from a password | Human-to-human protected payloads |
163
- | Raw 256-bit key | Exact 32-byte random key supplied by the application | Apps, tickets, provisioning, enterprise scanners |
164
-
165
- Both modes use **AES-256-GCM**, which provides encryption and authentication. A wrong password/key or modified encrypted payload fails decryption instead of returning silent garbage.
166
-
167
- Password mode currently defaults to **600,000 PBKDF2-HMAC-SHA-256 iterations** with a random 16-byte salt. Raw-key mode avoids password-KDF work and automatically stores an 8-byte SHA-256 key fingerprint as a non-secret key ID unless disabled or overridden. The actual raw key is never embedded in the symbol.
168
-
169
- The security envelope is versioned independently from the QuadQR matrix format so a future release can add another KDF or encryption mode without redesigning the RGBW/ECC layer.
170
-
171
- Security is intentionally opt-in. Unencrypted codes remain the default.
172
-
173
- The browser demo supports secure scanning in **both scanner paths**:
174
-
175
- - **Image scanner:** upload/exported images and photographs are decoded normally; secure symbols are detected automatically and show the matching password or raw-key decrypt control inline.
176
- - **Camera scanner:** live camera frames use the same secure-aware decode path. When an encrypted symbol is verified, scanning stops, the result is marked **Secure QR**, and plaintext is revealed only after successful authenticated decryption.
177
-
178
- The scanner never expects the secret to be embedded in the QuadQR itself. Raw-key symbols expose only their non-secret key ID/fingerprint.
179
-
180
- ---
181
-
182
- ## Current benchmark
183
-
184
- Run:
185
-
186
- ```bash
187
- npm run benchmark
188
- ```
189
-
190
- The benchmark compares QuadQR with standard QR **at the same matrix dimensions** using byte-mode QR reference capacities.
191
-
192
- Current representative results at the project's `M` ECC profile:
193
-
194
- | Version | Matrix | QuadQR payload | Standard QR payload | Gain | Ratio |
195
- |---:|---:|---:|---:|---:|---:|
196
- | 1 | 21×21 | **24 B** | 14 B | +10 B | **1.71×** |
197
- | 2 | 25×25 | **48 B** | 26 B | +22 B | **1.85×** |
198
- | 5 | 37×37 | **227 B** | 84 B | +143 B | **2.70×** |
199
- | 10 | 57×57 | **630 B** | 213 B | +417 B | **2.96×** |
200
- | 20 | 97×97 | **1992 B** | 666 B | +1326 B | **2.99×** |
201
- | 30 | 137×137 | **4054 B** | 1370 B | +2684 B | **2.96×** |
202
- | 40 | 177×177 | **6858 B** | 2331 B | +4527 B | **2.94×** |
203
-
204
- ### How to read this table
205
-
206
- For example:
207
-
208
- ```text
209
- Version 10
210
- Matrix: 57×57
211
-
212
- QuadQR: 630 bytes
213
- Standard QR: 213 bytes
214
-
215
- Gain: 417 bytes
216
- Ratio: 2.96×
217
- ```
218
-
219
- This means that under the benchmark's current assumptions, a 57×57 QuadQR symbol can carry 630 user-payload bytes, while the standard QR byte-mode reference at the same dimensions and `M` label carries 213 bytes.
220
-
221
- ### Important benchmark warning
222
-
223
- The letters `L`, `M`, `Q`, and `H` in QuadQR are **project-defined ECC profiles**.
224
-
225
- They do **not** currently claim the same standardized recovery percentages as ISO QR Code ECC levels with the same letters.
226
-
227
- Therefore:
228
-
229
- > The capacity benchmark is a same-dimension and same-label comparison, not yet an equal-damage-tolerance comparison.
230
-
231
- 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.
232
-
233
- 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.
234
-
235
- ---
236
-
237
- ## Performance benchmark
238
-
239
- The benchmark also measures direct codec performance.
240
-
241
- It reports:
242
-
243
- - payload size;
244
- - automatically selected version;
245
- - matrix dimensions;
246
- - mean encode time;
247
- - 95th percentile encode time;
248
- - mean decode time;
249
- - 95th percentile decode time.
250
-
251
- Example output:
252
-
253
- ```text
254
- payload_B matrix version encode_mean encode_p95 decode_mean decode_p95
255
- 24 21x21 v1 ...
256
- 128 33x33 v4 ...
257
- 512 53x53 v9 ...
258
- 1024 73x73 v14 ...
259
- 2048 101x101 v21 ...
260
- ```
261
-
262
- These timings measure the **matrix codec**, not the complete camera-scanning pipeline.
263
-
264
- Camera scanning additionally includes finder detection, perspective correction, calibration, module sampling, and color classification.
265
-
266
- For more stable local timing results, use more iterations:
267
-
268
- ```bash
269
- npm run benchmark -- --iterations=500
270
- ```
271
-
272
- For machine-readable results:
273
-
274
- ```bash
275
- npm run --silent benchmark -- --json
276
- ```
277
-
278
- ---
279
-
280
- ## Version 1 optimization
281
-
282
- Small symbols have a difficult tradeoff because fixed metadata and ECC consume a much larger fraction of the matrix.
283
-
284
- The original 21×21 QuadQR design could not fit a valid `M` payload because the protected framing itself was larger than the available data area.
285
-
286
- Version 1 now uses a dedicated compact small-symbol profile.
287
-
288
- At `M`:
289
-
290
- ```text
291
- 21×21 QuadQR
292
- 24 bytes user payload
293
- ```
294
-
295
- compared with:
296
-
297
- ```text
298
- 21×21 standard QR reference
299
- 14 bytes byte-mode payload
300
- ```
301
-
302
- Version 2 and above continue to use the normal framing and ECC structure.
303
-
304
- ---
305
-
306
- ## Encoding pipeline
307
-
308
- The current QuadQR pipeline is:
309
-
310
- ```text
311
- payload bytes
312
-
313
- optional Secure Payload v1 (AES-256-GCM)
314
-
315
- header + CRC-32
316
-
317
- GF(256) Reed-Solomon error correction
318
-
319
- interleaved byte codewords
320
-
321
- split every encoded byte into four 2-bit values
322
-
323
- zero-overhead spectral-spatial permutation
324
-
325
- 00 / 01 / 10 / 11
326
-
327
- Red / Green / Blue / White
328
-
329
- quaternary masking at physical positions
330
-
331
- square QuadQR matrix
332
- ```
333
-
334
- Because four states map naturally to two bits, QuadQR does not require base-3 conversion or fractional-bit packing.
335
-
336
- One encoded byte maps naturally to four data cells:
337
-
338
- ```text
339
- 8 bits ÷ 2 bits/cell = 4 cells
340
- ```
341
-
342
- ---
343
-
344
- ## Symbol structure
345
-
346
- QuadQR currently uses:
347
-
348
- - a square overall symbol;
349
- - square modules;
350
- - three 7×7 black-and-white finder patterns;
351
- - black-and-white timing structures;
352
- - one 5×5 primary alignment reference plus compact 3×3 secondary alignment markers on larger symbols;
353
- - RGB calibration swatches;
354
- - structural black/white references;
355
- - a two-column zig-zag physical data-position path;
356
- - deterministic spectral-spatial interleaving of logical codeword cells;
357
- - four-state masking;
358
- - GF(256) Reed-Solomon ECC with error + erasure decoding;
359
- - per-module RGBW confidence for scanner-assisted erasures;
360
- - CRC-32 integrity verification;
361
- - optional versioned AES-256-GCM Secure Payload envelope.
362
-
363
- White is a valid data state.
364
-
365
- QuadQR still uses exactly three large finder patterns, just like standard QR. Starting at version 2, alignment markers follow the standard QR version-dependent center schedule. The bottom-right alignment reference remains a full 5×5 marker and is used as the fourth homography reference; additional distributed markers are compact 3×3 black rings with white centers. Version 1 keeps one QuadQR-specific 5×5 bottom-right bootstrap marker because it otherwise would have no fourth projective reference.
366
-
367
- The decoder does not treat a white-looking area as automatically empty. It reconstructs the matrix geometry first and then determines whether a sampled position is structural or data.
368
-
369
- ---
370
-
371
- ## Color mapping
372
-
373
- The current mapping is intentionally simple:
374
-
375
- ```text
376
- 00 → Red
377
- 01 → Green
378
- 10 → Blue
379
- 11 → White
380
- ```
381
-
382
- Ideal display-space reference colors are conceptually:
383
-
384
- ```text
385
- Red → (255, 0, 0)
386
- Green → ( 0, 255, 0)
387
- Blue → ( 0, 0, 255)
388
- White → (255, 255, 255)
389
- ```
390
-
391
- Real camera input is not expected to match those exact values.
392
-
393
- 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.
394
-
395
- ---
396
-
397
- ## Reed-Solomon error correction
398
-
399
- QuadQR uses Reed-Solomon over:
400
-
401
- ```text
402
- GF(2^8) = GF(256)
403
- ```
404
-
405
- Primitive polynomial:
406
-
407
- ```text
408
- x^8 + x^4 + x^3 + x^2 + 1
409
- 0x11d
410
- ```
411
-
412
- One Reed-Solomon symbol is one byte.
413
-
414
- Since one byte becomes four QuadQR data cells, ECC symbols stay naturally aligned with the RGBW representation.
415
-
416
- ### Versions 2 through 40
417
-
418
- | Profile | Parity bytes per body block | Correctable byte symbols per block |
419
- |---|---:|---:|
420
- | L | 12 | 6 |
421
- | M | 24 | 12 |
422
- | Q | 36 | 18 |
423
- | H | 48 | 24 |
424
-
425
- ### Version 1 compact profile
426
-
427
- | Profile | Body parity bytes | Correctable body byte symbols |
428
- |---|---:|---:|
429
- | L | 4 | 2 |
430
- | M | 8 | 4 |
431
- | Q | 12 | 6 |
432
- | H | 16 | 8 |
433
-
434
- Version 1 also uses a compact 4-byte logical header protected by 4 Reed-Solomon parity bytes, correcting up to 2 damaged header byte symbols.
435
-
436
- CRC-32 remains the final integrity check.
437
-
438
- ---
439
-
440
- ## Scanner pipeline
441
-
442
- The image/camera scanner currently follows this general pipeline:
443
-
444
- ```text
445
- camera or image RGB frame
446
-
447
- grayscale structural analysis
448
-
449
- finder candidate detection
450
-
451
- version hypothesis
452
-
453
- primary alignment search
454
-
455
- homography / perspective correction
456
-
457
- distributed alignment-grid validation
458
-
459
- module-grid reconstruction
460
-
461
- RGB + structural black/white calibration
462
-
463
- nearest-color classification + confidence scoring
464
-
465
- four-state unmasking
466
-
467
- reverse spectral-spatial permutation
468
-
469
- protected header Reed-Solomon hard decode
470
-
471
- confidence-guided erasure retry when needed
472
-
473
- body deinterleaving + error/erasure Reed-Solomon decode
474
-
475
- CRC-32 verification
476
-
477
- payload
478
- ```
479
-
480
- An axis-aligned fallback is also available for clean generated images and simple inputs.
481
-
482
- ---
483
-
484
- ## Rendering styles
485
-
486
- QuadQR keeps visual styling separate from the wire format. The encoded matrix is unchanged, so applications can choose a renderer without creating a new barcode format.
487
-
488
- The current render profiles are:
489
-
490
- | Style | Behavior |
491
- |---|---|
492
- | `classic` | Original fully solid square modules. |
493
- | `depth` | Deterministically mixes normal and lightly faded data tiles with subtle edge shading for a layered/3D appearance. |
494
- | `soft` | Uses rounded data tiles for a softer visual treatment. |
495
- | `inset` | Uses narrow recessed edge lighting while preserving the exact encoded center color. |
496
-
497
- Finder patterns, timing structures, alignment markers, and calibration cells stay **square and fully solid** in styled output so the visual treatment does not weaken the scanner's geometric/color references.
498
-
499
- The `inset` style preserves the exact encoded R/G/B/W color around the center of every data module. Its recessed shading is confined to a narrow outer band, and white data modules remain pure white. This matches the scanner's center-sampling strategy and avoids visual effects bleeding into neighboring cells.
500
-
501
- ```js
502
- renderToCanvas(code, canvas, {
503
- moduleSize: 12,
504
- quietZone: 4,
505
- style: "inset" // classic | depth | soft | inset
506
- });
507
- ```
508
-
509
- The styling is deterministic. Generating the same matrix with the same style produces the same visual tile treatment rather than changing randomly on every render.
510
-
511
- ---
512
-
513
- ## Demo
514
-
515
- **Live demo:** https://akanshsirohi.github.io/QuadQR/demo/
516
-
517
- The browser demo runs directly on GitHub Pages and is split into separate views so the interface does not become overloaded.
518
-
519
- ### Generator & Image Scanner
520
-
521
- Use this tab to:
522
-
523
- - enter text/data;
524
- - generate a QuadQR code;
525
- - select ECC;
526
- - optionally encrypt using a password or raw 256-bit key;
527
- - inspect version and capacity;
528
- - download the generated image;
529
- - scan an uploaded image.
530
-
531
- ### Camera Scanner
532
-
533
- The live camera scanner has its own dedicated tab.
534
-
535
- This keeps the generator interface lightweight and prevents an always-visible video element from making the main page bulky.
536
-
537
- The camera stream automatically stops when you leave the camera tab.
538
-
539
- ### Benchmark
540
-
541
- The benchmark tab provides an easier visual view of:
542
-
543
- - capacity by matrix size;
544
- - comparison with standard QR;
545
- - byte gain;
546
- - capacity ratio;
547
- - codec timing.
548
-
549
- ---
550
-
551
- ## Getting started
552
-
553
- QuadQR can be used as an npm package, directly from a CDN, or from this repository.
554
-
555
- ### Requirements
556
-
557
- - **Node.js 20.19+** for Node.js and CommonJS usage
558
- - npm or another Node package manager for installation
559
- - A modern browser for the demo and documentation site
560
- - HTTPS or localhost for browser camera access
561
-
562
- ### Install from npm
563
-
564
- ```bash
565
- npm install quadqr-js
566
- ```
567
-
568
- Core usage:
569
-
570
- ```js
571
- import { encodeText, decodeMatrix } from "quadqr-js";
572
-
573
- const code = encodeText("Hello from QuadQR", { ecc: "M" });
574
- const result = decodeMatrix(code.matrix);
575
- console.log(result.text);
576
- ```
577
-
578
- Node PNG usage:
579
-
580
- ```js
581
- import { encodeText } from "quadqr-js";
582
- import { savePNG, scanFile } from "quadqr-js/node";
583
-
584
- const code = encodeText("Generated on Node.js");
585
- await savePNG(code, "quadqr.png", { moduleSize: 12, quietZone: 4 });
586
-
587
- const result = await scanFile("quadqr.png");
588
- console.log(result.text);
589
- ```
590
-
591
- ### CDN / script tag
592
-
593
- The `quadqr-js` package can be loaded directly from npm-backed CDNs:
594
-
595
- ```html
596
- <script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.0.1/dist/quadqr.min.js"></script>
597
- <script>
598
- const code = QuadQR.encodeText("Hello from a script tag");
599
- </script>
600
- ```
601
-
602
- The same file is available through unpkg. Pin an exact version in production.
603
-
604
- ### CLI
605
-
606
- Encode and decode directly from the terminal:
607
-
608
- ```bash
609
- npx quadqr-js encode "Hello QuadQR" -o hello.png
610
- npx quadqr-js decode hello.png
611
- ```
612
-
613
- Password-protected payloads use the same commands:
614
-
615
- ```bash
616
- npx quadqr-js encode "Private data" --password "my-password" -o secure.png
617
- npx quadqr-js decode secure.png --password "my-password"
618
- ```
619
-
620
- See [`docs/CLI.md`](docs/CLI.md) for all CLI options, including raw 256-bit key mode.
621
-
622
- ### Run from source
623
-
624
- ```bash
625
- git clone https://github.com/akanshsirohi/QuadQR.git
626
- cd QuadQR
627
- npm install
628
- ```
629
-
630
- Run the complete test suite:
631
-
632
- ```bash
633
- npm test
634
- ```
635
-
636
- Build the distributable browser, Node.js, CDN, and WASM files:
637
-
638
- ```bash
639
- npm run build
640
- ```
641
-
642
- Start the interactive demo:
643
-
644
- ```bash
645
- npm start
646
- ```
647
-
648
- Start the documentation site:
649
-
650
- ```bash
651
- npm run docs
652
- ```
653
-
654
- Run benchmarks:
655
-
656
- ```bash
657
- npm run benchmark
658
- ```
659
-
660
- ### Optional WASM acceleration
661
-
662
- The package ships a prebuilt WASM helper but never requires it.
663
-
664
- ```js
665
- import { initWasm } from "quadqr-js";
666
-
667
- await initWasm();
668
- ```
669
-
670
- If WASM cannot load, the normal JavaScript codec remains available.
671
-
672
- ---
673
-
674
- ## Package entry points
675
-
676
- | Import | Purpose |
677
- |---|---|
678
- | `quadqr-js` | Runtime-neutral core, secure payloads, rendering, scanning, optional WASM |
679
- | `quadqr-js/browser` | Browser ESM entry |
680
- | `quadqr-js/node` | Node core plus PNG/file/buffer helpers |
681
- | `quadqr-js/benchmark` | Capacity and codec benchmark helpers |
682
- | `quadqr-js/quadqr.min.js` | Classic browser global/CDN bundle |
683
-
684
- The Node PNG path is dependency-free. For JPEG, WebP, or AVIF input, the Node adapter can use `sharp` when the consuming application already has it installed.
685
-
686
- ---
687
-
688
- ## Repository layout
689
-
690
- ```text
691
- library/
692
- quadqr.js Core encoder, decoder, renderer, scanner, and public API
693
- security.js Secure Payload v1 encryption and key handling
694
- reed-solomon.js GF(256) Reed-Solomon implementation
695
- geometry.js Version, size, and alignment geometry
696
- vision.js Finder detection, perspective correction, and color sampling
697
- node.js Node PNG/file/buffer adapters
698
- wasm.js Optional prebuilt WASM loader
699
- benchmark.js Reusable benchmark utilities
700
-
701
- wasm-src/
702
- quadqr_core.c Small portable WASM accelerator source
703
-
704
- wasm/
705
- quadqr-core.wasm Source-tree WASM build output
706
-
707
- dist/
708
- index.js ESM package entry
709
- index.cjs Modern CommonJS wrapper
710
- browser.js Browser ESM entry
711
- node.js Node entry
712
- quadqr.js Classic global browser bundle
713
- quadqr.min.js Compact CDN browser bundle
714
- wasm/ Prebuilt WASM package asset
715
-
716
- demo/
717
- index.html Interactive generator, image scanner, camera scanner, benchmark
718
- app.js
719
- styles.css
720
-
721
- docs-site/
722
- index.html Standalone documentation website
723
- app.js
724
- styles.css
725
-
726
- docs/
727
- README.md Markdown documentation index
728
- GETTING_STARTED.md
729
- API.md
730
- BROWSER_CDN.md
731
- NODE.md
732
- SECURITY.md
733
- CLI.md
734
- WASM.md
735
-
736
- types/ TypeScript declarations for JavaScript consumers
737
- bin/ `quadqr` CLI (`npx quadqr-js`)
738
- scripts/ Build, benchmark, and local server scripts
739
- tests/ Codec and package distribution tests
740
- FORMAT.md Wire-format specification
741
- AGENT.md Development guidance
742
- README.md Project overview
743
- ```
744
-
745
- ---
746
-
747
- ## Main API
748
-
749
- ### `encodeText(text, options?)`
750
-
751
- ```js
752
- const code = encodeText("Hello from QuadQR", {
753
- version: "auto",
754
- minVersion: 1,
755
- maxVersion: 40,
756
- ecc: "M"
757
- });
758
- ```
759
-
760
- ### `encodeBytes(bytes, options?)`
761
-
762
- Encodes arbitrary binary data from a `Uint8Array`.
763
-
764
- ```js
765
- const code = encodeBytes(myBytes, {
766
- version: "auto",
767
- ecc: "M"
768
- });
769
- ```
770
-
771
- ### `encodeSecureText(text, options?)`
772
-
773
- Secure encoding is asynchronous because it uses Web Crypto.
774
-
775
- Password mode:
776
-
777
- ```js
778
- const code = await encodeSecureText("Private message", {
779
- ecc: "M",
780
- security: {
781
- mode: "password",
782
- password: "correct horse battery staple"
783
- }
784
- });
785
- ```
786
-
787
- Raw 256-bit key mode:
788
-
789
- ```js
790
- const key = generateRaw256Key();
791
-
792
- const code = await encodeSecureText("Device configuration", {
793
- ecc: "M",
794
- security: {
795
- mode: "raw-key",
796
- key
797
- }
798
- });
799
- ```
800
-
801
- Raw keys may be supplied as an exact 32-byte `Uint8Array` or as a 64-character hexadecimal string. By default, raw-key envelopes include a short SHA-256 key fingerprint (`keyIdHex`) that helps an application select the correct secret key without storing the key inside the QuadQR.
802
-
803
- ### `encodeSecureBytes(bytes, options?)`
804
-
805
- Binary equivalent of `encodeSecureText()`.
806
-
807
- ### `decryptDecoded(result, credentials)`
808
-
809
- A secure matrix/image scan first returns encrypted metadata without exposing plaintext:
810
-
811
- ```js
812
- const locked = decodeMatrix(matrix);
813
-
814
- console.log(locked.secure); // true
815
- console.log(locked.requiresDecryption); // true
816
- console.log(locked.security.mode); // password | raw-key
817
- ```
818
-
819
- Then decrypt it:
820
-
821
- ```js
822
- const result = await decryptDecoded(locked, {
823
- password: "correct horse battery staple"
824
- });
825
-
826
- console.log(result.text);
827
- ```
828
-
829
- For raw-key mode:
830
-
831
- ```js
832
- const result = await decryptDecoded(locked, { key });
833
- ```
834
-
835
- The decrypted result preserves the encrypted envelope as `encryptedPayload` for applications that need both forms.
836
-
837
- ### `decodeMatrix(matrix, options?)`
838
-
839
- Decodes an already reconstructed QuadQR matrix.
840
-
841
- ```js
842
- const result = decodeMatrix(matrix);
843
- ```
844
-
845
- When a scanner has per-cell confidence values, they can also be supplied directly:
846
-
847
- ```js
848
- const result = decodeMatrix(matrix, {
849
- cellConfidence: confidenceMatrix
850
- });
851
- ```
852
-
853
- The result reports fields such as `spectralInterleaving`, `confidenceAssisted`, `erasureSymbols`, and `correctedSymbols`. Secure symbols additionally report `secure`, `requiresDecryption`, and parsed `security` metadata while keeping `text` unset until successful decryption.
854
-
855
- ### `renderToCanvas(codeOrMatrix, canvas, options?)`
856
-
857
- Renders a QuadQR symbol into a browser canvas. `options.style` supports `classic`, `depth`, `soft`, and `inset`.
858
-
859
- ### `renderToImageData(codeOrMatrix, options?)`
860
-
861
- Returns an ImageData-like object and supports the same rendering styles as `renderToCanvas()`:
862
-
863
- ```js
864
- {
865
- width,
866
- height,
867
- data
868
- }
869
- ```
870
-
871
- This is also useful for tests and non-DOM workflows.
872
-
873
- ### `scanImageData(imageData, options?)`
874
-
875
- Runs the complete perspective-aware and color-aware image scanner. The scanner automatically computes RGBW confidence values and uses confidence-guided erasure decoding only when ordinary hard-decision ECC is insufficient.
876
-
877
- ### `scanFile(file, options?)`
878
-
879
- Scans an uploaded browser image file.
880
-
881
- ### `scanVideoFrame(video, options?)`
882
-
883
- Scans one frame from an HTML video element.
884
-
885
- ### `startCameraScanner(video, options?)`
886
-
887
- Starts a reusable live-camera scanning loop.
888
-
889
- ### `getVersionInfo(version, options?)`
890
-
891
- Returns information such as:
892
-
893
- - matrix size;
894
- - number of data cells;
895
- - theoretical raw bits;
896
- - usable payload capacity;
897
- - structural metadata.
898
-
899
- ---
900
-
901
- ## Tests
902
-
903
- Run:
904
-
905
- ```bash
906
- npm test
907
- ```
908
-
909
- The current test suite covers areas including:
910
-
911
- - RGBW 2-bit mapping;
912
- - binary round trips;
913
- - Unicode text round trips;
914
- - automatic version selection;
915
- - all ECC profiles;
916
- - version 1 compact framing;
917
- - capacity boundaries;
918
- - deliberate Reed-Solomon corruption recovery;
919
- - Reed-Solomon error + erasure recovery;
920
- - zero-overhead spectral-spatial permutation validation;
921
- - confidence-assisted recovery beyond the ordinary hard-error limit;
922
- - rotation handling;
923
- - generated-image scanning;
924
- - perspective distortion;
925
- - color-cast scanning;
926
- - benchmark reference data;
927
- - timed codec round trips;
928
- - password-mode secure round trips and wrong-password rejection;
929
- - raw 256-bit key round trips, key fingerprinting, and wrong-key rejection;
930
- - secure rendered-image scan and decryption.
931
-
932
- ---
933
-
934
- ## Current limitations
935
-
936
- QuadQR is still experimental.
937
-
938
- Important areas that need more research and real-device testing include:
939
-
940
- - printed codes across different printers and inks;
941
- - paper color and reflectivity;
942
- - display brightness and color profiles;
943
- - screen glare;
944
- - moiré patterns;
945
- - motion blur;
946
- - very small modules;
947
- - extreme camera angles;
948
- - low-light scanning;
949
- - RGBW confusion under difficult illumination;
950
- - damaged or partially hidden symbols;
951
- - standardized recovery percentages;
952
- - equal-reliability comparison with ISO QR Code;
953
- - local/non-projective distortion correction using the distributed alignment grid;
954
- - formal print-quality grading;
955
- - performance across different phones and camera systems;
956
- - memory-hard password KDF option such as Argon2id for environments where a small WASM/runtime dependency is acceptable;
957
- - public/private-key secure payload mode.
958
-
959
- The project should currently be treated as a research and experimental implementation rather than a replacement for standardized QR Code in production-critical environments.
960
-
961
- ---
962
-
963
- ## Roadmap
964
-
965
- - [ ] Equal-reliability benchmark against standard QR
966
- - [ ] Automated camera torture-test suite
967
- - [ ] Blur, JPEG, noise, perspective, and lighting benchmarks
968
- - [ ] Print-and-rescan dataset
969
- - [ ] Confidence-based RGBW classification
970
- - [ ] Improved adaptive color calibration
971
- - [x] Distributed alignment patterns for large versions
972
- - [ ] Interleaving tuned for localized physical damage
973
- - [ ] Real-device benchmark dataset
974
- - [ ] Formal versioned QuadQR specification
975
- - [ ] Implementations in additional languages
976
- - [ ] Independent decoder implementation
977
-
978
- ---
979
-
980
- ## Why keep the square design?
981
-
982
- QuadQR intentionally keeps square modules and a square overall matrix.
983
-
984
- Square cells:
985
-
986
- - tessellate without gaps;
987
- - provide predictable row/column addressing;
988
- - maximize colored area inside each module;
989
- - are easy to sample at their center;
990
- - work naturally with perspective correction;
991
- - keep the geometry relatively simple for camera scanning.
992
-
993
- Alternative module shapes may be interesting visually, but the current focus is data density, reliability, and scan robustness.
994
-
995
- ---
996
-
997
- ## Is QuadQR a QR Code replacement?
998
-
999
- Not currently.
1000
-
1001
- Standard QR Code has enormous advantages:
1002
-
1003
- - decades of deployment;
1004
- - international standardization;
1005
- - extremely mature decoders;
1006
- - broad device support;
1007
- - extensive real-world testing;
1008
- - proven print reliability.
1009
-
1010
- QuadQR is exploring a different question:
1011
-
1012
- > **What can a QR-inspired matrix code look like if we design its data layer around modern color-capable cameras and displays?**
1013
-
1014
- The goal is experimentation, measurement, and learning.
1015
-
1016
- ---
1017
-
1018
- ## Contributing
1019
-
1020
- Contributions, experiments, test images, scanner improvements, benchmarking ideas, and independent implementations are welcome.
1021
-
1022
- If you are contributing changes to the wire format, please also update:
1023
-
1024
- ```text
1025
- FORMAT.md
1026
- ```
1027
-
1028
- Changes that affect capacity, ECC, scanning behavior, or version selection should include tests where practical.
1029
-
1030
- ---
1031
-
1032
- ## Security
1033
-
1034
- Decoded payloads are untrusted input.
1035
-
1036
- Do not automatically execute decoded:
1037
-
1038
- - HTML;
1039
- - JavaScript;
1040
- - shell commands;
1041
- - application commands;
1042
- - URLs.
1043
-
1044
- Applications using QuadQR should validate and safely handle decoded content just as they would any other external input.
1045
-
1046
- ---
1047
-
1048
- ## License
1049
-
1050
- AGPL v3.0. See `LICENSE`.
1051
-
1052
- ---
1053
-
1054
- ## Project status
1055
-
1056
- **Experimental / research project**
1057
-
1
+ # QuadQR (QQR)
2
+
3
+ <p align="center">
4
+ <img src="assets/banner.png?raw=true" alt="QuadQR banner">
5
+ </p>
6
+
7
+ <p align="center">
8
+ <strong>Four visible states. Two bits per data cell. A modern experiment in high-density matrix codes.</strong>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://akanshsirohi.github.io/QuadQR/demo/"><strong>Try the Live Demo</strong></a>
13
+ ·
14
+ <a href="https://akanshsirohi.github.io/QuadQR/docs-site/"><strong>Documentation Site</strong></a>
15
+ ·
16
+ <a href="https://www.npmjs.com/package/quadqr-js">npm</a>
17
+ ·
18
+ <a href="#current-benchmark">Benchmark</a>
19
+ ·
20
+ <a href="#getting-started">Use the Library</a>
21
+ ·
22
+ <a href="docs/README.md">Markdown Docs</a>
23
+ ·
24
+ <a href="FORMAT.md">Format Specification</a>
25
+ </p>
26
+
27
+ **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.
28
+
29
+ Each QuadQR data cell represents exactly **2 bits**:
30
+
31
+ | Color | Bits |
32
+ |---|---|
33
+ | Red | `00` |
34
+ | Green | `01` |
35
+ | Blue | `10` |
36
+ | White | `11` |
37
+
38
+ That gives QuadQR a four-symbol alphabet and a raw density of **2 bits per data cell**.
39
+
40
+ 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.
41
+
42
+ > **Important:** QuadQR is an experimental custom format. It is **not ISO QR Code**, and normal QR scanner apps cannot decode it.
43
+
44
+ ## Examples
45
+
46
+ Below are QuadQR symbols generated with different error-correction profiles. They use the same four-state RGBW data alphabet while varying the amount of space dedicated to Reed-Solomon protection.
47
+
48
+ <table>
49
+ <tr>
50
+ <td align="center">
51
+ <img src="assets/quadqr-v2-L.png?raw=true" alt="QuadQR example using ECC profile L" width="320"><br>
52
+ <sub><strong>ECC L</strong> · Higher payload capacity</sub>
53
+ </td>
54
+ <td align="center">
55
+ <img src="assets/quadqr-v2-M.png?raw=true" alt="QuadQR example using ECC profile M" width="320"><br>
56
+ <sub><strong>ECC M</strong> · More error-correction redundancy</sub>
57
+ </td>
58
+ </tr>
59
+ </table>
60
+
61
+ <p align="center">
62
+ <a href="https://akanshsirohi.github.io/QuadQR/demo/"><strong>Generate, scan, and benchmark QuadQR in your browser →</strong></a>
63
+ </p>
64
+
65
+ ---
66
+
67
+ ## Why I built QuadQR
68
+
69
+ I have always found QR codes fascinating. They are compact, practical, extremely well optimized, and they have continued to work reliably for decades.
70
+
71
+ But one thing caught my attention: the basic visual data representation is still binary. A module is essentially one of two states, black or white, representing `0` or `1`.
72
+
73
+ That made me wonder:
74
+
75
+ > If modern cameras, displays, image processing, and computing are much more capable today, can a QR-like matrix code reliably use more than two visible states and store more information in the same area?
76
+
77
+ I started researching the idea and working through the math. Four reliably distinguishable states are especially interesting because:
78
+
79
+ ```text
80
+ log2(4) = 2 bits
81
+ ```
82
+
83
+ So a four-state module can represent exactly two bits without requiring fractional-bit packing.
84
+
85
+ That eventually became QuadQR.
86
+
87
+ The current format uses **Red, Green, Blue, and White**, giving every data cell four possible states while keeping a square module grid that is easy to generate, sample, correct for perspective, and scan.
88
+
89
+ This project is an experiment in exploring how far a QR-inspired design can be pushed with modern hardware and software.
90
+
91
+ ### Development note
92
+
93
+ I designed the format, researched the approach, iterated on the encoding structure, and built the project as an independent experiment. **OpenAI Codex** helped significantly with some of the more complex implementation work, including parts of the codec, error correction, image-processing logic, testing, and optimization.
94
+
95
+ ---
96
+
97
+ ## What makes QuadQR different?
98
+
99
+ A traditional QR data module has two possible states:
100
+
101
+ ```text
102
+ Black
103
+ White
104
+ ```
105
+
106
+ That gives:
107
+
108
+ ```text
109
+ log2(2) = 1 bit per module
110
+ ```
111
+
112
+ QuadQR uses four data states:
113
+
114
+ ```text
115
+ Red
116
+ Green
117
+ Blue
118
+ White
119
+ ```
120
+
121
+ That gives:
122
+
123
+ ```text
124
+ log2(4) = 2 bits per data cell
125
+ ```
126
+
127
+ So at the raw data-cell level:
128
+
129
+ | Format | States per data cell | Raw information |
130
+ |---|---:|---:|
131
+ | Binary QR | 2 | 1 bit |
132
+ | QuadQR | 4 | 2 bits |
133
+
134
+ This is a **2× raw symbol-density advantage**.
135
+
136
+ ### Spectrum ECC: QuadQR-specific reliability without capacity loss
137
+
138
+ 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**.
139
+
140
+ 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.
141
+
142
+ 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.
143
+
144
+ 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**.
145
+
146
+ These features do **not** change the RGBW alphabet, module count, ECC parity count, or payload-capacity calculation. They improve how the existing redundancy is used.
147
+
148
+ However, raw cell density is not the same thing as final user-payload capacity. Finder patterns, timing structures, calibration cells, headers, CRC, masking, error correction, and other reserved cells all consume space.
149
+
150
+ That is why QuadQR includes its own benchmark instead of assuming that every completed symbol will contain exactly twice the payload of a standard QR code.
151
+
152
+ ---
153
+
154
+ ## Secure Payload v1
155
+
156
+ QuadQR now includes an **optional authenticated-encryption layer**. It sits above the matrix codec, so normal QuadQR symbols keep the same RGBW mapping, Spectrum ECC, scanner, and capacity behavior.
157
+
158
+ Two security modes are supported:
159
+
160
+ | Mode | Key source | Best suited for |
161
+ |---|---|---|
162
+ | Password | PBKDF2-HMAC-SHA-256 derives a 256-bit key from a password | Human-to-human protected payloads |
163
+ | Raw 256-bit key | Exact 32-byte random key supplied by the application | Apps, tickets, provisioning, enterprise scanners |
164
+
165
+ Both modes use **AES-256-GCM**, which provides encryption and authentication. A wrong password/key or modified encrypted payload fails decryption instead of returning silent garbage.
166
+
167
+ Password mode currently defaults to **600,000 PBKDF2-HMAC-SHA-256 iterations** with a random 16-byte salt. Raw-key mode avoids password-KDF work and automatically stores an 8-byte SHA-256 key fingerprint as a non-secret key ID unless disabled or overridden. The actual raw key is never embedded in the symbol.
168
+
169
+ The security envelope is versioned independently from the QuadQR matrix format so a future release can add another KDF or encryption mode without redesigning the RGBW/ECC layer.
170
+
171
+ Security is intentionally opt-in. Unencrypted codes remain the default.
172
+
173
+ The browser demo supports secure scanning in **both scanner paths**:
174
+
175
+ - **Image scanner:** upload/exported images and photographs are decoded normally; secure symbols are detected automatically and show the matching password or raw-key decrypt control inline.
176
+ - **Camera scanner:** live camera frames use the same secure-aware decode path. When an encrypted symbol is verified, scanning stops, the result is marked **Secure QR**, and plaintext is revealed only after successful authenticated decryption.
177
+
178
+ The scanner never expects the secret to be embedded in the QuadQR itself. Raw-key symbols expose only their non-secret key ID/fingerprint.
179
+
180
+ ---
181
+
182
+ ## Current benchmark
183
+
184
+ Run:
185
+
186
+ ```bash
187
+ npm run benchmark
188
+ ```
189
+
190
+ The benchmark compares QuadQR with standard QR **at the same matrix dimensions** using byte-mode QR reference capacities.
191
+
192
+ Current representative results at the project's `M` ECC profile:
193
+
194
+ | Version | Matrix | QuadQR payload | Standard QR payload | Gain | Ratio |
195
+ |---:|---:|---:|---:|---:|---:|
196
+ | 1 | 21×21 | **24 B** | 14 B | +10 B | **1.71×** |
197
+ | 2 | 25×25 | **48 B** | 26 B | +22 B | **1.85×** |
198
+ | 5 | 37×37 | **227 B** | 84 B | +143 B | **2.70×** |
199
+ | 10 | 57×57 | **630 B** | 213 B | +417 B | **2.96×** |
200
+ | 20 | 97×97 | **1992 B** | 666 B | +1326 B | **2.99×** |
201
+ | 30 | 137×137 | **4054 B** | 1370 B | +2684 B | **2.96×** |
202
+ | 40 | 177×177 | **6858 B** | 2331 B | +4527 B | **2.94×** |
203
+
204
+ ### How to read this table
205
+
206
+ For example:
207
+
208
+ ```text
209
+ Version 10
210
+ Matrix: 57×57
211
+
212
+ QuadQR: 630 bytes
213
+ Standard QR: 213 bytes
214
+
215
+ Gain: 417 bytes
216
+ Ratio: 2.96×
217
+ ```
218
+
219
+ This means that under the benchmark's current assumptions, a 57×57 QuadQR symbol can carry 630 user-payload bytes, while the standard QR byte-mode reference at the same dimensions and `M` label carries 213 bytes.
220
+
221
+ ### Important benchmark warning
222
+
223
+ The letters `L`, `M`, `Q`, and `H` in QuadQR are **project-defined ECC profiles**.
224
+
225
+ They do **not** currently claim the same standardized recovery percentages as ISO QR Code ECC levels with the same letters.
226
+
227
+ Therefore:
228
+
229
+ > The capacity benchmark is a same-dimension and same-label comparison, not yet an equal-damage-tolerance comparison.
230
+
231
+ 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.
232
+
233
+ 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.
234
+
235
+ ---
236
+
237
+ ## Performance benchmark
238
+
239
+ The benchmark also measures direct codec performance.
240
+
241
+ It reports:
242
+
243
+ - payload size;
244
+ - automatically selected version;
245
+ - matrix dimensions;
246
+ - mean encode time;
247
+ - 95th percentile encode time;
248
+ - mean decode time;
249
+ - 95th percentile decode time.
250
+
251
+ Example output:
252
+
253
+ ```text
254
+ payload_B matrix version encode_mean encode_p95 decode_mean decode_p95
255
+ 24 21x21 v1 ...
256
+ 128 33x33 v4 ...
257
+ 512 53x53 v9 ...
258
+ 1024 73x73 v14 ...
259
+ 2048 101x101 v21 ...
260
+ ```
261
+
262
+ These timings measure the **matrix codec**, not the complete camera-scanning pipeline.
263
+
264
+ Camera scanning additionally includes finder detection, perspective correction, calibration, module sampling, and color classification.
265
+
266
+ For more stable local timing results, use more iterations:
267
+
268
+ ```bash
269
+ npm run benchmark -- --iterations=500
270
+ ```
271
+
272
+ For machine-readable results:
273
+
274
+ ```bash
275
+ npm run --silent benchmark -- --json
276
+ ```
277
+
278
+ ---
279
+
280
+ ## Version 1 optimization
281
+
282
+ Small symbols have a difficult tradeoff because fixed metadata and ECC consume a much larger fraction of the matrix.
283
+
284
+ The original 21×21 QuadQR design could not fit a valid `M` payload because the protected framing itself was larger than the available data area.
285
+
286
+ Version 1 now uses a dedicated compact small-symbol profile.
287
+
288
+ At `M`:
289
+
290
+ ```text
291
+ 21×21 QuadQR
292
+ 24 bytes user payload
293
+ ```
294
+
295
+ compared with:
296
+
297
+ ```text
298
+ 21×21 standard QR reference
299
+ 14 bytes byte-mode payload
300
+ ```
301
+
302
+ Version 2 and above continue to use the normal framing and ECC structure.
303
+
304
+ ---
305
+
306
+ ## Encoding pipeline
307
+
308
+ The current QuadQR pipeline is:
309
+
310
+ ```text
311
+ payload bytes
312
+
313
+ optional Secure Payload v1 (AES-256-GCM)
314
+
315
+ header + CRC-32
316
+
317
+ GF(256) Reed-Solomon error correction
318
+
319
+ interleaved byte codewords
320
+
321
+ split every encoded byte into four 2-bit values
322
+
323
+ zero-overhead spectral-spatial permutation
324
+
325
+ 00 / 01 / 10 / 11
326
+
327
+ Red / Green / Blue / White
328
+
329
+ quaternary masking at physical positions
330
+
331
+ square QuadQR matrix
332
+ ```
333
+
334
+ Because four states map naturally to two bits, QuadQR does not require base-3 conversion or fractional-bit packing.
335
+
336
+ One encoded byte maps naturally to four data cells:
337
+
338
+ ```text
339
+ 8 bits ÷ 2 bits/cell = 4 cells
340
+ ```
341
+
342
+ ---
343
+
344
+ ## Symbol structure
345
+
346
+ QuadQR currently uses:
347
+
348
+ - a square overall symbol;
349
+ - square modules;
350
+ - three 7×7 black-and-white finder patterns;
351
+ - black-and-white timing structures;
352
+ - one 5×5 primary alignment reference plus compact 3×3 secondary alignment markers on larger symbols;
353
+ - RGB calibration swatches;
354
+ - structural black/white references;
355
+ - a two-column zig-zag physical data-position path;
356
+ - deterministic spectral-spatial interleaving of logical codeword cells;
357
+ - four-state masking;
358
+ - GF(256) Reed-Solomon ECC with error + erasure decoding;
359
+ - per-module RGBW confidence for scanner-assisted erasures;
360
+ - CRC-32 integrity verification;
361
+ - optional versioned AES-256-GCM Secure Payload envelope.
362
+
363
+ White is a valid data state.
364
+
365
+ QuadQR still uses exactly three large finder patterns, just like standard QR. Starting at version 2, alignment markers follow the standard QR version-dependent center schedule. The bottom-right alignment reference remains a full 5×5 marker and is used as the fourth homography reference; additional distributed markers are compact 3×3 black rings with white centers. Version 1 keeps one QuadQR-specific 5×5 bottom-right bootstrap marker because it otherwise would have no fourth projective reference.
366
+
367
+ The decoder does not treat a white-looking area as automatically empty. It reconstructs the matrix geometry first and then determines whether a sampled position is structural or data.
368
+
369
+ ---
370
+
371
+ ## Color mapping
372
+
373
+ The current mapping is intentionally simple:
374
+
375
+ ```text
376
+ 00 → Red
377
+ 01 → Green
378
+ 10 → Blue
379
+ 11 → White
380
+ ```
381
+
382
+ Ideal display-space reference colors are conceptually:
383
+
384
+ ```text
385
+ Red → (255, 0, 0)
386
+ Green → ( 0, 255, 0)
387
+ Blue → ( 0, 0, 255)
388
+ White → (255, 255, 255)
389
+ ```
390
+
391
+ Real camera input is not expected to match those exact values.
392
+
393
+ 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.
394
+
395
+ ---
396
+
397
+ ## Reed-Solomon error correction
398
+
399
+ QuadQR uses Reed-Solomon over:
400
+
401
+ ```text
402
+ GF(2^8) = GF(256)
403
+ ```
404
+
405
+ Primitive polynomial:
406
+
407
+ ```text
408
+ x^8 + x^4 + x^3 + x^2 + 1
409
+ 0x11d
410
+ ```
411
+
412
+ One Reed-Solomon symbol is one byte.
413
+
414
+ Since one byte becomes four QuadQR data cells, ECC symbols stay naturally aligned with the RGBW representation.
415
+
416
+ ### Versions 2 through 40
417
+
418
+ | Profile | Parity bytes per body block | Correctable byte symbols per block |
419
+ |---|---:|---:|
420
+ | L | 12 | 6 |
421
+ | M | 24 | 12 |
422
+ | Q | 36 | 18 |
423
+ | H | 48 | 24 |
424
+
425
+ ### Version 1 compact profile
426
+
427
+ | Profile | Body parity bytes | Correctable body byte symbols |
428
+ |---|---:|---:|
429
+ | L | 4 | 2 |
430
+ | M | 8 | 4 |
431
+ | Q | 12 | 6 |
432
+ | H | 16 | 8 |
433
+
434
+ Version 1 also uses a compact 4-byte logical header protected by 4 Reed-Solomon parity bytes, correcting up to 2 damaged header byte symbols.
435
+
436
+ CRC-32 remains the final integrity check.
437
+
438
+ ---
439
+
440
+ ## Scanner pipeline
441
+
442
+ The image/camera scanner currently follows this general pipeline:
443
+
444
+ ```text
445
+ camera or image RGB frame
446
+
447
+ grayscale structural analysis
448
+
449
+ finder candidate detection
450
+
451
+ version hypothesis
452
+
453
+ primary alignment search
454
+
455
+ homography / perspective correction
456
+
457
+ distributed alignment-grid validation
458
+
459
+ module-grid reconstruction
460
+
461
+ fast observed-RGB decode attempt
462
+ (only if needed)
463
+ white balance + spatial normalization
464
+ (only if still needed)
465
+ Auto Tone / Auto Contrast / Auto Color-style recovery
466
+ (only if still needed)
467
+ sub-module geometry refinement
468
+
469
+ RGB + structural black/white calibration
470
+
471
+ nearest-color classification + confidence scoring
472
+
473
+ four-state unmasking
474
+
475
+ reverse spectral-spatial permutation
476
+
477
+ protected header Reed-Solomon hard decode
478
+
479
+ confidence-guided erasure retry when needed
480
+
481
+ body deinterleaving + error/erasure Reed-Solomon decode
482
+
483
+ CRC-32 verification
484
+
485
+ payload
486
+ ```
487
+
488
+ An axis-aligned fallback is also available for clean generated images and simple inputs.
489
+
490
+ ---
491
+
492
+ ## Rendering styles
493
+
494
+ QuadQR keeps visual styling separate from the wire format. The encoded matrix is unchanged, so applications can choose a renderer without creating a new barcode format.
495
+
496
+ The current render profiles are:
497
+
498
+ | Style | Behavior |
499
+ |---|---|
500
+ | `classic` | Original fully solid square modules. |
501
+ | `depth` | Deterministically mixes normal and lightly faded data tiles with subtle edge shading for a layered/3D appearance. |
502
+ | `soft` | Uses rounded data tiles for a softer visual treatment. |
503
+ | `inset` | Uses narrow recessed edge lighting while preserving the exact encoded center color. |
504
+
505
+ Finder patterns, timing structures, alignment markers, and calibration cells stay **square and fully solid** in styled output so the visual treatment does not weaken the scanner's geometric/color references.
506
+
507
+ The `inset` style preserves the exact encoded R/G/B/W color around the center of every data module. Its recessed shading is confined to a narrow outer band, and white data modules remain pure white. This matches the scanner's center-sampling strategy and avoids visual effects bleeding into neighboring cells.
508
+
509
+ ```js
510
+ renderToCanvas(code, canvas, {
511
+ moduleSize: 12,
512
+ quietZone: 4,
513
+ style: "inset" // classic | depth | soft | inset
514
+ });
515
+ ```
516
+
517
+ The styling is deterministic. Generating the same matrix with the same style produces the same visual tile treatment rather than changing randomly on every render.
518
+
519
+ ---
520
+
521
+ ## Demo
522
+
523
+ **Live demo:** https://akanshsirohi.github.io/QuadQR/demo/
524
+
525
+ The browser demo runs directly on GitHub Pages and is split into separate views so the interface does not become overloaded.
526
+
527
+ ### Generator & Image Scanner
528
+
529
+ Use this tab to:
530
+
531
+ - enter text/data;
532
+ - generate a QuadQR code;
533
+ - select ECC;
534
+ - optionally encrypt using a password or raw 256-bit key;
535
+ - inspect version and capacity;
536
+ - download the generated image;
537
+ - scan an uploaded image.
538
+
539
+ ### Camera Scanner
540
+
541
+ The live camera scanner has its own dedicated tab.
542
+
543
+ This keeps the generator interface lightweight and prevents an always-visible video element from making the main page bulky.
544
+
545
+ The camera stream automatically stops when you leave the camera tab.
546
+
547
+ ### Benchmark
548
+
549
+ The benchmark tab provides an easier visual view of:
550
+
551
+ - capacity by matrix size;
552
+ - comparison with standard QR;
553
+ - byte gain;
554
+ - capacity ratio;
555
+ - codec timing.
556
+
557
+ ---
558
+
559
+ ## Getting started
560
+
561
+ QuadQR can be used as an npm package, directly from a CDN, or from this repository.
562
+
563
+ ### Requirements
564
+
565
+ - **Node.js 20.19+** for Node.js and CommonJS usage
566
+ - npm or another Node package manager for installation
567
+ - A modern browser for the demo and documentation site
568
+ - HTTPS or localhost for browser camera access
569
+
570
+ ### Install from npm
571
+
572
+ ```bash
573
+ npm install quadqr-js
574
+ ```
575
+
576
+ Core usage:
577
+
578
+ ```js
579
+ import { encodeText, decodeMatrix } from "quadqr-js";
580
+
581
+ const code = encodeText("Hello from QuadQR", { ecc: "M" });
582
+ const result = decodeMatrix(code.matrix);
583
+ console.log(result.text);
584
+ ```
585
+
586
+ Node PNG usage:
587
+
588
+ ```js
589
+ import { encodeText } from "quadqr-js";
590
+ import { savePNG, scanFile } from "quadqr-js/node";
591
+
592
+ const code = encodeText("Generated on Node.js");
593
+ await savePNG(code, "quadqr.png", { moduleSize: 12, quietZone: 4 });
594
+
595
+ const result = await scanFile("quadqr.png");
596
+ console.log(result.text);
597
+ ```
598
+
599
+ ### CDN / script tag
600
+
601
+ The `quadqr-js` package can be loaded directly from npm-backed CDNs:
602
+
603
+ ```html
604
+ <script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.0.1/dist/quadqr.min.js"></script>
605
+ <script>
606
+ const code = QuadQR.encodeText("Hello from a script tag");
607
+ </script>
608
+ ```
609
+
610
+ The same file is available through unpkg. Pin an exact version in production.
611
+
612
+ ### CLI
613
+
614
+ Encode and decode directly from the terminal:
615
+
616
+ ```bash
617
+ npx quadqr-js encode "Hello QuadQR" -o hello.png
618
+ npx quadqr-js decode hello.png
619
+ ```
620
+
621
+ Password-protected payloads use the same commands:
622
+
623
+ ```bash
624
+ npx quadqr-js encode "Private data" --password "my-password" -o secure.png
625
+ npx quadqr-js decode secure.png --password "my-password"
626
+ ```
627
+
628
+ See [`docs/CLI.md`](docs/CLI.md) for all CLI options, including raw 256-bit key mode.
629
+
630
+ ### Run from source
631
+
632
+ ```bash
633
+ git clone https://github.com/akanshsirohi/QuadQR.git
634
+ cd QuadQR
635
+ npm install
636
+ ```
637
+
638
+ Run the complete test suite:
639
+
640
+ ```bash
641
+ npm test
642
+ ```
643
+
644
+ Build the distributable browser, Node.js, CDN, and WASM files:
645
+
646
+ ```bash
647
+ npm run build
648
+ ```
649
+
650
+ Start the interactive demo:
651
+
652
+ ```bash
653
+ npm start
654
+ ```
655
+
656
+ Start the documentation site:
657
+
658
+ ```bash
659
+ npm run docs
660
+ ```
661
+
662
+ Run benchmarks:
663
+
664
+ ```bash
665
+ npm run benchmark
666
+ ```
667
+
668
+ ### Optional WASM acceleration
669
+
670
+ The package ships a prebuilt WASM helper but never requires it.
671
+
672
+ ```js
673
+ import { initWasm } from "quadqr-js";
674
+
675
+ await initWasm();
676
+ ```
677
+
678
+ If WASM cannot load, the normal JavaScript codec remains available.
679
+
680
+ ---
681
+
682
+ ## Package entry points
683
+
684
+ | Import | Purpose |
685
+ |---|---|
686
+ | `quadqr-js` | Runtime-neutral core, secure payloads, rendering, scanning, optional WASM |
687
+ | `quadqr-js/browser` | Browser ESM entry |
688
+ | `quadqr-js/node` | Node core plus PNG/file/buffer helpers |
689
+ | `quadqr-js/benchmark` | Capacity and codec benchmark helpers |
690
+ | `quadqr-js/quadqr.min.js` | Classic browser global/CDN bundle |
691
+
692
+ The Node PNG path is dependency-free. For JPEG, WebP, or AVIF input, the Node adapter can use `sharp` when the consuming application already has it installed.
693
+
694
+ ---
695
+
696
+ ## Repository layout
697
+
698
+ ```text
699
+ library/
700
+ quadqr.js Core encoder, decoder, renderer, scanner, and public API
701
+ security.js Secure Payload v1 encryption and key handling
702
+ reed-solomon.js GF(256) Reed-Solomon implementation
703
+ geometry.js Version, size, and alignment geometry
704
+ vision.js Finder detection, perspective correction, and color sampling
705
+ node.js Node PNG/file/buffer adapters
706
+ wasm.js Optional prebuilt WASM loader
707
+ benchmark.js Reusable benchmark utilities
708
+
709
+ wasm-src/
710
+ quadqr_core.c Small portable WASM accelerator source
711
+
712
+ wasm/
713
+ quadqr-core.wasm Source-tree WASM build output
714
+
715
+ dist/
716
+ index.js ESM package entry
717
+ index.cjs Modern CommonJS wrapper
718
+ browser.js Browser ESM entry
719
+ node.js Node entry
720
+ quadqr.js Classic global browser bundle
721
+ quadqr.min.js Compact CDN browser bundle
722
+ wasm/ Prebuilt WASM package asset
723
+
724
+ demo/
725
+ index.html Interactive generator, image scanner, camera scanner, benchmark
726
+ app.js
727
+ styles.css
728
+
729
+ docs-site/
730
+ index.html Standalone documentation website
731
+ app.js
732
+ styles.css
733
+
734
+ docs/
735
+ README.md Markdown documentation index
736
+ GETTING_STARTED.md
737
+ API.md
738
+ BROWSER_CDN.md
739
+ NODE.md
740
+ SECURITY.md
741
+ CLI.md
742
+ WASM.md
743
+
744
+ types/ TypeScript declarations for JavaScript consumers
745
+ bin/ `quadqr` CLI (`npx quadqr-js`)
746
+ scripts/ Build, benchmark, and local server scripts
747
+ tests/ Codec and package distribution tests
748
+ FORMAT.md Wire-format specification
749
+ AGENT.md Development guidance
750
+ README.md Project overview
751
+ ```
752
+
753
+ ---
754
+
755
+ ## Main API
756
+
757
+ ### `encodeText(text, options?)`
758
+
759
+ ```js
760
+ const code = encodeText("Hello from QuadQR", {
761
+ version: "auto",
762
+ minVersion: 1,
763
+ maxVersion: 40,
764
+ ecc: "M"
765
+ });
766
+ ```
767
+
768
+ ### `encodeBytes(bytes, options?)`
769
+
770
+ Encodes arbitrary binary data from a `Uint8Array`.
771
+
772
+ ```js
773
+ const code = encodeBytes(myBytes, {
774
+ version: "auto",
775
+ ecc: "M"
776
+ });
777
+ ```
778
+
779
+ ### `encodeSecureText(text, options?)`
780
+
781
+ Secure encoding is asynchronous because it uses Web Crypto.
782
+
783
+ Password mode:
784
+
785
+ ```js
786
+ const code = await encodeSecureText("Private message", {
787
+ ecc: "M",
788
+ security: {
789
+ mode: "password",
790
+ password: "correct horse battery staple"
791
+ }
792
+ });
793
+ ```
794
+
795
+ Raw 256-bit key mode:
796
+
797
+ ```js
798
+ const key = generateRaw256Key();
799
+
800
+ const code = await encodeSecureText("Device configuration", {
801
+ ecc: "M",
802
+ security: {
803
+ mode: "raw-key",
804
+ key
805
+ }
806
+ });
807
+ ```
808
+
809
+ Raw keys may be supplied as an exact 32-byte `Uint8Array` or as a 64-character hexadecimal string. By default, raw-key envelopes include a short SHA-256 key fingerprint (`keyIdHex`) that helps an application select the correct secret key without storing the key inside the QuadQR.
810
+
811
+ ### `encodeSecureBytes(bytes, options?)`
812
+
813
+ Binary equivalent of `encodeSecureText()`.
814
+
815
+ ### `decryptDecoded(result, credentials)`
816
+
817
+ A secure matrix/image scan first returns encrypted metadata without exposing plaintext:
818
+
819
+ ```js
820
+ const locked = decodeMatrix(matrix);
821
+
822
+ console.log(locked.secure); // true
823
+ console.log(locked.requiresDecryption); // true
824
+ console.log(locked.security.mode); // password | raw-key
825
+ ```
826
+
827
+ Then decrypt it:
828
+
829
+ ```js
830
+ const result = await decryptDecoded(locked, {
831
+ password: "correct horse battery staple"
832
+ });
833
+
834
+ console.log(result.text);
835
+ ```
836
+
837
+ For raw-key mode:
838
+
839
+ ```js
840
+ const result = await decryptDecoded(locked, { key });
841
+ ```
842
+
843
+ The decrypted result preserves the encrypted envelope as `encryptedPayload` for applications that need both forms.
844
+
845
+ ### `decodeMatrix(matrix, options?)`
846
+
847
+ Decodes an already reconstructed QuadQR matrix.
848
+
849
+ ```js
850
+ const result = decodeMatrix(matrix);
851
+ ```
852
+
853
+ When a scanner has per-cell confidence values, they can also be supplied directly:
854
+
855
+ ```js
856
+ const result = decodeMatrix(matrix, {
857
+ cellConfidence: confidenceMatrix
858
+ });
859
+ ```
860
+
861
+ The result reports fields such as `spectralInterleaving`, `confidenceAssisted`, `erasureSymbols`, and `correctedSymbols`. Secure symbols additionally report `secure`, `requiresDecryption`, and parsed `security` metadata while keeping `text` unset until successful decryption.
862
+
863
+ ### `renderToCanvas(codeOrMatrix, canvas, options?)`
864
+
865
+ Renders a QuadQR symbol into a browser canvas. `options.style` supports `classic`, `depth`, `soft`, and `inset`.
866
+
867
+ ### `renderToImageData(codeOrMatrix, options?)`
868
+
869
+ Returns an ImageData-like object and supports the same rendering styles as `renderToCanvas()`:
870
+
871
+ ```js
872
+ {
873
+ width,
874
+ height,
875
+ data
876
+ }
877
+ ```
878
+
879
+ This is also useful for tests and non-DOM workflows.
880
+
881
+ ### `scanImageData(imageData, options?)`
882
+
883
+ 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.
884
+
885
+ ### `scanFile(file, options?)`
886
+
887
+ Scans an uploaded browser image file.
888
+
889
+ ### `scanVideoFrame(video, options?)`
890
+
891
+ Scans one frame from an HTML video element. By default, if the video is displayed with `object-fit: cover`, QuadQR scans the source crop that is actually visible in the element rather than hidden sensor pixels outside the preview. Set `videoCropMode: "full"` to opt out.
892
+
893
+ ### `startCameraScanner(video, options?)`
894
+
895
+ 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.
896
+
897
+ ### `getVersionInfo(version, options?)`
898
+
899
+ Returns information such as:
900
+
901
+ - matrix size;
902
+ - number of data cells;
903
+ - theoretical raw bits;
904
+ - usable payload capacity;
905
+ - structural metadata.
906
+
907
+ ---
908
+
909
+ ## Tests
910
+
911
+ Run:
912
+
913
+ ```bash
914
+ npm test
915
+ ```
916
+
917
+ The current test suite covers areas including:
918
+
919
+ - RGBW 2-bit mapping;
920
+ - binary round trips;
921
+ - Unicode text round trips;
922
+ - automatic version selection;
923
+ - all ECC profiles;
924
+ - version 1 compact framing;
925
+ - capacity boundaries;
926
+ - deliberate Reed-Solomon corruption recovery;
927
+ - Reed-Solomon error + erasure recovery;
928
+ - zero-overhead spectral-spatial permutation validation;
929
+ - confidence-assisted recovery beyond the ordinary hard-error limit;
930
+ - rotation handling;
931
+ - generated-image scanning;
932
+ - perspective distortion;
933
+ - color-cast scanning;
934
+ - dirty-camera stress scanning with strong yellow cast, haze, blue-channel suppression, and blur;
935
+ - low-contrast warm-camera regression where normal scanning fails but progressive Auto Tone / Contrast / Color recovery succeeds;
936
+ - multi-frame classification voting;
937
+ - benchmark reference data;
938
+ - timed codec round trips;
939
+ - password-mode secure round trips and wrong-password rejection;
940
+ - raw 256-bit key round trips, key fingerprinting, and wrong-key rejection;
941
+ - secure rendered-image scan and decryption.
942
+
943
+ ---
944
+
945
+ ## Current limitations
946
+
947
+ QuadQR is still experimental.
948
+
949
+ Important areas that need more research and real-device testing include:
950
+
951
+ - printed codes across different printers and inks;
952
+ - paper color and reflectivity;
953
+ - display brightness and color profiles;
954
+ - screen glare;
955
+ - moiré patterns;
956
+ - motion blur;
957
+ - very small modules;
958
+ - extreme camera angles;
959
+ - low-light scanning;
960
+ - RGBW confusion under difficult illumination;
961
+ - damaged or partially hidden symbols;
962
+ - standardized recovery percentages;
963
+ - equal-reliability comparison with ISO QR Code;
964
+ - local/non-projective distortion correction using the distributed alignment grid;
965
+ - formal print-quality grading;
966
+ - performance across different phones and camera systems;
967
+ - memory-hard password KDF option such as Argon2id for environments where a small WASM/runtime dependency is acceptable;
968
+ - public/private-key secure payload mode.
969
+
970
+ The project should currently be treated as a research and experimental implementation rather than a replacement for standardized QR Code in production-critical environments.
971
+
972
+ ---
973
+
974
+ ## Roadmap
975
+
976
+ - [ ] Equal-reliability benchmark against standard QR
977
+ - [ ] Automated camera torture-test suite
978
+ - [ ] Blur, JPEG, noise, perspective, and lighting benchmarks
979
+ - [ ] Print-and-rescan dataset
980
+ - [x] Confidence-based RGBW classification
981
+ - [x] Improved adaptive color calibration
982
+ - [x] Distributed alignment patterns for large versions
983
+ - [ ] Interleaving tuned for localized physical damage
984
+ - [ ] Real-device benchmark dataset
985
+ - [ ] Formal versioned QuadQR specification
986
+ - [ ] Implementations in additional languages
987
+ - [ ] Independent decoder implementation
988
+
989
+ ---
990
+
991
+ ## Why keep the square design?
992
+
993
+ QuadQR intentionally keeps square modules and a square overall matrix.
994
+
995
+ Square cells:
996
+
997
+ - tessellate without gaps;
998
+ - provide predictable row/column addressing;
999
+ - maximize colored area inside each module;
1000
+ - are easy to sample at their center;
1001
+ - work naturally with perspective correction;
1002
+ - keep the geometry relatively simple for camera scanning.
1003
+
1004
+ Alternative module shapes may be interesting visually, but the current focus is data density, reliability, and scan robustness.
1005
+
1006
+ ---
1007
+
1008
+ ## Is QuadQR a QR Code replacement?
1009
+
1010
+ Not currently.
1011
+
1012
+ Standard QR Code has enormous advantages:
1013
+
1014
+ - decades of deployment;
1015
+ - international standardization;
1016
+ - extremely mature decoders;
1017
+ - broad device support;
1018
+ - extensive real-world testing;
1019
+ - proven print reliability.
1020
+
1021
+ QuadQR is exploring a different question:
1022
+
1023
+ > **What can a QR-inspired matrix code look like if we design its data layer around modern color-capable cameras and displays?**
1024
+
1025
+ The goal is experimentation, measurement, and learning.
1026
+
1027
+ ---
1028
+
1029
+ ## Contributing
1030
+
1031
+ Contributions, experiments, test images, scanner improvements, benchmarking ideas, and independent implementations are welcome.
1032
+
1033
+ If you are contributing changes to the wire format, please also update:
1034
+
1035
+ ```text
1036
+ FORMAT.md
1037
+ ```
1038
+
1039
+ Changes that affect capacity, ECC, scanning behavior, or version selection should include tests where practical.
1040
+
1041
+ ---
1042
+
1043
+ ## Security
1044
+
1045
+ Decoded payloads are untrusted input.
1046
+
1047
+ Do not automatically execute decoded:
1048
+
1049
+ - HTML;
1050
+ - JavaScript;
1051
+ - shell commands;
1052
+ - application commands;
1053
+ - URLs.
1054
+
1055
+ Applications using QuadQR should validate and safely handle decoded content just as they would any other external input.
1056
+
1057
+ ---
1058
+
1059
+ ## License
1060
+
1061
+ AGPL v3.0. See `LICENSE`.
1062
+
1063
+ ---
1064
+
1065
+ ## Project status
1066
+
1067
+ **Experimental / research project**
1068
+
1058
1069
  QuadQR is actively evolving. Format details may change between versions until the wire format is considered stable.