TreeXcode logo TreeXcode
Open specification

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:

00 · full horizontal01 · full diagonal 10 · half horizontal11 · half diagonal
The four data glyphs. The visual alphabet actually has six shapes — a centre dot and an empty cell as well — but only four carry payload.

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.

Why four, not six? Keeping data on a power-of-two alphabet (2 bits, 4 cells → 1 byte) is what makes error correction work: one misread cell then equals at most one wrong byte, which Reed–Solomon repairs cleanly. A six-symbol alphabet would pack ~29% more raw bits, but a single misread would smear across several bytes and the centre-dot/empty cells are the easiest to confuse with a neighbour's stroke — so the dot and empty shapes are reserved for structure and decoration, not payload.

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:

crown — data roots — data node trunk ornament
Functional zones of a TreeXcode. Shaded green bands hold payload leaves; the trunk carries the decorative background; four black diamonds are fiducials; the node marks the centre and the up/down axis.

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 frame is optional. The generator always emits the four diamonds because they enable angled and hand-held scanning. A code can be produced without them — it is still fully decodable head-on (the scanner falls back to the node anchor plus the lattice pitch), but perspective correction is no longer possible, so reading under a tilt will suffer.

4. Automatic encoding

The encoder picks the densest mode that fits your content — you never choose it. This mirrors the QR "modes" idea:

ModeAlphabetBits / charChosen when
Numeric0–9≈ 3.33content is all digits
Alphanumeric0–9 A–Z space $%*+-./:≈ 5.5content is uppercase alnum
UTF-8any Unicode8–32everything else (default)
Latin-1bytes 0–2558available 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.

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:

byte0 byte1 length (2B) RS block 1 RS block 2 …×K chunk (≤251−nsym) CRC32 parity (nsym) byte0 = version(2) · type(4) · compressed(1) · encrypted(1) byte1 = encoding(3) · multipart(1) · ecc-level(4) content is split into K chunks; each chunk gets a CRC32 and its own Reed–Solomon parity. The tail of the tree is filled with noise.
The 4-byte cleartext header, then K independent Reed–Solomon blocks — each carrying a chunk of the content, a CRC32 and its own parity.

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:

LevelParity bytesRepairs up to
0 — none0detection only (CRC)
1 — low84 byte errors
2 — medium (interface default)168 byte errors
3 — high3216 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:

  1. 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.
  2. Components. Connected-component labelling produces blobs with area, centroid and solidity.
  3. Node. The largest roundish blob is the anchor O.
  4. Fiducials. If four diamond blobs forming a symmetric cross are found, they yield a homography that removes perspective; the size N is recovered from the lattice pitch and refined by grid-snapping. This is the path used for photos and live camera at an angle.
  5. 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 N and accepts the value whose CRC checks out. This reads head-on codes even without a frame.
  6. Classification. For each leaf, six rays from the cell centre vote horizontal vs. diagonal and full vs. half — recovering the 2-bit value.
  7. 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:

Because the decoder regenerates the leaf set and the marker positions purely from 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.

Dynamic short-link creation is not enabled in this release. The type is reserved in the format and scanners already recognise it; generation will open in a future update.

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.