quadqr-js 1.3.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/API.md CHANGED
@@ -179,7 +179,7 @@ const svg = renderToSVG(code, {
179
179
 
180
180
  ### `scanImageData(imageData, options?)`
181
181
 
182
- Scans an ImageData-like RGBA object. The scanner automatically attempts both normal RGBW center sampling and Triangle16 dual-region sampling when geometry is found. When normal projective geometry is locatable but too coarse for half-cell Triangle16 regions, one bounded precise-alignment recovery pass refines the alignment center at sub-module resolution. Clean frames use the normal observed-RGB path first. Difficult frames then get bounded fallback attempts using per-channel white balancing, spatial black/white normalization, tighter centre sampling, module-grid Auto Tone / Auto Contrast / Auto Color recovery, a rectified QR-region pixel enhancement pass, and sub-module geometry micro-refinement before the scan is rejected. Dense versions also use their distributed alignment markers to refine a noisy four-point projective solution when the initial alignment-grid score is plausible but imperfect. If exactly two strong finder patterns survive a steep angle, a bounded perspective-tolerant third-finder recovery pass is attempted before heavier color recovery.
182
+ Scans an ImageData-like RGBA object. The scanner automatically attempts both normal RGBW center sampling and Triangle16 dual-region sampling when geometry is found. When normal projective geometry is locatable but too coarse for half-cell Triangle16 regions, one bounded precise-alignment recovery pass refines the alignment center at sub-module resolution. Clean frames use the normal observed-RGB path first. Difficult frames then get bounded fallback attempts using per-channel white balancing, spatial black/white normalization, tighter centre sampling, QuadQR module-grid Auto Tone / Auto Contrast / Auto Color recovery, a rectified QR-region pixel enhancement pass, and sub-module geometry micro-refinement before the scan is rejected. Dense versions also use their distributed alignment markers to refine a noisy four-point projective solution when the initial alignment-grid score is plausible but imperfect. If exactly two strong finder patterns survive a steep angle, a bounded perspective-tolerant third-finder recovery pass is attempted before heavier color recovery.
183
183
 
184
184
  ```js
185
185
  const result = scanImageData({
@@ -189,7 +189,7 @@ const result = scanImageData({
189
189
  });
190
190
  ```
191
191
 
192
- Useful scanner options include `sampleRadius`, `robustSampleRadius`, `adaptiveSampling`, `spatialColorNormalization`, `autoEnhanceRecovery`, `rectifiedAutoEnhanceRecovery`, `rectifiedRecoveryModuleSize`, `autoEnhanceBlackClip`, `autoEnhanceWhiteClip`, `autoEnhanceSaturation`, `geometryRefinement`, `alignmentRefinement`, `alignmentRefinePatternThreshold`, `refinementOffset`, `structureTolerance`, and `maxErasureConfidence`. Auto enhancement and geometry refinement are enabled by default, but bounded recovery work only runs when the normal scan path or initial geometry needs it.
192
+ Useful scanner options include `sampleRadius`, `robustSampleRadius`, `adaptiveSampling`, `spatialColorNormalization`, `autoEnhanceRecovery`, `rectifiedAutoEnhanceRecovery`, `rectifiedRecoveryModuleSize`, `geometryRefinement`, `alignmentRefinement`, `structureTolerance`, `maxErasureConfidence`, `softDecoding`, `softDecodeConfidence`, `softDecodeMaxCells`, and `softDecodePairCells`. Spectrum ECC 2.0 soft decoding is bounded and only runs after ordinary hard/error-erasure decoding fails.
193
193
 
194
194
  ### Browser `scanFile(file, options?)`
195
195
 
@@ -209,13 +209,15 @@ const result = scanVideoFrame(video);
209
209
 
210
210
  ### `startCameraScanner(video, options?)`
211
211
 
212
- Starts live camera scanning. On browsers that expose camera controls, QuadQR requests continuous autofocus, exposure, and white balance. It scans the CSS-visible preview region by default. Finder detection uses a QuadQR-specific RGB value channel (`max(R,G,B)`) on the fast pass so saturated blue/red/green data cells are not mistaken for structural black. If a miss still contains at least two strong finder patterns, the scanner can retry the visible ROI at up to 1600 px before heavier recovery, which preserves more pixels per module for dense versions. If that does not decode, the **same captured frame** is retried through code-centric Auto Color recovery. The default recovery sequence crops 8%, 16%, and 22% from the camera-frame edges, then falls back to the full frame. This prevents dark room pixels, browser UI, or monitor bezels outside the guide from controlling Auto Color and Otsu thresholds. Each crop uses the Photoshop-style per-channel shadow/highlight correction with a neutral mid-high highlight target (190 by default). Finder-only recovery also tries multiple center-weighted Auto Color histogram windows before raw threshold bracketing. The normal fast path is untouched; these extra passes run only after a miss. If geometry is found but color decoding fails, the captured ROI is retried with the stronger color/geometry recovery, and consecutive failed frames can still be combined with confidence-weighted module voting.
212
+ Starts live camera scanning. On browsers that expose camera controls, QuadQR requests continuous autofocus, exposure, and white balance. It scans the CSS-visible preview region by default. Finder detection uses a QuadQR-specific RGB value channel (`max(R,G,B)`) on the fast pass so saturated blue/red/green data cells are not mistaken for structural black. If a miss still contains at least two strong finder patterns, the scanner can retry the visible ROI at up to 1600 px before heavier recovery, which preserves more pixels per module for dense versions. If that does not decode, the **same captured frame** is retried through QuadQR code-centric Auto Color recovery. The default recovery sequence crops 8%, 16%, and 22% from the camera-frame edges, then falls back to the full frame. This prevents dark room pixels, browser UI, or monitor bezels outside the guide from controlling QuadQR Auto Color and Otsu thresholds. Each crop uses the QuadQR-specific per-channel shadow/highlight correction with a neutral mid-high highlight target (190 by default). Finder-only recovery also tries multiple center-weighted QuadQR Auto Color histogram windows before raw threshold bracketing. The normal fast path is untouched; these extra passes run only after a miss. If geometry is found but color decoding fails, the captured ROI is retried with stronger color/geometry recovery, including affine cross-channel calibration. Consecutive failed frames can then be combined with tracked-symbol **confidence fusion**: cell evidence is weighted by source confidence, frame quality, and recency, and second hypotheses are carried forward into Spectrum ECC 2.0.
213
213
 
214
214
  ```js
215
215
  const scanner = await startCameraScanner(video, {
216
216
  scanInterval: 120,
217
217
  multiFrame: true,
218
218
  multiFrameWindow: 4,
219
+ multiFrameMinFrames: 2,
220
+ softDecoding: true,
219
221
  cameraAutoColorEvery: 1,
220
222
  cameraAutoColorCropInsets: [0.08, 0.16, 0.22, 0],
221
223
  cameraAutoColorHighlightPercentile: 0.95,
@@ -410,17 +412,38 @@ const code = QuadQR.encodeText("hello hello hello", {
410
412
  });
411
413
  ```
412
414
 
413
- `compression` may be `none`, `auto`, or `lz`.
415
+ `compression` may be `none`, `auto`, `smart`, `brotli`, `deflate`, or `lz`.
414
416
 
415
417
  - `none` stores the payload directly.
416
- - `auto` compresses only when the result is meaningfully smaller. If compression does not help, no internal envelope is added.
417
- - `lz` always stores the payload through QuadQR's portable LZ compressor.
418
+ - `auto` is the fast balanced mode: one pass each of LZ level 6, DEFLATE level 6, and Brotli quality 6, followed by a complete stored-size comparison including envelope overhead.
419
+ - `smart` is the CPU-heavy mode. It starts with the Auto pass, checks the resulting QuadQR version, and only escalates to DEFLATE 8 / Brotli 9 and then DEFLATE 9 / Brotli 11 when a smaller physical version is realistically reachable.
420
+ - `brotli` always stores the payload using QuadQR's bundled synchronous Brotli codec.
421
+ - `deflate` always stores the payload using QuadQR's synchronous pure-JavaScript raw DEFLATE codec.
422
+ - `lz` always stores the payload through the original portable LZSS-style compressor for backward compatibility.
423
+
424
+ Explicit LZ, DEFLATE, and Brotli support `compressionLevel`:
425
+
426
+ ```js
427
+ encodeText(text, { compression: "lz", compressionLevel: 9 }); // 1..9, default 6
428
+ encodeText(text, { compression: "deflate", compressionLevel: 9 }); // 1..9, default 6
429
+ encodeText(text, { compression: "brotli", compressionLevel: 11 }); // 0..11, default 11
430
+ ```
431
+
432
+ `lzLevel`, `deflateLevel`, and `brotliQuality` are accepted as algorithm-specific aliases. Levels affect encoder effort only and are not serialized because the decoder does not need them. Generated code objects expose `compressionLevel`, `compressionStrategy`, and Smart-mode diagnostics; scanned symbols only need the stored compression algorithm ID.
418
433
 
419
434
  There is no public content-type registry. Text remains text and byte arrays remain byte arrays.
420
435
 
421
- ### `compressPayload(input)` / `decompressPayload(input, expectedLength?)`
436
+ ### `compressPayload(input, options?)` / `decompressPayload(input, expectedLength?)`
437
+
438
+ Legacy portable synchronous LZSS-style compression helpers. `options.level` accepts integers `1..9` and defaults to 6. Higher levels walk deeper candidate history and may use bounded lazy matching; level 6 preserves the historical QuadQR search depth. The LZ wire format and decoder are unchanged at every level.
439
+
440
+ ### `compressDeflatePayload(input, options?)` / `decompressDeflatePayload(input, expectedLength?)`
422
441
 
423
- Portable synchronous LZSS-style compression helpers. They do not require Node zlib or browser `CompressionStream`.
442
+ Raw-DEFLATE helpers. `options.level` accepts integers `1..9` and defaults to 6. Higher levels spend more CPU on deeper LZ77 candidate search and lazy matching. The compressor emits standard RFC 1951 fixed-Huffman blocks with a 32 KiB match window and matches up to 258 bytes. The implementation is pure JavaScript and synchronous, so the same core works in browsers, Node.js servers, workers, and other JavaScript runtimes without `node:zlib`, `CompressionStream`, DOM APIs, or native dependencies.
443
+
444
+ ### `compressBrotliPayload(input, options?)` / `decompressBrotliPayload(input, expectedLength?)`
445
+
446
+ Bundled synchronous Brotli helpers. `compressBrotliPayload()` accepts an optional `{ quality }` setting from 0 through 11 for direct codec use. The generated stream is standard Brotli and the decoder accepts standard Brotli streams. QuadQR does not require `node:zlib`, browser `CompressionStream`, DOM APIs, a native addon, or a runtime dependency for this path.
424
447
 
425
448
  ## Binary convenience APIs
426
449
 
@@ -536,9 +559,9 @@ Use `scanImageData(image, { debug: true })` for detailed geometry/sampling infor
536
559
 
537
560
  ## Scanability and stress testing
538
561
 
539
- ### `applyStressDistortion(imageData, type, severity?)`
562
+ ### `applyStressDistortion(imageData, type, severity?, options?)`
540
563
 
541
- Deterministically applies a selected synthetic distortion.
564
+ Deterministically applies a selected synthetic distortion. In addition to the original blur/exposure/shadow/resampling profiles, `perspective-3d` accepts `pitchDegrees`, `yawDegrees`, and `rollDegrees` for camera-angle testing.
542
565
 
543
566
  ### `runImageStressTest(imageData, expected?, options?)`
544
567
 
@@ -548,4 +571,12 @@ Runs the standard torture profiles and returns a 0–100 score plus per-scenario
548
571
 
549
572
  Renders a code and runs the standard test suite. It returns `Excellent`, `Good`, `Risky`, or `Likely unscannable` together with recommendations.
550
573
 
551
- Synthetic scores are regression aids and do not replace physical camera/print testing.
574
+ ### `runReliabilityLab(imageData, expected?, options?)`
575
+
576
+ Runs the broader Reliability Lab suite. `options.suite` can be `quick`, `full`, or `extreme`. Results include overall score, per-scenario CRC verification, category scores, confidence, RS corrections, and timing.
577
+
578
+ ### `runPerspectiveSweep(imageData, expected?, options?)`
579
+
580
+ Sweeps `yaw`, `pitch`, or `roll`/Z rotation across a list of angles and reports the largest tested angle that decoded successfully.
581
+
582
+ Synthetic scores are regression aids and do not replace physical camera/print testing. See [`RELIABILITY_LAB.md`](./RELIABILITY_LAB.md).
@@ -54,6 +54,10 @@ const compressed = encodeText("repeat repeat repeat repeat", {
54
54
  compression: "auto"
55
55
  });
56
56
 
57
+ const smallest = encodeText("structured payload ".repeat(500), {
58
+ compression: "smart"
59
+ });
60
+
57
61
  const keys = await generateSigningKeyPair();
58
62
  const signed = await encodeSignedText("ticket", {
59
63
  compression: "auto",
@@ -72,7 +76,7 @@ const report = assessScanability(compressed, { imageSize: 480 });
72
76
  console.log(report.score, report.rating);
73
77
  ```
74
78
 
75
- Compression and signing use internal metadata only when required. There is no public content-type mode to configure. The private key signs, while the public verification key stays outside the QuadQR by default. Use the stored `keyId` to select an application-trusted public key. Scanability testing is deterministic synthetic regression testing and should be supplemented with real devices and print samples.
79
+ Compression uses bundled synchronous Brotli/DEFLATE/LZ implementations with explicit codec levels, fast Auto, and CPU-heavy Smart modes and signing uses internal metadata only when required. The repository demo keeps heavy encoding, verification, stress testing, perspective sweeps, image scanning, and benchmarks off the UI thread with module Web Workers, while the public library API itself remains synchronous where documented. There is no public content-type mode to configure. The private key signs, while the public verification key stays outside the QuadQR by default. Use the stored `keyId` to select an application-trusted public key. Scanability testing is deterministic synthetic regression testing and should be supplemented with real devices and print samples.
76
80
 
77
81
  ## Scan an uploaded image
78
82
 
@@ -133,7 +137,7 @@ The global browser build exposes `window.QuadQR` / `globalThis.QuadQR`.
133
137
 
134
138
  ```html
135
139
  <canvas id="qr"></canvas>
136
- <script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.1.0/dist/quadqr.min.js"></script>
140
+ <script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.4.1/dist/quadqr.min.js"></script>
137
141
  <script>
138
142
  const code = QuadQR.encodeText("Hello CDN");
139
143
 
@@ -147,7 +151,7 @@ The global browser build exposes `window.QuadQR` / `globalThis.QuadQR`.
147
151
  ## unpkg
148
152
 
149
153
  ```html
150
- <script src="https://unpkg.com/quadqr-js@1.1.0/dist/quadqr.min.js"></script>
154
+ <script src="https://unpkg.com/quadqr-js@1.4.1/dist/quadqr.min.js"></script>
151
155
  ```
152
156
 
153
157
  ## Direct CDN ESM
@@ -157,7 +161,7 @@ The global browser build exposes `window.QuadQR` / `globalThis.QuadQR`.
157
161
  import {
158
162
  encodeText,
159
163
  renderToCanvas
160
- } from "https://cdn.jsdelivr.net/npm/quadqr-js@1.1.0/dist/browser.js";
164
+ } from "https://cdn.jsdelivr.net/npm/quadqr-js@1.4.1/dist/browser.js";
161
165
 
162
166
  const code = encodeText("ES module CDN");
163
167
  renderToCanvas(code, document.querySelector("#qr"));
@@ -184,4 +188,4 @@ WASM is optional. The normal JavaScript implementation remains available if it c
184
188
 
185
189
  ## Production recommendation
186
190
 
187
- Pin a concrete package version such as `@1.1.0` in CDN URLs so an existing site does not silently change when a newer package version becomes available.
191
+ Pin a concrete package version such as `@1.4.1` in CDN URLs so an existing site does not silently change when a newer package version becomes available.
package/docs/CLI.md CHANGED
@@ -33,7 +33,17 @@ npx quadqr-js encode "repeat repeat repeat repeat" \
33
33
  -o compressed.png
34
34
  ```
35
35
 
36
- Compression modes are `none`, `auto`, and `lz`. `auto` keeps the original payload untouched when compression does not make it smaller. No separate payload mode is required.
36
+ Compression modes are `none`, `auto`, `smart`, `brotli`, `deflate`, and `lz`. `auto` performs one balanced comparison using LZ level 6, DEFLATE level 6, and Brotli quality 6. `smart` is CPU-heavy: it starts with the same pass and only escalates to stronger DEFLATE/Brotli levels when a smaller QuadQR version is realistically reachable. If envelope overhead would erase the gain, Auto/Smart leave the original payload untouched. No separate payload mode is required.
37
+
38
+ Explicit codecs can select a level:
39
+
40
+ ```bash
41
+ npx quadqr-js encode "structured payload" --compression lz --compression-level 9 -o lz.png
42
+ npx quadqr-js encode "structured payload" --compression deflate --compression-level 9 -o deflate.png
43
+ npx quadqr-js encode "structured payload" --compression brotli --compression-level 11 -o brotli.png
44
+ ```
45
+
46
+ LZ and DEFLATE accept levels `1..9` and default to 6. Brotli accepts qualities `0..11` and defaults to 11. `--compression-level` is ignored by Auto/Smart because those modes manage their own staged levels.
37
47
 
38
48
  ## Signed QuadQR
39
49
 
@@ -133,7 +143,8 @@ npx quadqr-js decode secure-key.png --key <64-hex-key>
133
143
  | `-o, --output <file>` | Output PNG/SVG path, or signing-key JSON path for `signkeygen` |
134
144
  | `--ecc <L|M|Q|H>` | QuadQR ECC profile. Default: `M` |
135
145
  | `--version <auto|1..40>` | Symbol version. Default: `auto` |
136
- | `--compression <mode>` | `none`, `auto`, or `lz`. Default: `auto` |
146
+ | `--compression <mode>` | `none`, `auto`, `smart`, `brotli`, `deflate`, or `lz`. Default: `auto` |
147
+ | `--compression-level <n>` | Explicit LZ/DEFLATE `1..9` or Brotli `0..11` encoder level |
137
148
  | `--high-density` | Enable experimental Triangle16 High Density Mode |
138
149
  | `--sign-key <file>` | Sign using a key bundle generated by `signkeygen` |
139
150
  | `--key-id <id>` | Override the signing key ID stored in the symbol |
@@ -0,0 +1,170 @@
1
+ # Compression 3.0
2
+
3
+ QuadQR keeps compression as an internal transport detail around normal text or byte payloads. It does not introduce a payload type system, and the decompressor does not need to know which compression level was used.
4
+
5
+ ## Modes
6
+
7
+ ```js
8
+ encodeText(text, { compression: "none" });
9
+ encodeText(text, { compression: "auto" });
10
+ encodeText(text, { compression: "smart" });
11
+ encodeText(text, { compression: "brotli" });
12
+ encodeText(text, { compression: "deflate" });
13
+ encodeText(text, { compression: "lz" });
14
+ ```
15
+
16
+ - `none` stores the application payload directly.
17
+ - `auto` is the fast balanced mode. It performs one pass with LZ level 6, DEFLATE level 6, and Brotli quality 6, compares the complete stored sizes, and keeps the smallest result.
18
+ - `smart` is the opt-in CPU-heavy mode. It starts with the same balanced candidates as `auto`, checks the resulting QuadQR version, and only spends more CPU on stronger DEFLATE/Brotli passes when a smaller physical QuadQR is realistically reachable.
19
+ - `brotli` forces the bundled Brotli codec.
20
+ - `deflate` forces the portable raw-DEFLATE codec.
21
+ - `lz` forces the original QuadQR LZSS-style stream for compatibility and testing.
22
+
23
+ For unsigned payloads, `auto` and `smart` include the 16-byte internal extension-envelope cost when deciding whether compression helps. If compression would not make the complete stored representation smaller, the original payload is kept with no compression envelope. Signed payloads already require the extension envelope, so their comparison includes the fixed signing metadata automatically. Secure payload planning also includes the fixed encryption-envelope overhead when Smart evaluates version boundaries.
24
+
25
+ ## Auto versus Smart
26
+
27
+ `auto` intentionally avoids expensive maximum-quality passes:
28
+
29
+ ```text
30
+ Raw
31
+ LZ level 6
32
+ DEFLATE level 6
33
+ Brotli quality 6
34
+ ↓
35
+ smallest final representation
36
+ ```
37
+
38
+ `smart` begins the same way, then considers the next smaller QuadQR version. When that boundary is close enough to be plausible, it escalates in stages:
39
+
40
+ ```text
41
+ Balanced pass
42
+ LZ 6
43
+ DEFLATE 6
44
+ Brotli 6
45
+ ↓
46
+ Is a smaller QuadQR version realistically reachable?
47
+ ↓ yes
48
+ Strong pass
49
+ DEFLATE 8
50
+ Brotli 9
51
+ ↓
52
+ Still close to another/same smaller-version boundary?
53
+ ↓ yes
54
+ Maximum pass
55
+ DEFLATE 9
56
+ Brotli 11
57
+ ```
58
+
59
+ Smart is deliberately CPU-heavy and should be used when minimizing the physical matrix matters more than generation time. If an exact `version` is requested and the balanced pass does not fit, Smart may also escalate to stronger levels when that can plausibly make the requested version fit. The current plausibility gates are intentionally conservative: the strong stage is considered when the next boundary is within 30% of the current stored size or 192 bytes, and the maximum stage when it is within 16% or 96 bytes. The demo runs Smart in a Web Worker so the browser UI remains responsive.
60
+
61
+ The generated code object includes generation-only diagnostics such as `compressionLevel`, `compressionStrategy`, and `smartCompression`. Compression level is not stored in the QuadQR because LZ, DEFLATE, and Brotli decoders do not need it.
62
+
63
+ ## Explicit compression levels
64
+
65
+ Use the generic `compressionLevel` option when you explicitly select LZ, DEFLATE, or Brotli:
66
+
67
+ ```js
68
+ const lz = encodeText(text, {
69
+ compression: "lz",
70
+ compressionLevel: 9
71
+ });
72
+
73
+ const deflated = encodeText(text, {
74
+ compression: "deflate",
75
+ compressionLevel: 9
76
+ });
77
+
78
+ const brotlied = encodeText(text, {
79
+ compression: "brotli",
80
+ compressionLevel: 11
81
+ });
82
+ ```
83
+
84
+ Ranges and defaults:
85
+
86
+ | Algorithm | Range | Explicit default |
87
+ | --- | ---: | ---: |
88
+ | Legacy LZ | 1..9 | 6 |
89
+ | DEFLATE | 1..9 | 6 |
90
+ | Brotli | 0..11 | 11 |
91
+
92
+ Algorithm-specific aliases are also accepted: `lzLevel`, `deflateLevel`, and `brotliQuality`. `compressionLevel` is the recommended public option.
93
+
94
+ Levels only affect encoder CPU/search effort and output size. They do not change the compression ID or decoder behavior.
95
+
96
+ ## Legacy LZ profile
97
+
98
+ Compression ID `1` keeps the original QuadQR LZSS-style wire format. Levels `1..9` only control how much encoder work is spent walking candidate history and, at stronger levels, bounded lazy-match lookahead. Level 6 preserves the historical 32-candidate QuadQR search depth, so existing callers that do not pass a level keep the same default behavior.
99
+
100
+ The direct helpers are:
101
+
102
+ ```js
103
+ compressPayload(bytes, { level: 9 });
104
+ decompressPayload(compressedBytes, originalLength);
105
+ ```
106
+
107
+ The LZ window remains 4095 bytes and each back-reference still represents a 3..18 byte match. The compressed stream format is identical at every level, so the level is never serialized and old/new LZ decoders remain compatible. Auto and Smart use LZ level 6; Smart does not escalate LZ because its additional staged CPU budget is reserved for the stronger DEFLATE/Brotli candidates.
108
+
109
+ ## Brotli profile
110
+
111
+ Compression ID `3` is a standard Brotli stream. QuadQR vendors the codec into the library so Brotli compression and decompression are synchronous and available in browser ESM, the classic browser bundle, Web Workers, and server-side Node.js without a runtime package or native binding.
112
+
113
+ The direct helpers are:
114
+
115
+ ```js
116
+ compressBrotliPayload(bytes, { quality: 9 });
117
+ decompressBrotliPayload(compressedBytes, originalLength);
118
+ ```
119
+
120
+ Brotli quality accepts integers `0..11`. Explicit `compression: "brotli"` defaults to quality 11 for backward compatibility. `auto` starts at quality 6. `smart` starts at 6 and may test 9 and 11 when a smaller QuadQR version is plausible.
121
+
122
+ ## Portable DEFLATE profile
123
+
124
+ Compression ID `2` is a raw RFC 1951 DEFLATE stream. QuadQR's bundled encoder uses a fixed-Huffman block with level-dependent LZ77 match-search effort. It supports:
125
+
126
+ - a 32 KiB LZ77 window;
127
+ - match distances up to 32768 bytes;
128
+ - match lengths up to 258 bytes;
129
+ - DEFLATE levels `1..9`;
130
+ - progressively deeper candidate search and lazy matching at stronger levels;
131
+ - deterministic synchronous output;
132
+ - no runtime dependency.
133
+
134
+ The direct helpers are:
135
+
136
+ ```js
137
+ compressDeflatePayload(bytes, { level: 9 });
138
+ decompressDeflatePayload(compressedBytes, originalLength);
139
+ ```
140
+
141
+ The stream remains standard raw DEFLATE at every level. The level is not serialized.
142
+
143
+ ## Node.js and browser portability
144
+
145
+ All payload compression paths are bundled JavaScript. The library does not import `node:zlib`, use browser `CompressionStream`, depend on DOM APIs, or fetch a codec at runtime.
146
+
147
+ ```js
148
+ import { encodeText, decodeMatrix } from "quadqr-js";
149
+
150
+ const code = encodeText("hello ".repeat(1000), {
151
+ compression: "smart"
152
+ });
153
+
154
+ const decoded = decodeMatrix(code.matrix);
155
+ console.log(code.compression, code.compressionLevel);
156
+ console.log(decoded.compression); // decoder only needs the algorithm ID
157
+ ```
158
+
159
+ ## Compression IDs and compatibility
160
+
161
+ The internal envelope keeps the stable IDs:
162
+
163
+ ```text
164
+ 0 = none
165
+ 1 = legacy LZ
166
+ 2 = raw DEFLATE
167
+ 3 = Brotli
168
+ ```
169
+
170
+ Compression level is intentionally not part of the envelope. Existing LZ, DEFLATE, and Brotli decoders remain compatible with streams produced at any supported level.
@@ -163,7 +163,7 @@ PNG generation/decoding and SVG generation are built into the Node.js adapter.
163
163
 
164
164
  ```html
165
165
  <canvas id="qr"></canvas>
166
- <script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.1.0/dist/quadqr.min.js"></script>
166
+ <script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.4.1/dist/quadqr.min.js"></script>
167
167
  <script>
168
168
  const code = QuadQR.encodeText("No build step");
169
169
  QuadQR.renderToCanvas(code, document.querySelector("#qr"));
@@ -201,7 +201,17 @@ const code = encodeText("repeat repeat repeat repeat", {
201
201
  });
202
202
  ```
203
203
 
204
- `auto` keeps the original payload untouched when compression would not save space.
204
+ `auto` is the fast default: it compares LZ level 6, DEFLATE level 6, and Brotli quality 6 once, then keeps the smallest complete representation. `smart` is the CPU-heavy option; it starts with the same pass and only tries DEFLATE 8/9 and Brotli 9/11 when stronger compression can realistically reduce the QuadQR version.
205
+
206
+ When you explicitly choose a codec, `compressionLevel` controls encoder effort:
207
+
208
+ ```js
209
+ encodeText(text, { compression: "lz", compressionLevel: 9 }); // 1..9
210
+ encodeText(text, { compression: "deflate", compressionLevel: 9 }); // 1..9
211
+ encodeText(text, { compression: "brotli", compressionLevel: 11 }); // 0..11
212
+ ```
213
+
214
+ LZ defaults to level 6, DEFLATE defaults to level 6, and Brotli defaults to quality 11. The level is not stored in the symbol because decoding does not depend on it.
205
215
 
206
216
  For offline integrity verification:
207
217
 
@@ -46,9 +46,9 @@ This uses four cells per protected header byte, exactly like RGBW, but makes it
46
46
 
47
47
  ## Scanner sampling
48
48
 
49
- The scanner does not sample the diagonal or the exact module center. For each data cell it samples two small regions well inside the triangles, approximately around `(0.28, 0.28)` and `(0.72, 0.72)` in normalized module coordinates.
49
+ The scanner does not sample the diagonal or the exact module center. Each triangle now uses a small **three-anchor sampling cluster** positioned safely inside its region. The cluster is robustly aggregated, and the variation between the three anchors is retained as a spatial-stability signal. This is less sensitive to blur, resampling, small homography offsets, and diagonal color bleed than relying on one point per triangle.
50
50
 
51
- Each triangle is classified independently against the calibrated RGBW palette. The cell confidence is the weaker of the two triangle confidences. That confidence feeds the existing confidence-aware Spectrum ECC path, allowing an ambiguous triangle pair to become a useful erasure candidate instead of an arbitrary hard error.
51
+ Each triangle is classified independently against the calibrated RGBW palette. The cell confidence combines the weaker color-classification confidence with the measured within-triangle sample stability. The scanner also retains the most plausible alternate Triangle16 state by changing the less reliable of the two regions first. Those confidence and alternate-state signals feed Spectrum ECC 2.0 for erasure and bounded soft-decision recovery.
52
52
 
53
53
  The normal image and camera scanner automatically detects High Density Mode and attempts Triangle16 dual-region sampling when needed. No separate scan mode is required. If ordinary finder/alignment geometry is good enough to locate the symbol but not precise enough to decode the half-cell regions, the scanner performs one bounded **precise-alignment recovery** pass. That pass uses denser alignment-pattern probes and a finer sub-module search, then retries the same dual-triangle classifier.
54
54
 
package/docs/NODE.md CHANGED
@@ -68,6 +68,28 @@ const result = await QuadQRNode.scanBuffer(pngBuffer);
68
68
 
69
69
  PNG generation and decoding do not require external native dependencies.
70
70
 
71
+ ## Server-side compression
72
+
73
+ Compression uses the same synchronous JavaScript codec on Node.js as it does in the browser. No DOM API, browser `CompressionStream`, or Node `zlib` call is required by the QuadQR payload codec.
74
+
75
+ ```js
76
+ import { encodeText, decodeMatrix } from "quadqr-js";
77
+
78
+ const code = encodeText("hello ".repeat(1000), {
79
+ compression: "auto"
80
+ });
81
+
82
+ const smallest = encodeText("structured payload ".repeat(500), {
83
+ compression: "smart"
84
+ });
85
+
86
+ const decoded = decodeMatrix(code.matrix);
87
+ console.log(decoded.compression); // usually "brotli" for repetitive data
88
+ console.log(decoded.text);
89
+ ```
90
+
91
+ `compression: "auto"` performs one balanced comparison using LZ level 6, DEFLATE level 6, and Brotli quality 6. `compression: "smart"` is the opt-in CPU-heavy mode and escalates to stronger levels only when a smaller QuadQR version is realistically reachable. Explicit `lz` and `deflate` accept `compressionLevel: 1..9` (default 6), while explicit `brotli` accepts `compressionLevel: 0..11` (default 11). `lzLevel`, `deflateLevel`, and `brotliQuality` are available as algorithm-specific aliases.
92
+
71
93
  ## Other image formats
72
94
 
73
95
  For JPEG, WebP, or AVIF input, the Node adapter can use `sharp` when the consuming application has it installed:
package/docs/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # QuadQR Documentation
2
2
 
3
- QuadQR is an experimental RGBW matrix symbology. Default RGBW cells carry two bits using red, green, blue, or white. The experimental High Density Mode splits payload cells into two RGBW triangles and carries four raw bits per body data cell. The JavaScript library supports encoding, canvas/RGBA/SVG rendering, adjustable quiet zones, optional centered logos with transparent or cleared backgrounds, matrix decoding, image and camera scanning, Spectrum ECC, optional authenticated encryption, Node.js PNG/SVG workflows, CDN usage, a CLI, TypeScript declarations, and optional prebuilt WebAssembly acceleration.
3
+ QuadQR is an experimental RGBW matrix symbology. Default RGBW cells carry two bits using red, green, blue, or white. The experimental High Density Mode splits payload cells into two RGBW triangles and carries four raw bits per body data cell. The JavaScript library supports encoding, canvas/RGBA/SVG rendering, adjustable quiet zones, optional centered logos with transparent or cleared backgrounds, matrix decoding, image and camera scanning, Spectrum ECC, optional authenticated encryption, Node.js PNG/SVG workflows, CDN usage, a CLI, and optional prebuilt WebAssembly acceleration.
4
4
 
5
5
  ## Live links
6
6
 
7
- - [Documentation Site](https://akanshsirohi.github.io/QuadQR/docs-site/)
7
+ - [Documentation Site](https://akanshsirohi.github.io/QuadQR/documentation/)
8
8
  - [Interactive Demo](https://akanshsirohi.github.io/QuadQR/demo/)
9
9
  - [quadqr-js on npm](https://www.npmjs.com/package/quadqr-js)
10
10
  - [GitHub Repository](https://github.com/akanshsirohi/QuadQR)
@@ -13,7 +13,9 @@ QuadQR is an experimental RGBW matrix symbology. Default RGBW cells carry two bi
13
13
 
14
14
  - [Getting Started](./GETTING_STARTED.md)
15
15
  - [API Reference](./API.md)
16
+ - [Compression 3.0](./COMPRESSION.md)
16
17
  - [High Density Mode (Experimental)](./HIGH_DENSITY_MODE.md)
18
+ - [Reliability Lab](./RELIABILITY_LAB.md)
17
19
  - [Browser and CDN](./BROWSER_CDN.md)
18
20
  - [Node.js](./NODE.md)
19
21
  - [CLI](./CLI.md)
@@ -46,8 +48,8 @@ QuadQR is licensed under AGPL-3.0. See [`LICENSE`](../LICENSE).
46
48
 
47
49
  The current library also includes:
48
50
 
49
- - normal text/byte payloads with optional internal LZ compression;
50
- - portable automatic LZ compression;
51
+ - normal text/byte payloads with optional internal Compression 3.0;
52
+ - bundled synchronous Brotli + portable DEFLATE + level-aware legacy LZ compatibility;
51
53
  - first-class binary `Uint8Array` APIs;
52
54
  - Ed25519 signed QuadQR payloads and trusted-key verification;
53
55
  - signed + AES-256-GCM encrypted composition;
@@ -55,6 +57,6 @@ The current library also includes:
55
57
  - ECC-aware automatic logo sizing;
56
58
  - normalized scanner confidence and detailed debug mode;
57
59
  - deterministic scanability/torture testing;
58
- - an interactive browser stress-test lab and capacity calculator.
60
+ - a dedicated browser Reliability Lab with 3D perspective sweeps, plus the capacity calculator.
59
61
 
60
62
  See [`../SPECIFICATION.md`](../SPECIFICATION.md) for the layering and interoperability rules and [`API.md`](./API.md) for the public APIs.
@@ -0,0 +1,64 @@
1
+ # Reliability Lab
2
+
3
+ QuadQR includes a deterministic Reliability Lab for regression testing generated symbols against camera-style damage. It is intended for comparing scanner changes, render settings, ECC profiles, output size, and the experimental High Density Mode under repeatable conditions.
4
+
5
+ The lab verifies the decoded payload CRC. Finding three locators is not counted as success unless the final payload is recovered correctly.
6
+
7
+ ## Browser demo
8
+
9
+ Open the **Reliability Lab** tab after generating a QuadQR. The lab provides:
10
+
11
+ - Quick, Full, and Extreme suites;
12
+ - per-scenario pass/fail, confidence, Reed-Solomon correction count, and decode time;
13
+ - category-level scoring for optics, lighting, color, resampling, sensor damage, and perspective;
14
+ - a 3D perspective playground with X pitch, Y yaw, and Z rotation controls;
15
+ - angle sweeps to find the largest tested pitch, yaw, or Z rotation that still decodes.
16
+
17
+ X pitch and Y yaw simulate a code plane tilting in depth. Z rotation is an in-plane rotation. The controls can be combined.
18
+
19
+ ## Public API
20
+
21
+ ```js
22
+ import {
23
+ applyStressDistortion,
24
+ runReliabilityLab,
25
+ runPerspectiveSweep
26
+ } from "quadqr-js";
27
+
28
+ const transformed = applyStressDistortion(imageData, "perspective-3d", 0.5, {
29
+ pitchDegrees: 20,
30
+ yawDegrees: 45,
31
+ rollDegrees: 15
32
+ });
33
+
34
+ const report = runReliabilityLab(imageData, {
35
+ version: encoded.version,
36
+ crc32: encoded.crc32
37
+ }, {
38
+ suite: "full"
39
+ });
40
+
41
+ const sweep = runPerspectiveSweep(imageData, {
42
+ version: encoded.version,
43
+ crc32: encoded.crc32
44
+ }, {
45
+ axis: "yaw",
46
+ angles: [0, 15, 25, 35, 45, 55]
47
+ });
48
+ ```
49
+
50
+ ## Suites
51
+
52
+ The Reliability Lab extends the smaller scanability suite with deterministic cases for lens blur, motion blur, low/high exposure, gradient shadow, glare, warm/cool color casts, contrast loss, sensor noise, JPEG-like damage, downscaling, projective skew, 3D pitch/yaw, Z rotation, and combined 3D tilt.
53
+
54
+ The **Extreme** suite adds stronger perspective cases. These are deliberately demanding and may fail at low pixels-per-module even when the same angle succeeds at a larger rendered size.
55
+
56
+ ## Perspective scanner recovery
57
+
58
+ The scanner keeps its normal fast path for ordinary frames. When locator scale differences indicate projective foreshortening, the geometry stage can perform a bounded coarse-to-fine search for the primary alignment reference. The fine stage uses denser sub-cell scoring so payload cells are less likely to impersonate the alignment marker. This improves steep-angle recovery without weakening normal structure checks globally.
59
+
60
+ Distributed secondary alignment references are still used to validate the resulting homography.
61
+
62
+ ## Interpreting results
63
+
64
+ Synthetic results are regression aids, not a substitute for real phone-camera and print testing. A useful practical metric is the largest reliable angle at the intended physical size and scanning distance, not only the theoretical bits per cell.
@@ -1,6 +1,6 @@
1
- # Triangle16 experimental data-cell profile
1
+ # Triangle16 internals for High Density Mode
2
2
 
3
- Triangle16 is an experimental QuadQR data-cell encoding for the high-density branch. It keeps the existing QuadQR geometry, finder patterns, timing pattern, alignment patterns, calibration, Spectrum ECC, CRC, security envelope, compression, and signing behavior, but changes how payload data cells are represented.
3
+ Triangle16 is the experimental physical cell layout used internally by QuadQR **High Density Mode**. It keeps the existing QuadQR geometry, finder patterns, timing pattern, alignment patterns, calibration, Spectrum ECC, CRC, security envelope, compression, and signing behavior, but changes how payload data cells are represented.
4
4
 
5
5
  ## Physical cell
6
6
 
@@ -46,9 +46,7 @@ This uses four cells per protected header byte, exactly like RGBW, but makes it
46
46
 
47
47
  ## Scanner sampling
48
48
 
49
- The scanner does not sample the diagonal or the exact module center. For each data cell it samples two small regions well inside the triangles, approximately around `(0.28, 0.28)` and `(0.72, 0.72)` in normalized module coordinates.
50
-
51
- Each triangle is classified independently against the calibrated RGBW palette. The cell confidence is the weaker of the two triangle confidences. That confidence feeds the existing confidence-aware Spectrum ECC path, allowing an ambiguous triangle pair to become a useful erasure candidate instead of an arbitrary hard error.
49
+ The scanner does not sample the diagonal or the exact module center. Each triangle uses three protected interior sample anchors, robust aggregation, and a spatial-stability score. The weaker/less stable region lowers the cell confidence and supplies the first alternate state for Spectrum ECC 2.0 soft recovery.
52
50
 
53
51
  The normal image and camera scanner automatically attempts Triangle16 sampling. No separate scan mode is required. If ordinary finder/alignment geometry is good enough to locate the symbol but not precise enough to decode the half-cell regions, the scanner performs one bounded **precise-alignment recovery** pass. That pass uses denser alignment-pattern probes and a finer sub-module search, then retries the same dual-triangle classifier.
54
52
 
@@ -61,23 +59,15 @@ PNG/ImageData, Canvas, SVG, browser, and Node rendering all support Triangle16.
61
59
  ## API
62
60
 
63
61
  ```js
64
- import { encodeText, CELL_ENCODINGS } from "quadqr-js";
62
+ import { encodeText } from "quadqr-js";
65
63
 
66
64
  const code = encodeText("High-density QuadQR", {
67
65
  ecc: "M",
68
- cellEncoding: CELL_ENCODINGS.TRIANGLE16
69
- });
70
- ```
71
-
72
- The string form is also accepted:
73
-
74
- ```js
75
- const code = encodeText("High-density QuadQR", {
76
- cellEncoding: "triangle16"
66
+ highDensity: true
77
67
  });
78
68
  ```
79
69
 
80
- `rgbw` remains the library default so existing callers are not silently moved to an experimental physical format.
70
+ High Density Mode is disabled by default. `Triangle16` is an internal/technical name for the current experimental physical layout, not a separate public mode selector.
81
71
 
82
72
  ## Capacity
83
73
 
@@ -90,7 +80,7 @@ Triangle16 16 states 4 bits/body cell
90
80
 
91
81
  The protected header remains RGBW-equivalent, and ECC/CRC overhead is unchanged at the byte level, so usable payload capacity is close to but not exactly 2x for a fixed matrix version.
92
82
 
93
- Use `getVersionInfo(version, { ecc, cellEncoding: "triangle16" })` or the demo capacity calculator for the exact value.
83
+ Use `getVersionInfo(version, { ecc, highDensity: true })` or the demo capacity calculator for the exact value.
94
84
 
95
85
  ## Reliability caveat
96
86
 
package/docs/WASM.md CHANGED
@@ -56,7 +56,7 @@ You can also provide WASM bytes directly through the `bytes` option.
56
56
  When the classic `quadqr.min.js` global build is loaded from a CDN, `QuadQR.initWasm()` resolves the bundled sibling WASM asset from the same package/version location.
57
57
 
58
58
  ```html
59
- <script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.1.0/dist/quadqr.min.js"></script>
59
+ <script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.4.1/dist/quadqr.min.js"></script>
60
60
  <script>
61
61
  await QuadQR.initWasm();
62
62
  </script>