quadqr-js 1.0.2 → 1.3.0

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:
@@ -388,3 +414,14 @@ Format v5 is intentionally incompatible with standard QR scanners and with the p
388
414
  ## Rendering profiles are not part of the wire format
389
415
 
390
416
  The canonical QuadQR matrix is independent of presentation style. Renderers may offer styles such as `classic`, `depth`, `soft`, or `inset`, provided structural finder/timing/alignment/calibration references remain sufficiently faithful for decoding. Style selection is not encoded in the payload/header and does not change matrix cell values.
417
+
418
+ ## Compression and signature flags
419
+
420
+ Format v5 keeps the physical matrix and ECC framing unchanged while reserving two protected-header flags for optional internal payload metadata:
421
+
422
+ ```text
423
+ bit 4 = internal payload-extension metadata present
424
+ bit 5 = signed-payload hint
425
+ ```
426
+
427
+ 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.
package/README.md CHANGED
@@ -5,7 +5,7 @@
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">
@@ -21,12 +21,14 @@
21
21
  ·
22
22
  <a href="docs/README.md">Markdown Docs</a>
23
23
  ·
24
- <a href="FORMAT.md">Format Specification</a>
24
+ <a href="FORMAT.md">Matrix Format</a>
25
+ ·
26
+ <a href="SPECIFICATION.md">Technical Specification</a>
25
27
  </p>
26
28
 
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.
29
+ **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.
28
30
 
29
- Each QuadQR data cell represents exactly **2 bits**:
31
+ Default RGBW mapping:
30
32
 
31
33
  | Color | Bits |
32
34
  |---|---|
@@ -37,6 +39,28 @@ Each QuadQR data cell represents exactly **2 bits**:
37
39
 
38
40
  That gives QuadQR a four-symbol alphabet and a raw density of **2 bits per data cell**.
39
41
 
42
+ ### Experimental High Density Mode
43
+
44
+ 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:
45
+
46
+ ```text
47
+ 4 colors × 4 colors = 16 states
48
+ log2(16) = 4 bits per data cell
49
+ ```
50
+
51
+ 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.
52
+
53
+ ```js
54
+ const code = encodeText("High-density QuadQR", {
55
+ ecc: "M",
56
+ highDensity: true
57
+ });
58
+ ```
59
+
60
+ 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.
61
+
62
+ See [`docs/HIGH_DENSITY_MODE.md`](docs/HIGH_DENSITY_MODE.md) for the physical cell mapping, protected-header strategy, scanner sampling rules, and reliability caveats.
63
+
40
64
  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
65
 
42
66
  > **Important:** QuadQR is an experimental custom format. It is **not ISO QR Code**, and normal QR scanner apps cannot decode it.
@@ -129,7 +153,8 @@ So at the raw data-cell level:
129
153
  | Format | States per data cell | Raw information |
130
154
  |---|---:|---:|
131
155
  | Binary QR | 2 | 1 bit |
132
- | QuadQR | 4 | 2 bits |
156
+ | QuadQR RGBW | 4 | 2 bits |
157
+ | QuadQR High Density Mode (Triangle16) | 16 | 4 bits |
133
158
 
134
159
  This is a **2× raw symbol-density advantage**.
135
160
 
@@ -179,6 +204,56 @@ The scanner never expects the secret to be embedded in the QuadQR itself. Raw-ke
179
204
 
180
205
  ---
181
206
 
207
+ ## Compression, signatures, and robustness tooling
208
+
209
+ QuadQR keeps the application payload simple: **text stays text and bytes stay bytes**. There is no public payload-type registry to maintain. Compression and signing metadata are added only as internal implementation details when those features are enabled.
210
+
211
+ ```js
212
+ import {
213
+ encodeText,
214
+ encodeSignedText,
215
+ generateSigningKeyPair,
216
+ decodeMatrix,
217
+ verifyDecodedSignature
218
+ } from "quadqr-js";
219
+
220
+ // Auto compression is zero-overhead when it does not help.
221
+ const compressed = encodeText("repeated repeated repeated", {
222
+ compression: "auto",
223
+ ecc: "M"
224
+ });
225
+
226
+ const keys = await generateSigningKeyPair();
227
+ const signed = await encodeSignedText("verified offline", {
228
+ compression: "auto",
229
+ privateKey: keys.privateKey,
230
+ keyId: keys.keyId
231
+ });
232
+
233
+ const decoded = decodeMatrix(signed.matrix);
234
+ const verified = await verifyDecodedSignature(decoded, {
235
+ publicKey: keys.publicKey
236
+ });
237
+ console.log(verified.signatureVerified); // true
238
+ console.log(verified.signatureTrusted); // true
239
+ ```
240
+
241
+ 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.
242
+
243
+ 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`.
244
+
245
+ The renderer supports an explicit `mode: "print"`. Print mode enforces a minimum 4-module quiet zone, uses darker print-safe RGB defaults, and prefers Classic solid modules. `getPrintGuidance()` converts a chosen physical size into module millimeters/pixels so print layouts can be checked before production testing.
246
+
247
+ Centered logos support `size: "auto"`, which estimates a conservative ECC-aware ratio from code utilization and rendering choices. `findMaxSafeLogoSize()` can additionally probe ImageData output and empirically search for the largest size that still decodes.
248
+
249
+ Scanner results include normalized diagnostics such as `confidence`, `geometryConfidence`, `calibrationConfidence`, `structureConfidence`, `eccUtilization`, and `correctedErrors`. Set `debug: true` or call `debugScanImageData()` to inspect finder/geometry candidates, the sampled matrix, color-confidence data, ECC stages, and the stage that failed.
250
+
251
+ 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.
252
+
253
+ The interoperability details are documented in [`SPECIFICATION.md`](./SPECIFICATION.md).
254
+
255
+ ---
256
+
182
257
  ## Current benchmark
183
258
 
184
259
  Run:
@@ -228,7 +303,7 @@ Therefore:
228
303
 
229
304
  > The capacity benchmark is a same-dimension and same-label comparison, not yet an equal-damage-tolerance comparison.
230
305
 
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.
306
+ 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.
232
307
 
233
308
  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
309
 
@@ -390,7 +465,7 @@ White → (255, 255, 255)
390
465
 
391
466
  Real camera input is not expected to match those exact values.
392
467
 
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.
468
+ 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, 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. 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.
394
469
 
395
470
  ---
396
471
 
@@ -452,9 +527,11 @@ version hypothesis
452
527
 
453
528
  primary alignment search
454
529
 
455
- homography / perspective correction
530
+ initial homography / perspective correction
456
531
 
457
532
  distributed alignment-grid validation
533
+ ↓ (when geometry is plausible but imperfect)
534
+ secondary alignment multi-point homography refinement
458
535
 
459
536
  module-grid reconstruction
460
537
 
@@ -508,7 +585,7 @@ The `inset` style preserves the exact encoded R/G/B/W color around the center of
508
585
 
509
586
  ```js
510
587
  renderToCanvas(code, canvas, {
511
- moduleSize: 12,
588
+ imageSize: 720,
512
589
  quietZone: 4,
513
590
  style: "inset" // classic | depth | soft | inset
514
591
  });
@@ -516,13 +593,58 @@ renderToCanvas(code, canvas, {
516
593
 
517
594
  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
595
 
596
+ `imageSize` sets the exact square output size in pixels. When neither `imageSize` nor `moduleSize` is supplied, QuadQR renders at **720 × 720 px** by default. `moduleSize` remains available as the lower-level legacy sizing control; when `imageSize` is supplied, the exact image size takes precedence.
597
+
598
+ ### Logo overlays, quiet zones, and SVG export
599
+
600
+ The renderer can place a centered logo over the symbol. Transparent pixels in the logo stay transparent, so the QuadQR modules remain visible through those areas. Enable `clearBackground` when you want a clean padded white area behind the logo instead.
601
+
602
+ ```js
603
+ const logoImage = new Image();
604
+ logoImage.src = "/brand-mark.png";
605
+ await logoImage.decode();
606
+
607
+ renderToCanvas(code, canvas, {
608
+ imageSize: 720,
609
+ quietZone: 6,
610
+ style: "classic",
611
+ logo: {
612
+ source: logoImage,
613
+ size: 0.12,
614
+ clearBackground: true,
615
+ padding: 0.65,
616
+ radius: 0.8
617
+ }
618
+ });
619
+ ```
620
+
621
+ `quietZone` is measured in modules and can be set to `0` or increased for print/camera use. Four modules remains the recommended default.
622
+
623
+ SVG uses the same matrix, palette, styles, quiet-zone size, and logo geometry:
624
+
625
+ ```js
626
+ import { renderToSVG } from "quadqr-js";
627
+
628
+ const svg = renderToSVG(code, {
629
+ imageSize: 720,
630
+ quietZone: 4,
631
+ logo: {
632
+ source: "data:image/png;base64,...",
633
+ size: 0.12,
634
+ clearBackground: true
635
+ }
636
+ });
637
+ ```
638
+
639
+ Logo overlays intentionally consume some ECC margin because they cover encoded cells. Keep logos conservative, especially with `L`/`M` ECC. The browser demo verifies the final rendered image before enabling downloads.
640
+
519
641
  ---
520
642
 
521
643
  ## Demo
522
644
 
523
645
  **Live demo:** https://akanshsirohi.github.io/QuadQR/demo/
524
646
 
525
- The browser demo runs directly on GitHub Pages and is split into separate views so the interface does not become overloaded.
647
+ 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.
526
648
 
527
649
  ### Generator & Image Scanner
528
650
 
@@ -531,9 +653,13 @@ Use this tab to:
531
653
  - enter text/data;
532
654
  - generate a QuadQR code;
533
655
  - select ECC;
656
+ - open Output & Rendering controls for image size, quiet zone, style, and print mode;
657
+ - open the Center Logo accordion for branding;
658
+ - enable internal automatic compression without choosing a payload type;
659
+ - optionally add an Ed25519 signature;
534
660
  - optionally encrypt using a password or raw 256-bit key;
535
661
  - inspect version and capacity;
536
- - download the generated image;
662
+ - download PNG or SVG output;
537
663
  - scan an uploaded image.
538
664
 
539
665
  ### Camera Scanner
@@ -552,7 +678,43 @@ The benchmark tab provides an easier visual view of:
552
678
  - comparison with standard QR;
553
679
  - byte gain;
554
680
  - capacity ratio;
555
- - codec timing.
681
+ - codec timing;
682
+ - capacity planning;
683
+ - interactive stress testing.
684
+
685
+ Benchmark tools are also separated into accordions so the page stays compact until a tool is needed.
686
+
687
+ ---
688
+
689
+ ## New API highlights
690
+
691
+ ```js
692
+ import {
693
+ encodeText,
694
+ encodeUint8Array,
695
+ decodeUint8Array,
696
+ renderToImageData,
697
+ assessScanability,
698
+ getPrintGuidance
699
+ } from "quadqr-js";
700
+
701
+ const binary = encodeUint8Array(new Uint8Array([1, 2, 3, 4]), { ecc: "M" });
702
+ const bytes = decodeUint8Array(binary.matrix);
703
+
704
+ const code = encodeText("Hello hello hello hello", {
705
+ compression: "auto",
706
+ ecc: "Q"
707
+ });
708
+
709
+ const printImage = renderToImageData(code, {
710
+ imageSize: 1200,
711
+ mode: "print",
712
+ logo: { source: logoImageData, size: "auto", clearBackground: true }
713
+ });
714
+
715
+ console.log(getPrintGuidance(code, { physicalSizeMm: 45, dpi: 300 }));
716
+ console.log(assessScanability(code, { imageSize: 480 }));
717
+ ```
556
718
 
557
719
  ---
558
720
 
@@ -583,14 +745,15 @@ const result = decodeMatrix(code.matrix);
583
745
  console.log(result.text);
584
746
  ```
585
747
 
586
- Node PNG usage:
748
+ Node PNG/SVG usage:
587
749
 
588
750
  ```js
589
751
  import { encodeText } from "quadqr-js";
590
- import { savePNG, scanFile } from "quadqr-js/node";
752
+ import { savePNG, saveSVG, scanFile } from "quadqr-js/node";
591
753
 
592
754
  const code = encodeText("Generated on Node.js");
593
- await savePNG(code, "quadqr.png", { moduleSize: 12, quietZone: 4 });
755
+ await savePNG(code, "quadqr.png", { imageSize: 720, quietZone: 4 });
756
+ await saveSVG(code, "quadqr.svg", { imageSize: 720, quietZone: 4 });
594
757
 
595
758
  const result = await scanFile("quadqr.png");
596
759
  console.log(result.text);
@@ -601,7 +764,7 @@ console.log(result.text);
601
764
  The `quadqr-js` package can be loaded directly from npm-backed CDNs:
602
765
 
603
766
  ```html
604
- <script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.0.1/dist/quadqr.min.js"></script>
767
+ <script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.1.0/dist/quadqr.min.js"></script>
605
768
  <script>
606
769
  const code = QuadQR.encodeText("Hello from a script tag");
607
770
  </script>
@@ -618,6 +781,16 @@ npx quadqr-js encode "Hello QuadQR" -o hello.png
618
781
  npx quadqr-js decode hello.png
619
782
  ```
620
783
 
784
+ Compressed, print-safe, and signed output are also available:
785
+
786
+ ```bash
787
+ npx quadqr-js encode "repeat repeat repeat" --compression auto -o compressed.svg
788
+ npx quadqr-js encode "Print me" --print -o print.svg
789
+ npx quadqr-js signkeygen -o signing-key.json
790
+ npx quadqr-js encode "Verified ticket" --sign-key signing-key.json -o signed.png
791
+ npx quadqr-js decode signed.png --verify-key signing-key.json
792
+ ```
793
+
621
794
  Password-protected payloads use the same commands:
622
795
 
623
796
  ```bash
@@ -625,7 +798,7 @@ npx quadqr-js encode "Private data" --password "my-password" -o secure.png
625
798
  npx quadqr-js decode secure.png --password "my-password"
626
799
  ```
627
800
 
628
- See [`docs/CLI.md`](docs/CLI.md) for all CLI options, including raw 256-bit key mode.
801
+ See [`docs/CLI.md`](docs/CLI.md) for all CLI options, including compression, raw 256-bit key mode, signing, print mode, and scanner diagnostics.
629
802
 
630
803
  ### Run from source
631
804
 
@@ -862,7 +1035,7 @@ The result reports fields such as `spectralInterleaving`, `confidenceAssisted`,
862
1035
 
863
1036
  ### `renderToCanvas(codeOrMatrix, canvas, options?)`
864
1037
 
865
- Renders a QuadQR symbol into a browser canvas. `options.style` supports `classic`, `depth`, `soft`, and `inset`.
1038
+ Renders a QuadQR symbol into a browser canvas. `imageSize` sets the exact square pixel output and defaults to 720 when neither sizing option is supplied. `moduleSize` remains available for legacy pixels-per-module sizing. `options.style` supports `classic`, `depth`, `soft`, and `inset`. `quietZone` controls the border in modules. `logo` accepts a loaded image/canvas source or `{ source, size, clearBackground, padding, radius, backgroundColor }`.
866
1039
 
867
1040
  ### `renderToImageData(codeOrMatrix, options?)`
868
1041
 
@@ -878,9 +1051,15 @@ Returns an ImageData-like object and supports the same rendering styles as `rend
878
1051
 
879
1052
  This is also useful for tests and non-DOM workflows.
880
1053
 
1054
+ When a logo is used with `renderToImageData()`, its source must be an ImageData-like `{ width, height, data }` object so the renderer can composite it without DOM APIs.
1055
+
1056
+ ### `renderToSVG(codeOrMatrix, options?)`
1057
+
1058
+ Returns a standalone SVG string using the same exact `imageSize`, render styles, and quiet-zone controls. SVG logo sources can be a URL/data URL string or an object with a `src` string. The SVG remains vector-sharp regardless of how large the preview is displayed.
1059
+
881
1060
  ### `scanImageData(imageData, options?)`
882
1061
 
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.
1062
+ 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 / 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
1063
 
885
1064
  ### `scanFile(file, options?)`
886
1065
 
@@ -892,7 +1071,7 @@ Scans one frame from an HTML video element. By default, if the video is displaye
892
1071
 
893
1072
  ### `startCameraScanner(video, options?)`
894
1073
 
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.
1074
+ 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 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 paths run only after a miss. Finder-only recovery also tries multiple center-weighted Auto Color histograms before threshold bracketing. `cameraHighResolutionMaxDimension` defaults to 1600, `cameraHighResolutionEvery` defaults to 2, and `cameraAutoColorEvery` defaults to 1. 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
1075
 
897
1076
  ### `getVersionInfo(version, options?)`
898
1077