quadqr-js 1.4.2 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FORMAT.md +5 -5
- package/README.md +19 -8
- package/SPECIFICATION.md +12 -12
- package/dist/camera-scanner-worker.js +793 -0
- package/dist/esm/camera-scanner-worker.js +793 -0
- package/dist/esm/geometry.js +25 -10
- package/dist/esm/quadqr.js +843 -62
- package/dist/esm/vision.js +887 -122
- package/dist/esm/wasm.js +85 -12
- package/dist/quadqr.js +1803 -198
- package/dist/quadqr.min.js +2032 -274
- package/dist/wasm/quadqr-core.wasm +0 -0
- package/docs/API.md +13 -5
- package/docs/BROWSER_CDN.md +5 -5
- package/docs/CLI.md +167 -167
- package/docs/COMPRESSION.md +170 -170
- package/docs/GETTING_STARTED.md +1 -1
- package/docs/HIGH_DENSITY_MODE.md +100 -100
- package/docs/NODE.md +159 -159
- package/docs/README.md +62 -62
- package/docs/RELIABILITY_LAB.md +64 -64
- package/docs/TRIANGLE16.md +98 -98
- package/docs/WASM.md +30 -4
- package/package.json +5 -3
package/docs/COMPRESSION.md
CHANGED
|
@@ -1,170 +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.
|
|
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.
|
package/docs/GETTING_STARTED.md
CHANGED
|
@@ -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.
|
|
166
|
+
<script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.5.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"));
|
|
@@ -1,100 +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.
|
|
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.
|