quadqr-js 1.2.0 → 1.4.2

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