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 value is the same for both tree types (§3a) — only its drawing differs. The tubular tree draws each value as strokes radiating from the cell centre, which stay readable even when a cell is small or slightly warped:
The diamond tree encodes the very same 2-bit value as a half-fill of a rotated square: the two bits directly drive the top and bottom halves — top = bit 1, bottom = bit 0. So the value reads off the fill pattern with no ray voting at all:
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. - Edge rows carry no data. The topmost and bottommost leaf rows are excluded from the payload — data runs from the second leaf row down to the second-to-last. Those outermost rows are the most exposed to damage and deformation, so the encoder fills them with pseudo-random noise instead of message symbols, and every decoder skips them.
- 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.
3a. Tree types & type detection
The same 2-bit payload can be drawn as one of two tree types, and both decode to identical content — the geometry and data layout are shared; only the glyph skin differs:
- Tubular — each leaf is a stroke-and-dot glyph (the four symbols read by ray classification). The four fiducials sit at the full inscribed radius, on a symmetric cross: horizontal and vertical arms are equal length.
- Diamond — each leaf is a rotated diamond whose value shows as top/bottom half-fills (
0empty,1bottom,2top,3solid →top = bit1,bottom = bit0). The lattice is compacted toward the trunk into a dense mosaic, and the fiducial cross is asymmetric: the vertical arms sit at0.95·Rfrom the node, the horizontal arms at0.6·R.
A scanner tells them apart by that asymmetry. After locating the circle-in-diamond node and the two vertical fiducials, it measures the horizontal fiducials: when the horizontal-to-vertical distance ratio is ≈ 1.0 the code is tubular; when it is ≈ 0.63 (0.6 / 0.95) the code is diamond. It then forks into the matching read — ray classification for tubular strokes, top/bottom half-fill sampling at the compacted diamond positions for diamond — over the same leaf set.
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 — excluding the top and bottom leaf rows, which carry noise, not data — 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 60 B at the default N=32, 165 B at 48, 270 B at 64, 673 B at 96, and ~1 KB at N=120 (a little lower than a naïve leaf count because the outermost rows carry no data, §2); the auto-sizer starts at N=32 and sizes below 24 switch to the mini format (§10). 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, and reads both tree types from the same pipeline:
- Foreground. A threshold yields a binary mask — a local adaptive one for thin tubular strokes, and a global (Otsu-style) solid mask for the dense diamond mosaic whose filled halves an edge-detector would miss. Enclosed regions are hole-filled so solid shapes become blobs.
- Node. The anchor is found by distance transform: the code's one "circle-in-diamond" — a filled circle sitting inside an empty rotated square — is the distinctive local maximum. (A plain "largest blob" test fails here, because a dense diamond crown is itself a large round blob.)
- Fiducials & tree type. Around the node, the two vertical fiducials fix the axis; the horizontal pair then reveals the type by radius ratio — ≈1.0 → tubular, ≈0.63 → diamond (§3a). The four give a homography that removes perspective;
Nis brute-forced within a small window and confirmed by CRC. Tubular and diamond are tried independently — a diamond's horizontal fiducials sit at 0.6·R, so the tubular snap failing must not skip the diamond attempt. - Frame / centre fallback. For a clean export the code is the centred square; with no usable fiducials the glyph pitch gives the cell size and the decoder brute-forces
Nanchored on the node. Both fallbacks also try both tree types. - Classification. Over the same leaf set (edge rows excluded, §2): tubular — six rays from the cell centre vote horizontal vs. diagonal and full vs. half; diamond — the top and bottom halves of each diamond are sampled for fill, giving
top·2 + bottom. Both recover the 2-bit value. - Recovery. For
N ≥ 32, Reed–Solomon repairs misreads and the CRC-32 validates; the header says how to unpack. For a mini code (N < 24, §10) there is no header — the bytes are a length, a base-36 payload and a CRC-8, decoded straight to atreex.cc/…link.
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. Mini format — treex.cc short links
Below N = 24 a tree is too small to carry a full error-corrected payload, so it switches to the mini format: it always encodes a short a–z 0–9 code that resolves to treex.cc/{code}. This is selected automatically when the size drops under 24 (in practice the 16×16 tree; 8×8 has no data cells).
- No header. The type is fixed (a treex.cc link), so the four-byte header is dropped. The message is just
[length(1)] + base-36 packed code + CRC-8. - Lighter checksum. Base-36 packs the code at the same density as alphanumeric mode (11 bits per two characters); a single CRC-8 (polynomial
0x07) replaces the 4-byte CRC-32, and there is no Reed–Solomon — every cell must read correctly, which a tiny code makes easy. - Capacity. A 16×16 code holds about 14 characters (tubular) or 13 (diamond) — enough for a short slug whose destination can be changed after printing.
Both tree types support the mini format, and scanners read it directly: at N < 24 the decoder skips the header/RS path and returns treex.cc/{code}.
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 your text plus an optional --tree tubular|diamond to choose the glyph style; the decoder reads either type automatically and prints the content with its full breakdown (including which tree type it found).
$ python treexcode_encode.py "https://treexcode.com" -o code.png Saved code.png (32x32 tubular tree, type=url, encoding=UTF-8, ecc=medium, 140/336 symbols) $ python treexcode_encode.py "https://treexcode.com" --tree diamond -o code.png Saved code.png (32x32 diamond tree, type=url, encoding=UTF-8, ecc=medium, 140/302 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.