chessmatrix 1.0.0__tar.gz
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.
- chessmatrix-1.0.0/LICENSE +21 -0
- chessmatrix-1.0.0/PKG-INFO +339 -0
- chessmatrix-1.0.0/README.md +314 -0
- chessmatrix-1.0.0/chessmatrix.egg-info/PKG-INFO +339 -0
- chessmatrix-1.0.0/chessmatrix.egg-info/SOURCES.txt +10 -0
- chessmatrix-1.0.0/chessmatrix.egg-info/dependency_links.txt +1 -0
- chessmatrix-1.0.0/chessmatrix.egg-info/entry_points.txt +2 -0
- chessmatrix-1.0.0/chessmatrix.egg-info/requires.txt +3 -0
- chessmatrix-1.0.0/chessmatrix.egg-info/top_level.txt +1 -0
- chessmatrix-1.0.0/chessmatrix.py +621 -0
- chessmatrix-1.0.0/pyproject.toml +41 -0
- chessmatrix-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zach Mills
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chessmatrix
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: ChessMatrix — 8×8 four-color 2D barcode codec with Reed-Solomon error correction
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/hacker6284/ChessMatrix
|
|
7
|
+
Project-URL: Repository, https://github.com/hacker6284/ChessMatrix
|
|
8
|
+
Project-URL: Issues, https://github.com/hacker6284/ChessMatrix/issues
|
|
9
|
+
Keywords: barcode,2d-barcode,color-barcode,reed-solomon,chessmatrix
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Provides-Extra: render
|
|
23
|
+
Requires-Dist: Pillow>=9.0; extra == "render"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# ChessMatrix
|
|
27
|
+
|
|
28
|
+
**A 4-color 8×8 2D barcode format.**
|
|
29
|
+
|
|
30
|
+
ChessMatrix is a compact, machine-readable 2D barcode that fits in an 8×8 cell grid — the same footprint as a chessboard — by using four colors (black, red, green, blue) instead of the traditional two, yielding 2 bits of information per cell. It is designed as a minimal, scannable symbol for applications where even the smallest standard formats (DataMatrix 10×10, Micro QR 11×11) are too large.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
**Python**
|
|
35
|
+
```bash
|
|
36
|
+
pip install chessmatrix
|
|
37
|
+
# render_image() also requires Pillow:
|
|
38
|
+
pip install "chessmatrix[render]"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**JavaScript / Node.js**
|
|
42
|
+
```bash
|
|
43
|
+
npm install chessmatrix
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Browser (no bundler)**
|
|
47
|
+
```html
|
|
48
|
+
<script type="module">
|
|
49
|
+
import { buildGrid, renderGrid } from 'https://cdn.jsdelivr.net/npm/chessmatrix/chessmatrix.js';
|
|
50
|
+
</script>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 1. Motivation
|
|
56
|
+
|
|
57
|
+
The smallest standard 2D barcodes — DataMatrix 10×10 and Micro QR Version 1 (11×11) — cannot physically fit in an 8×8 cell footprint. ChessMatrix fills this gap by encoding 2 bits per cell via color rather than 1 bit per cell via luminance, achieving a useful payload within the 8×8 constraint.
|
|
58
|
+
|
|
59
|
+
**Net payload: 4 bytes (32 bits) per symbol**, protected by Reed-Solomon error correction.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 2. Symbol Structure
|
|
64
|
+
|
|
65
|
+
The symbol is an 8×8 grid of colored cells, each one of:
|
|
66
|
+
|
|
67
|
+
| Symbol | Value | RGB (recommended) |
|
|
68
|
+
|--------|-------|-------------------|
|
|
69
|
+
| BLACK | `0b00` | `#0A0A0A` |
|
|
70
|
+
| RED | `0b01` | `#DC2828` |
|
|
71
|
+
| GREEN | `0b10` | `#28B428` |
|
|
72
|
+
| BLUE | `0b11` | `#2828DC` |
|
|
73
|
+
|
|
74
|
+
### 2.1 Grid Zones
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
Col: 0 1 2 3 4 5 6 7
|
|
78
|
+
┌────┬────┬────┬────┬────┬────┬────┬────┐
|
|
79
|
+
R0 │ T │ t │ T │ t │ T │ t │ T │ t │ ← Top timing (alternating K/W)
|
|
80
|
+
├────┼────┼────┼────┼────┼────┼────┼────┤
|
|
81
|
+
R1 │ F │[K] │ D │ D │ D │ D │[R] │ tm │ ← [K] = BLACK anchor
|
|
82
|
+
├────┼────┼────┼────┼────┼────┼────┼────┤
|
|
83
|
+
R2 │ F │ D │ D │ D │ D │ D │ D │ tm │
|
|
84
|
+
├────┼────┼────┼────┼────┼────┼────┼────┤
|
|
85
|
+
R3 │ F │ D │ D │ D │ D │ D │ D │ tm │
|
|
86
|
+
├────┼────┼────┼────┼────┼────┼────┼────┤
|
|
87
|
+
R4 │ F │ D │ D │ D │ D │ D │ D │ tm │
|
|
88
|
+
├────┼────┼────┼────┼────┼────┼────┼────┤
|
|
89
|
+
R5 │ F │ D │ D │ D │ D │ D │ D │ tm │
|
|
90
|
+
├────┼────┼────┼────┼────┼────┼────┼────┤
|
|
91
|
+
R6 │ F │[G] │ D │ D │ D │ D │[B] │ tm │ ← [G] = GREEN anchor, [B] = BLUE anchor
|
|
92
|
+
├────┼────┼────┼────┼────┼────┼────┼────┤
|
|
93
|
+
R7 │ F │ F │ F │ F │ F │ F │ F │ F │ ← Bottom finder (all BLACK)
|
|
94
|
+
└────┴────┴────┴────┴────┴────┴────┴────┘
|
|
95
|
+
|
|
96
|
+
F = Finder bar (BLACK)
|
|
97
|
+
T/t = Timing cell (T=BLACK, t=WHITE, alternating)
|
|
98
|
+
tm = Right timing column (alternating K/W, row0=WHITE, row1=BLACK, ...)
|
|
99
|
+
[K][R][G][B] = Color calibration anchors
|
|
100
|
+
D = Data cell (2-bit color-encoded)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 2.2 Border Cells (28 cells)
|
|
104
|
+
|
|
105
|
+
**Left finder bar** — column 0, rows 0–7: all **BLACK**
|
|
106
|
+
|
|
107
|
+
**Bottom finder bar** — row 7, cols 0–7: all **BLACK**
|
|
108
|
+
|
|
109
|
+
**Top timing strip** — row 0, cols 0–7:
|
|
110
|
+
- Even columns (0, 2, 4, 6): **BLACK**
|
|
111
|
+
- Odd columns (1, 3, 5, 7): **WHITE**
|
|
112
|
+
|
|
113
|
+
**Right timing strip** — col 7, rows 0–7:
|
|
114
|
+
- Even rows (0, 2, 4, 6): **WHITE**
|
|
115
|
+
- Odd rows (1, 3, 5, 7): **BLACK**
|
|
116
|
+
|
|
117
|
+
The timing strips continue to alternate even at their shared corners: (0,7) is WHITE (even column dominates in top timing; right timing confirms even row → WHITE).
|
|
118
|
+
|
|
119
|
+
### 2.3 Color Calibration Anchors (4 cells)
|
|
120
|
+
|
|
121
|
+
Located at the four corners of the 6×6 interior:
|
|
122
|
+
|
|
123
|
+
| Position | Color | Purpose |
|
|
124
|
+
|----------|--------|----------------------------------|
|
|
125
|
+
| (row=1, col=1) | **BLACK** | Dark reference |
|
|
126
|
+
| (row=1, col=6) | **RED** | Red channel reference |
|
|
127
|
+
| (row=6, col=1) | **GREEN** | Green channel reference |
|
|
128
|
+
| (row=6, col=6) | **BLUE** | Blue channel reference |
|
|
129
|
+
|
|
130
|
+
A decoder samples these four cells first to calibrate its color discrimination thresholds before decoding any data cells. This compensates for lighting color temperature, ink variation, and camera white-balance errors.
|
|
131
|
+
|
|
132
|
+
### 2.4 Data Cells (32 cells)
|
|
133
|
+
|
|
134
|
+
The remaining interior cells, read in row-major order (top to bottom, left to right), skipping the four anchors:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
Row 1: (1,2) (1,3) (1,4) (1,5) — 4 cells
|
|
138
|
+
Row 2: (2,1) (2,2) (2,3) (2,4) (2,5) (2,6) — 6 cells
|
|
139
|
+
Row 3: (3,1) (3,2) (3,3) (3,4) (3,5) (3,6) — 6 cells
|
|
140
|
+
Row 4: (4,1) (4,2) (4,3) (4,4) (4,5) (4,6) — 6 cells
|
|
141
|
+
Row 5: (5,1) (5,2) (5,3) (5,4) (5,5) (5,6) — 6 cells
|
|
142
|
+
Row 6: (6,2) (6,3) (6,4) (6,5) — 4 cells
|
|
143
|
+
TOTAL = 32 cells
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 3. Data Encoding
|
|
149
|
+
|
|
150
|
+
### 3.1 Color-to-Bit Mapping
|
|
151
|
+
|
|
152
|
+
Each cell encodes exactly 2 bits:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
BLACK = 0b00 = 0
|
|
156
|
+
RED = 0b01 = 1
|
|
157
|
+
GREEN = 0b10 = 2
|
|
158
|
+
BLUE = 0b11 = 3
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 3.2 Bit Packing
|
|
162
|
+
|
|
163
|
+
32 cells × 2 bits = **64 bits = 8 bytes** raw capacity.
|
|
164
|
+
|
|
165
|
+
Cells are packed MSB-first, 4 cells per byte:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
Byte 0: cells[0] bits[7:6] | cells[1] bits[5:4] | cells[2] bits[3:2] | cells[3] bits[1:0]
|
|
169
|
+
Byte 1: cells[4] bits[7:6] | cells[5] bits[5:4] | cells[6] bits[3:2] | cells[7] bits[1:0]
|
|
170
|
+
...
|
|
171
|
+
Byte 7: cells[28]...cells[31]
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
To decode byte `b` from cells `4b`, `4b+1`, `4b+2`, `4b+3`:
|
|
175
|
+
```
|
|
176
|
+
byte_val = (color[4b] << 6) | (color[4b+1] << 4) | (color[4b+2] << 2) | color[4b+3]
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 4. Reed-Solomon Error Correction
|
|
182
|
+
|
|
183
|
+
ChessMatrix uses a **RS(8, 4)** code over GF(2⁸):
|
|
184
|
+
|
|
185
|
+
| Parameter | Value |
|
|
186
|
+
|-----------|-------|
|
|
187
|
+
| Total codeword length | 8 symbols (bytes) |
|
|
188
|
+
| Data symbols | 4 bytes |
|
|
189
|
+
| Parity symbols | 4 bytes |
|
|
190
|
+
| Maximum correctable errors | **2 symbol errors** |
|
|
191
|
+
| Maximum detectable errors | 4 symbol errors |
|
|
192
|
+
| Primitive polynomial | x⁸ + x⁴ + x³ + x² + 1 (`0x11D`) |
|
|
193
|
+
| Generator roots | α⁰, α¹, α², α³ (where α = 2) |
|
|
194
|
+
|
|
195
|
+
The 4 data bytes occupy the **first 4 bytes** of the codeword; the 4 parity bytes occupy bytes 5–8. The codeword is stored across the 32 data cells in byte order (data bytes first, then parity bytes).
|
|
196
|
+
|
|
197
|
+
### 4.1 Error Budget Rationale
|
|
198
|
+
|
|
199
|
+
Color discrimination under real-world conditions is less reliable than luminance discrimination. A dirty scan, off-angle lighting, or color-shifted camera can misread an entire cell. Allocating 50% of capacity to error correction (vs. ~25% in standard DataMatrix 10×10) reflects this elevated error rate. RS(8,4) can correct any 2 completely wrong cells, which covers most single-color-channel failures.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 5. Encoding Algorithm
|
|
204
|
+
|
|
205
|
+
1. **Validate input**: data must be exactly 4 bytes.
|
|
206
|
+
2. **RS encode**: compute 4 parity bytes via polynomial long division over GF(2⁸), producing an 8-byte codeword `[d0, d1, d2, d3, p0, p1, p2, p3]`.
|
|
207
|
+
3. **Pack into cells**: convert the 8 bytes to 32 dibits (2-bit values), one per data cell.
|
|
208
|
+
4. **Build grid**: initialize the 8×8 grid with the border pattern (finder + timing) and calibration anchors, then write the 32 data cells.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 6. Decoding Algorithm
|
|
213
|
+
|
|
214
|
+
1. **Locate symbol**: find the L-shaped finder corner (bottom-left) and orientation markers.
|
|
215
|
+
2. **Rectify geometry**: use the finder bars and timing strips to establish the module grid (perspective correction, skew correction).
|
|
216
|
+
3. **Calibrate colors**: sample the four anchor cells at (1,1), (1,6), (6,1), (6,6) to build color classification thresholds for this scan.
|
|
217
|
+
4. **Classify cells**: for each of the 32 data cells, classify the sampled color as BLACK/RED/GREEN/BLUE using the calibrated thresholds.
|
|
218
|
+
5. **Unpack bytes**: convert the 32 dibits back to 8 bytes.
|
|
219
|
+
6. **RS decode**: compute syndromes. If nonzero, apply Berlekamp-Massey + Chien search + Forney algorithm to locate and correct up to 2 symbol errors.
|
|
220
|
+
7. **Return payload**: the first 4 bytes of the corrected codeword are the user data.
|
|
221
|
+
|
|
222
|
+
### 6.1 Color Calibration Detail
|
|
223
|
+
|
|
224
|
+
The decoder builds a calibration matrix from the four anchors:
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
reference = {
|
|
228
|
+
BLACK: sample(1, 1), # known-black pixel
|
|
229
|
+
RED: sample(1, 6), # known-red pixel
|
|
230
|
+
GREEN: sample(6, 1), # known-green pixel
|
|
231
|
+
BLUE: sample(6, 6), # known-blue pixel
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Each unknown cell is classified to the nearest anchor in RGB color space (Euclidean distance, or a simplified channel-dominance heuristic). This makes the format robust to uniform color shifts (e.g., tungsten lighting making everything yellowish).
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## 7. Constraints and Limitations
|
|
240
|
+
|
|
241
|
+
- **Payload**: 4 bytes (32 bits). Sufficient for a serial number, short ID, timestamp, or small integer.
|
|
242
|
+
- **Color printing required**: cannot be produced on a monochrome printer.
|
|
243
|
+
- **Scanner requirements**: color camera or color sensor; standard laser barcode scanners cannot read ChessMatrix.
|
|
244
|
+
- **Print resolution**: each cell must be large enough to be unambiguously colored (~0.5mm minimum per cell in practice, so minimum symbol size ~4mm × 4mm).
|
|
245
|
+
- **No rotation invariance built in**: the finder pattern establishes a single canonical orientation. A 180° rotated symbol will fail to decode. (A future extension could add a rotation indicator cell.)
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## 8. Python Implementation
|
|
250
|
+
|
|
251
|
+
Install: `pip install chessmatrix` (add `[render]` extra for PNG output)
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from chessmatrix import encode, decode, render_image, render_ascii
|
|
255
|
+
|
|
256
|
+
data = b'\xDE\xAD\xBE\xEF'
|
|
257
|
+
grid = encode(data) # List[List[int]], 8×8, values 0-3 or -1
|
|
258
|
+
render_ascii(grid) # colored output in terminal
|
|
259
|
+
render_image(grid, 'code.png', cell_size=60) # requires Pillow
|
|
260
|
+
|
|
261
|
+
recovered = decode(grid)
|
|
262
|
+
assert recovered == data
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**API**
|
|
266
|
+
|
|
267
|
+
| Function | Description |
|
|
268
|
+
|---|---|
|
|
269
|
+
| `encode(data: bytes) -> Grid` | Encode 4 bytes → 8×8 color grid |
|
|
270
|
+
| `decode(grid: Grid) -> bytes` | Decode grid → 4 bytes (RS error correction applied) |
|
|
271
|
+
| `render_image(grid, path, cell_size=40)` | Save PNG (requires Pillow) |
|
|
272
|
+
| `render_ascii(grid)` | Print colored ASCII art to terminal |
|
|
273
|
+
| `CorrectionError` | Raised when RS decoding cannot correct errors |
|
|
274
|
+
|
|
275
|
+
`Grid` is `List[List[int]]` — an 8×8 nested list of color values (0–3, or -1 for white structural cells).
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## 9. JavaScript Implementation
|
|
280
|
+
|
|
281
|
+
Install: `npm install chessmatrix`
|
|
282
|
+
|
|
283
|
+
```javascript
|
|
284
|
+
import { lettersToBytes, buildGrid, renderGrid } from 'chessmatrix';
|
|
285
|
+
|
|
286
|
+
// Encode 4 bytes onto a canvas
|
|
287
|
+
const bytes = new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF]);
|
|
288
|
+
const grid = buildGrid(bytes);
|
|
289
|
+
renderGrid(grid, document.getElementById('canvas'), 40); // 40px per cell
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
```javascript
|
|
293
|
+
import { bytesToLetters, lettersToBytes, buildGrid } from 'chessmatrix';
|
|
294
|
+
|
|
295
|
+
// 6-letter convenience codec (5-bit packing, A=0…Z=25)
|
|
296
|
+
const bytes = lettersToBytes('HELLO!'.replace(/[^A-Z]/gi, 'A')); // Uint8Array(4)
|
|
297
|
+
const letters = bytesToLetters(bytes); // → 'HELLOA'
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
import { ChessMatrixScanner } from 'chessmatrix';
|
|
302
|
+
|
|
303
|
+
// Camera scanner (browser only — requires getUserMedia)
|
|
304
|
+
const scanner = new ChessMatrixScanner({
|
|
305
|
+
videoEl: document.getElementById('video'),
|
|
306
|
+
canvasEl: document.getElementById('canvas'),
|
|
307
|
+
onDecode: (letters) => console.log('Scanned:', letters),
|
|
308
|
+
onStatus: (msg, state) => console.log(state, msg),
|
|
309
|
+
});
|
|
310
|
+
await scanner.start();
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**API**
|
|
314
|
+
|
|
315
|
+
| Export | Description |
|
|
316
|
+
|---|---|
|
|
317
|
+
| `buildGrid(bytes: Uint8Array) → Int8Array` | Encode 4 bytes → 64-cell flat grid (row-major) |
|
|
318
|
+
| `renderGrid(grid, canvas, cellSize?)` | Draw grid on an HTMLCanvasElement |
|
|
319
|
+
| `lettersToBytes(str) → Uint8Array` | 6-letter string → 4 bytes (5-bit packing) |
|
|
320
|
+
| `bytesToLetters(bytes) → string` | 4 bytes → 6-letter string |
|
|
321
|
+
| `ChessMatrixScanner` | Semi-guided camera scanner class (browser only) |
|
|
322
|
+
| `CorrectionError` | Thrown when RS decoding cannot correct errors |
|
|
323
|
+
| `DATA_CELLS` | `[row, col][]` — the 32 data cell positions in read order |
|
|
324
|
+
|
|
325
|
+
> `renderGrid` and `ChessMatrixScanner` require a browser environment (Canvas API / `getUserMedia`). The codec functions (`buildGrid`, `lettersToBytes`, etc.) are pure JavaScript and work in Node.js.
|
|
326
|
+
|
|
327
|
+
**Browser (CDN, no bundler)**
|
|
328
|
+
```html
|
|
329
|
+
<script type="module">
|
|
330
|
+
import { buildGrid, renderGrid } from
|
|
331
|
+
'https://cdn.jsdelivr.net/npm/chessmatrix/chessmatrix.js';
|
|
332
|
+
</script>
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## 10. License
|
|
338
|
+
|
|
339
|
+
ChessMatrix is an open specification. Implement freely.
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# ChessMatrix
|
|
2
|
+
|
|
3
|
+
**A 4-color 8×8 2D barcode format.**
|
|
4
|
+
|
|
5
|
+
ChessMatrix is a compact, machine-readable 2D barcode that fits in an 8×8 cell grid — the same footprint as a chessboard — by using four colors (black, red, green, blue) instead of the traditional two, yielding 2 bits of information per cell. It is designed as a minimal, scannable symbol for applications where even the smallest standard formats (DataMatrix 10×10, Micro QR 11×11) are too large.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
**Python**
|
|
10
|
+
```bash
|
|
11
|
+
pip install chessmatrix
|
|
12
|
+
# render_image() also requires Pillow:
|
|
13
|
+
pip install "chessmatrix[render]"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**JavaScript / Node.js**
|
|
17
|
+
```bash
|
|
18
|
+
npm install chessmatrix
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Browser (no bundler)**
|
|
22
|
+
```html
|
|
23
|
+
<script type="module">
|
|
24
|
+
import { buildGrid, renderGrid } from 'https://cdn.jsdelivr.net/npm/chessmatrix/chessmatrix.js';
|
|
25
|
+
</script>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 1. Motivation
|
|
31
|
+
|
|
32
|
+
The smallest standard 2D barcodes — DataMatrix 10×10 and Micro QR Version 1 (11×11) — cannot physically fit in an 8×8 cell footprint. ChessMatrix fills this gap by encoding 2 bits per cell via color rather than 1 bit per cell via luminance, achieving a useful payload within the 8×8 constraint.
|
|
33
|
+
|
|
34
|
+
**Net payload: 4 bytes (32 bits) per symbol**, protected by Reed-Solomon error correction.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 2. Symbol Structure
|
|
39
|
+
|
|
40
|
+
The symbol is an 8×8 grid of colored cells, each one of:
|
|
41
|
+
|
|
42
|
+
| Symbol | Value | RGB (recommended) |
|
|
43
|
+
|--------|-------|-------------------|
|
|
44
|
+
| BLACK | `0b00` | `#0A0A0A` |
|
|
45
|
+
| RED | `0b01` | `#DC2828` |
|
|
46
|
+
| GREEN | `0b10` | `#28B428` |
|
|
47
|
+
| BLUE | `0b11` | `#2828DC` |
|
|
48
|
+
|
|
49
|
+
### 2.1 Grid Zones
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
Col: 0 1 2 3 4 5 6 7
|
|
53
|
+
┌────┬────┬────┬────┬────┬────┬────┬────┐
|
|
54
|
+
R0 │ T │ t │ T │ t │ T │ t │ T │ t │ ← Top timing (alternating K/W)
|
|
55
|
+
├────┼────┼────┼────┼────┼────┼────┼────┤
|
|
56
|
+
R1 │ F │[K] │ D │ D │ D │ D │[R] │ tm │ ← [K] = BLACK anchor
|
|
57
|
+
├────┼────┼────┼────┼────┼────┼────┼────┤
|
|
58
|
+
R2 │ F │ D │ D │ D │ D │ D │ D │ tm │
|
|
59
|
+
├────┼────┼────┼────┼────┼────┼────┼────┤
|
|
60
|
+
R3 │ F │ D │ D │ D │ D │ D │ D │ tm │
|
|
61
|
+
├────┼────┼────┼────┼────┼────┼────┼────┤
|
|
62
|
+
R4 │ F │ D │ D │ D │ D │ D │ D │ tm │
|
|
63
|
+
├────┼────┼────┼────┼────┼────┼────┼────┤
|
|
64
|
+
R5 │ F │ D │ D │ D │ D │ D │ D │ tm │
|
|
65
|
+
├────┼────┼────┼────┼────┼────┼────┼────┤
|
|
66
|
+
R6 │ F │[G] │ D │ D │ D │ D │[B] │ tm │ ← [G] = GREEN anchor, [B] = BLUE anchor
|
|
67
|
+
├────┼────┼────┼────┼────┼────┼────┼────┤
|
|
68
|
+
R7 │ F │ F │ F │ F │ F │ F │ F │ F │ ← Bottom finder (all BLACK)
|
|
69
|
+
└────┴────┴────┴────┴────┴────┴────┴────┘
|
|
70
|
+
|
|
71
|
+
F = Finder bar (BLACK)
|
|
72
|
+
T/t = Timing cell (T=BLACK, t=WHITE, alternating)
|
|
73
|
+
tm = Right timing column (alternating K/W, row0=WHITE, row1=BLACK, ...)
|
|
74
|
+
[K][R][G][B] = Color calibration anchors
|
|
75
|
+
D = Data cell (2-bit color-encoded)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 2.2 Border Cells (28 cells)
|
|
79
|
+
|
|
80
|
+
**Left finder bar** — column 0, rows 0–7: all **BLACK**
|
|
81
|
+
|
|
82
|
+
**Bottom finder bar** — row 7, cols 0–7: all **BLACK**
|
|
83
|
+
|
|
84
|
+
**Top timing strip** — row 0, cols 0–7:
|
|
85
|
+
- Even columns (0, 2, 4, 6): **BLACK**
|
|
86
|
+
- Odd columns (1, 3, 5, 7): **WHITE**
|
|
87
|
+
|
|
88
|
+
**Right timing strip** — col 7, rows 0–7:
|
|
89
|
+
- Even rows (0, 2, 4, 6): **WHITE**
|
|
90
|
+
- Odd rows (1, 3, 5, 7): **BLACK**
|
|
91
|
+
|
|
92
|
+
The timing strips continue to alternate even at their shared corners: (0,7) is WHITE (even column dominates in top timing; right timing confirms even row → WHITE).
|
|
93
|
+
|
|
94
|
+
### 2.3 Color Calibration Anchors (4 cells)
|
|
95
|
+
|
|
96
|
+
Located at the four corners of the 6×6 interior:
|
|
97
|
+
|
|
98
|
+
| Position | Color | Purpose |
|
|
99
|
+
|----------|--------|----------------------------------|
|
|
100
|
+
| (row=1, col=1) | **BLACK** | Dark reference |
|
|
101
|
+
| (row=1, col=6) | **RED** | Red channel reference |
|
|
102
|
+
| (row=6, col=1) | **GREEN** | Green channel reference |
|
|
103
|
+
| (row=6, col=6) | **BLUE** | Blue channel reference |
|
|
104
|
+
|
|
105
|
+
A decoder samples these four cells first to calibrate its color discrimination thresholds before decoding any data cells. This compensates for lighting color temperature, ink variation, and camera white-balance errors.
|
|
106
|
+
|
|
107
|
+
### 2.4 Data Cells (32 cells)
|
|
108
|
+
|
|
109
|
+
The remaining interior cells, read in row-major order (top to bottom, left to right), skipping the four anchors:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
Row 1: (1,2) (1,3) (1,4) (1,5) — 4 cells
|
|
113
|
+
Row 2: (2,1) (2,2) (2,3) (2,4) (2,5) (2,6) — 6 cells
|
|
114
|
+
Row 3: (3,1) (3,2) (3,3) (3,4) (3,5) (3,6) — 6 cells
|
|
115
|
+
Row 4: (4,1) (4,2) (4,3) (4,4) (4,5) (4,6) — 6 cells
|
|
116
|
+
Row 5: (5,1) (5,2) (5,3) (5,4) (5,5) (5,6) — 6 cells
|
|
117
|
+
Row 6: (6,2) (6,3) (6,4) (6,5) — 4 cells
|
|
118
|
+
TOTAL = 32 cells
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 3. Data Encoding
|
|
124
|
+
|
|
125
|
+
### 3.1 Color-to-Bit Mapping
|
|
126
|
+
|
|
127
|
+
Each cell encodes exactly 2 bits:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
BLACK = 0b00 = 0
|
|
131
|
+
RED = 0b01 = 1
|
|
132
|
+
GREEN = 0b10 = 2
|
|
133
|
+
BLUE = 0b11 = 3
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 3.2 Bit Packing
|
|
137
|
+
|
|
138
|
+
32 cells × 2 bits = **64 bits = 8 bytes** raw capacity.
|
|
139
|
+
|
|
140
|
+
Cells are packed MSB-first, 4 cells per byte:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
Byte 0: cells[0] bits[7:6] | cells[1] bits[5:4] | cells[2] bits[3:2] | cells[3] bits[1:0]
|
|
144
|
+
Byte 1: cells[4] bits[7:6] | cells[5] bits[5:4] | cells[6] bits[3:2] | cells[7] bits[1:0]
|
|
145
|
+
...
|
|
146
|
+
Byte 7: cells[28]...cells[31]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
To decode byte `b` from cells `4b`, `4b+1`, `4b+2`, `4b+3`:
|
|
150
|
+
```
|
|
151
|
+
byte_val = (color[4b] << 6) | (color[4b+1] << 4) | (color[4b+2] << 2) | color[4b+3]
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 4. Reed-Solomon Error Correction
|
|
157
|
+
|
|
158
|
+
ChessMatrix uses a **RS(8, 4)** code over GF(2⁸):
|
|
159
|
+
|
|
160
|
+
| Parameter | Value |
|
|
161
|
+
|-----------|-------|
|
|
162
|
+
| Total codeword length | 8 symbols (bytes) |
|
|
163
|
+
| Data symbols | 4 bytes |
|
|
164
|
+
| Parity symbols | 4 bytes |
|
|
165
|
+
| Maximum correctable errors | **2 symbol errors** |
|
|
166
|
+
| Maximum detectable errors | 4 symbol errors |
|
|
167
|
+
| Primitive polynomial | x⁸ + x⁴ + x³ + x² + 1 (`0x11D`) |
|
|
168
|
+
| Generator roots | α⁰, α¹, α², α³ (where α = 2) |
|
|
169
|
+
|
|
170
|
+
The 4 data bytes occupy the **first 4 bytes** of the codeword; the 4 parity bytes occupy bytes 5–8. The codeword is stored across the 32 data cells in byte order (data bytes first, then parity bytes).
|
|
171
|
+
|
|
172
|
+
### 4.1 Error Budget Rationale
|
|
173
|
+
|
|
174
|
+
Color discrimination under real-world conditions is less reliable than luminance discrimination. A dirty scan, off-angle lighting, or color-shifted camera can misread an entire cell. Allocating 50% of capacity to error correction (vs. ~25% in standard DataMatrix 10×10) reflects this elevated error rate. RS(8,4) can correct any 2 completely wrong cells, which covers most single-color-channel failures.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 5. Encoding Algorithm
|
|
179
|
+
|
|
180
|
+
1. **Validate input**: data must be exactly 4 bytes.
|
|
181
|
+
2. **RS encode**: compute 4 parity bytes via polynomial long division over GF(2⁸), producing an 8-byte codeword `[d0, d1, d2, d3, p0, p1, p2, p3]`.
|
|
182
|
+
3. **Pack into cells**: convert the 8 bytes to 32 dibits (2-bit values), one per data cell.
|
|
183
|
+
4. **Build grid**: initialize the 8×8 grid with the border pattern (finder + timing) and calibration anchors, then write the 32 data cells.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 6. Decoding Algorithm
|
|
188
|
+
|
|
189
|
+
1. **Locate symbol**: find the L-shaped finder corner (bottom-left) and orientation markers.
|
|
190
|
+
2. **Rectify geometry**: use the finder bars and timing strips to establish the module grid (perspective correction, skew correction).
|
|
191
|
+
3. **Calibrate colors**: sample the four anchor cells at (1,1), (1,6), (6,1), (6,6) to build color classification thresholds for this scan.
|
|
192
|
+
4. **Classify cells**: for each of the 32 data cells, classify the sampled color as BLACK/RED/GREEN/BLUE using the calibrated thresholds.
|
|
193
|
+
5. **Unpack bytes**: convert the 32 dibits back to 8 bytes.
|
|
194
|
+
6. **RS decode**: compute syndromes. If nonzero, apply Berlekamp-Massey + Chien search + Forney algorithm to locate and correct up to 2 symbol errors.
|
|
195
|
+
7. **Return payload**: the first 4 bytes of the corrected codeword are the user data.
|
|
196
|
+
|
|
197
|
+
### 6.1 Color Calibration Detail
|
|
198
|
+
|
|
199
|
+
The decoder builds a calibration matrix from the four anchors:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
reference = {
|
|
203
|
+
BLACK: sample(1, 1), # known-black pixel
|
|
204
|
+
RED: sample(1, 6), # known-red pixel
|
|
205
|
+
GREEN: sample(6, 1), # known-green pixel
|
|
206
|
+
BLUE: sample(6, 6), # known-blue pixel
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Each unknown cell is classified to the nearest anchor in RGB color space (Euclidean distance, or a simplified channel-dominance heuristic). This makes the format robust to uniform color shifts (e.g., tungsten lighting making everything yellowish).
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 7. Constraints and Limitations
|
|
215
|
+
|
|
216
|
+
- **Payload**: 4 bytes (32 bits). Sufficient for a serial number, short ID, timestamp, or small integer.
|
|
217
|
+
- **Color printing required**: cannot be produced on a monochrome printer.
|
|
218
|
+
- **Scanner requirements**: color camera or color sensor; standard laser barcode scanners cannot read ChessMatrix.
|
|
219
|
+
- **Print resolution**: each cell must be large enough to be unambiguously colored (~0.5mm minimum per cell in practice, so minimum symbol size ~4mm × 4mm).
|
|
220
|
+
- **No rotation invariance built in**: the finder pattern establishes a single canonical orientation. A 180° rotated symbol will fail to decode. (A future extension could add a rotation indicator cell.)
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 8. Python Implementation
|
|
225
|
+
|
|
226
|
+
Install: `pip install chessmatrix` (add `[render]` extra for PNG output)
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
from chessmatrix import encode, decode, render_image, render_ascii
|
|
230
|
+
|
|
231
|
+
data = b'\xDE\xAD\xBE\xEF'
|
|
232
|
+
grid = encode(data) # List[List[int]], 8×8, values 0-3 or -1
|
|
233
|
+
render_ascii(grid) # colored output in terminal
|
|
234
|
+
render_image(grid, 'code.png', cell_size=60) # requires Pillow
|
|
235
|
+
|
|
236
|
+
recovered = decode(grid)
|
|
237
|
+
assert recovered == data
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**API**
|
|
241
|
+
|
|
242
|
+
| Function | Description |
|
|
243
|
+
|---|---|
|
|
244
|
+
| `encode(data: bytes) -> Grid` | Encode 4 bytes → 8×8 color grid |
|
|
245
|
+
| `decode(grid: Grid) -> bytes` | Decode grid → 4 bytes (RS error correction applied) |
|
|
246
|
+
| `render_image(grid, path, cell_size=40)` | Save PNG (requires Pillow) |
|
|
247
|
+
| `render_ascii(grid)` | Print colored ASCII art to terminal |
|
|
248
|
+
| `CorrectionError` | Raised when RS decoding cannot correct errors |
|
|
249
|
+
|
|
250
|
+
`Grid` is `List[List[int]]` — an 8×8 nested list of color values (0–3, or -1 for white structural cells).
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## 9. JavaScript Implementation
|
|
255
|
+
|
|
256
|
+
Install: `npm install chessmatrix`
|
|
257
|
+
|
|
258
|
+
```javascript
|
|
259
|
+
import { lettersToBytes, buildGrid, renderGrid } from 'chessmatrix';
|
|
260
|
+
|
|
261
|
+
// Encode 4 bytes onto a canvas
|
|
262
|
+
const bytes = new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF]);
|
|
263
|
+
const grid = buildGrid(bytes);
|
|
264
|
+
renderGrid(grid, document.getElementById('canvas'), 40); // 40px per cell
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
import { bytesToLetters, lettersToBytes, buildGrid } from 'chessmatrix';
|
|
269
|
+
|
|
270
|
+
// 6-letter convenience codec (5-bit packing, A=0…Z=25)
|
|
271
|
+
const bytes = lettersToBytes('HELLO!'.replace(/[^A-Z]/gi, 'A')); // Uint8Array(4)
|
|
272
|
+
const letters = bytesToLetters(bytes); // → 'HELLOA'
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
```javascript
|
|
276
|
+
import { ChessMatrixScanner } from 'chessmatrix';
|
|
277
|
+
|
|
278
|
+
// Camera scanner (browser only — requires getUserMedia)
|
|
279
|
+
const scanner = new ChessMatrixScanner({
|
|
280
|
+
videoEl: document.getElementById('video'),
|
|
281
|
+
canvasEl: document.getElementById('canvas'),
|
|
282
|
+
onDecode: (letters) => console.log('Scanned:', letters),
|
|
283
|
+
onStatus: (msg, state) => console.log(state, msg),
|
|
284
|
+
});
|
|
285
|
+
await scanner.start();
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**API**
|
|
289
|
+
|
|
290
|
+
| Export | Description |
|
|
291
|
+
|---|---|
|
|
292
|
+
| `buildGrid(bytes: Uint8Array) → Int8Array` | Encode 4 bytes → 64-cell flat grid (row-major) |
|
|
293
|
+
| `renderGrid(grid, canvas, cellSize?)` | Draw grid on an HTMLCanvasElement |
|
|
294
|
+
| `lettersToBytes(str) → Uint8Array` | 6-letter string → 4 bytes (5-bit packing) |
|
|
295
|
+
| `bytesToLetters(bytes) → string` | 4 bytes → 6-letter string |
|
|
296
|
+
| `ChessMatrixScanner` | Semi-guided camera scanner class (browser only) |
|
|
297
|
+
| `CorrectionError` | Thrown when RS decoding cannot correct errors |
|
|
298
|
+
| `DATA_CELLS` | `[row, col][]` — the 32 data cell positions in read order |
|
|
299
|
+
|
|
300
|
+
> `renderGrid` and `ChessMatrixScanner` require a browser environment (Canvas API / `getUserMedia`). The codec functions (`buildGrid`, `lettersToBytes`, etc.) are pure JavaScript and work in Node.js.
|
|
301
|
+
|
|
302
|
+
**Browser (CDN, no bundler)**
|
|
303
|
+
```html
|
|
304
|
+
<script type="module">
|
|
305
|
+
import { buildGrid, renderGrid } from
|
|
306
|
+
'https://cdn.jsdelivr.net/npm/chessmatrix/chessmatrix.js';
|
|
307
|
+
</script>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## 10. License
|
|
313
|
+
|
|
314
|
+
ChessMatrix is an open specification. Implement freely.
|