How TreeXcode works
A TreeXcode stores data exactly like a QR code — 2-bit symbols packed into a grid, protected by Reed–Solomon error correction — but the grid is grown inside a circuit-tree silhouette. This page documents the full format so anyone can build compatible generators and scanners.
1. Symbol alphabet — 2 bits per cell
Every data cell carries one of four distinguishable glyphs, so each cell encodes exactly 2 bits. Four cells therefore encode one byte. The glyphs are drawn as strokes radiating from the cell centre, which keeps them readable even when a cell is small or slightly warped:
Which physical tile is drawn for a given 2-bit value depends on the cell's quadrant (upper-left, upper-right, lower-left, lower-right). Each quadrant uses a mirror-consistent set of tiles so the tree stays visually symmetric, yet the glyph class — horizontal/diagonal, full/half — is what a scanner reads. Classification is therefore colour-independent: only the stroke geometry matters.
2. Tree geometry & scaling
A code of size N is an N×N lattice. Everything is expressed as a fraction of N, so a code scales to any resolution without a lookup table. With the field centre c = N/2:
- Inscribed disk radius
Rz = 0.47·N— all tree content lives inside it. - Crown band: rows
y ≤ round(32·(N−1)/72). Root band: rowsy ≥ round(56·(N−1)/72). Data leaves grow in these two bands. - Trunk column half-width
ht = max(1, round(0.045·N))is kept clear of leaves. - The central node sits at
(N/2, round(0.6·N))— deliberately below centre, so the crown reads as "up" and the roots as "down". This resolves 180° orientation ambiguity.
The crown edge can be drawn two ways. The public generator always uses the jagged edge (leaves clipped to a stepped boundary, matching the native look). A smooth edge fills the disk fully and therefore exposes more leaves — a way to densify capacity at the cost of the characteristic silhouette. Both are valid; a scanner reads whichever the encoder chose because the leaf set is regenerated from N and the edge mode is implied by the layout.
3. Node & diamond fiducials
Two structures let a camera locate and rectify the code:
- The node: a filled circle of radius
r = (0.5 + 0.25·max(0, ⌊N/32⌋−1))cells, wrapped in a rotated square (diamond) of side2·(2r)and a short outward edge-marker on the upper-right. The node is the scanner's anchor and its size grows in steps so it stays legible asNgrows. - Four diamond fiducials, inset 2 cells from each side at
(N/2, 2),(N/2, N−2),(2, N/2),(N−2, N/2). Being a symmetric cross, they give four correspondences for a homography — enough to undo perspective when the code is photographed at an angle.
4. Automatic encoding
The encoder picks the densest mode that fits your content — you never choose it. This mirrors the QR "modes" idea:
| Mode | Alphabet | Bits / char | Chosen when |
|---|---|---|---|
| Numeric | 0–9 | ≈ 3.33 | content is all digits |
| Alphanumeric | 0–9 A–Z space $%*+-./: | ≈ 5.5 | content is uppercase alnum |
| UTF-8 | any Unicode | 8–32 | everything else (default) |
| Latin-1 | bytes 0–255 | 8 | available for compact single-byte payloads |
Numeric mode packs three digits into 10 bits (2 digits → 7 bits, 1 → 4); alphanumeric packs two characters into 11 bits (1 → 6). A telephone number of 11 digits is 5 bytes instead of 11. The chosen mode is written into the header so the decoder unpacks the same way.
5. Header & data format
Payload cells are read row by row across the leaf set, four cells per byte. The byte stream is a 4-byte cleartext header followed by K Reed–Solomon blocks:
- version (2 bits) — format revision. Currently
0; the field reserves room for future layouts. - type (4 bits) — content class:
0text,1URL,2TreeX short link,3email,4phone,5SMS,6geo,7Wi-Fi,8contact,9event, and reserved values. - compressed / encrypted flags — reserved (currently 0), so implementers may add transforms without breaking the header.
- encoding (3 bits) — the mode from §4.
- multipart (1 bit) — set when the content spans more than one block (
K > 1). - ecc-level (4 bits) — see §6.
Multipart & capacity. A single Reed–Solomon codeword over GF(256) is capped at 255 bytes, so one block holds at most 251 − nsym content bytes. To use a bigger tree's extra leaves, the content is split into K = ⌈packedLen ÷ (251 − nsym)⌉ blocks, each with its own CRC32 and parity, laid down one after another. Capacity therefore grows with the tree: at medium ECC roughly 168 B at N=48, 280 B at 64, 679 B at 96, and ~1 KB at N=120. The decoder recovers K from the length field and the ECC level, so block boundaries need not be stored. Each chunk's CRC is checked independently; a corrupted header desynchronises the blocks and fails their CRCs, so a bad read is rejected rather than returned. The remaining leaves after the last block are filled with deterministic pseudo-random symbols, so the whole tree always looks complete and the message length is not exposed by a half-empty crown.
6. Error correction & integrity
Data is protected by Reed–Solomon codes over GF(256) (primitive polynomial 0x11d, generator 2, first consecutive root 0 — the same parameters as QR). One wrong cell corresponds to at most one wrong byte, and nsym parity bytes repair up to nsym/2 byte errors per block — so a multipart code (§5) repairs up to nsym/2 errors in each of its K blocks independently, and total correction scales with the tree size. Four levels are defined:
| Level | Parity bytes | Repairs up to |
|---|---|---|
| 0 — none | 0 | detection only (CRC) |
| 1 — low | 8 | 4 byte errors |
| 2 — medium (interface default) | 16 | 8 byte errors |
| 3 — high | 32 | 16 byte errors |
The website always encodes at medium. The other levels are fully part of the format: a third-party generator may emit any level, write it into byte1, and this site's scanners will read it. A 32-bit CRC then confirms the recovered bytes are exactly right; if it fails, the read is rejected rather than returning garbage.
7. The decoding pipeline
Decoding is deliberately colour- and scale-independent:
- Foreground. A local adaptive threshold (mark = a pixel that differs from its local background of any colour or gradient) yields a binary mask. Enclosed regions are hole-filled so solid shapes become blobs.
- Components. Connected-component labelling produces blobs with area, centroid and solidity.
- Node. The largest roundish blob is the anchor
O. - Fiducials. If four diamond blobs forming a symmetric cross are found, they yield a homography that removes perspective; the size
Nis recovered from the lattice pitch and refined by grid-snapping. This is the path used for photos and live camera at an angle. - Centre fallback. With no usable diamonds, the pitch between neighbouring glyphs gives the cell size in pixels; anchored on the node, the decoder brute-forces
Nand accepts the value whose CRC checks out. This reads head-on codes even without a frame. - Classification. For each leaf, six rays from the cell centre vote horizontal vs. diagonal and full vs. half — recovering the 2-bit value.
- Recovery. Reed–Solomon repairs any misread cells, the CRC validates, and the header tells the reader how to unpack the content.
8. Custom backgrounds & coding zones
The trunk ornament between the crown and the roots is purely decorative. It is generated by resampling a reference template, but you may replace it with any artwork of your own — a monogram, a logo, a texture — and the code still scans, provided you respect two rules:
- Do not draw inside the coding zones (the crown and root bands where leaves live) or over the node and the four diamond fiducials, including a small clear margin around each.
- Keep your artwork inside the inscribed disk and away from the trunk column centre line if you want the classic look.
N, it never looks at the ornament. Anyone can therefore design their own tree — brand it, restyle the trunk — and it remains readable by every TreeXcode scanner, exactly like adding a logo to the centre of a QR code.9. Line thickness & render bounds
The generator draws strokes at 3 px on the default preview. For reliable scanning the stroke width should stay between roughly 0.12 and 0.30 of the cell size: thinner and the rays may miss a stroke; thicker and adjacent glyphs bleed together and the ray classifier confuses half with full. Leaves are filled squares at 88 % of the cell; the node circle and diamonds scale with N as defined in §3. Any renderer that honours these bounds produces codes this site can read.
10. Dynamic short links
Content type 2 is the TreeX short link: a compact eight-character code that resolves to a dynamic destination. A short link opens at treex.cc/NNNNNNNN and, equivalently, at treexcode.com/view/NNNNNNNN. Because only eight characters are stored, the tree stays tiny regardless of the final URL, and the destination can be changed after printing.
11. Reference Python scripts
Two self-documenting scripts implement the format above. The encoder bakes in the standard parameters (version 0, automatic encoding, medium error correction, jagged crown, diamond frame) and takes only your text; the decoder takes an image and prints the content with its full breakdown.
$ python treexcode_encode.py "https://treexcode.com" -o code.png Saved code.png (32x32 tree, type=url, encoding=UTF-8, ecc=medium, 180/348 symbols) $ python treexcode_decode.py code.png Content : https://treexcode.com Type : url Version : 0 Encoding: UTF-8 ECC : medium Size : 32x32 (centre)
Requires Python 3 with pillow (encoder) and numpy (decoder). Keep both files in the same folder — they share the layout code.