A Claycode stores bits in what is inside what, not in a grid. Shapes, sizes and positions are free. That makes it one of the few machine-readable codes you can draw with a pen.
Found in kpayuk at public_html/kwt/tiptop/scan_claycode.html (served as /scan under kwt/tiptop). It is a camera scanner that imports the Claycode library from kpos.uk/claycode/webapp/common/ and opens tiptop.ninja/shortcode/<hex> when a code decodes to hex. Everything below follows that library's own decoder.
Every separate patch of ink or paper is a region. A region sitting directly inside another is its child. Give each region a number:
value = 1 + the sum of the squares of the values directly inside it.
An empty region is worth 1.
Work out the value of the outermost region you care about, write it in binary, and drop the leading 1. What is left is the bit string. To encode, go the other way: put a 1 in front of your bits, read that as a number N, then fill a region with things whose values squared add up to N − 1.
Squares add up in any order, so the left-to-right order of shapes means nothing. Only the nesting counts. And most numbers can be split into squares in several ways, so one bit string has many valid drawings.
A pen line drawn as a closed loop makes two regions: the ink of the line, and the paper it encloses. A short dash makes one. Use a dash rather than a dot: a dot looks like one region but can scan as two (see the rules below). Inside a box frame, the paper is your outer region, and these are the pieces you place in it:
A loop split into cells counts as one ink region holding several paper regions, which is why the strokes inside it must touch the loop. Anything placed inside a cell nests one level deeper, and you can keep going.
1 0110 in binary is 22.Check by reading it back: the frame's paper is 1 + 4² + 2² + 1² = 22 = 10110, drop the leading 1, and you have 0110.
21 can also be 9 + 9 + 1 + 1 + 1 (two halved loops and three dashes), or 4 + 4 + 4 + 4 + 4 + 1 (five empty loops and a dash), or twenty-one ones (a page of tally marks). All of these decode as 0110. You choose the split that is easiest or nicest to draw.
1010 gives 26, and 26 − 1 = 25 = 5². So the frame holds a single thing worth 5, which is a loop with a dash inside. 11 gives 7, and 6 = 4 + 1 + 1: an empty loop and two dashes.
Four bits always land between 16 and 31, so a frame with at most four small pieces covers every one of them. These use the fewest regions. Drawn one after another, they work as a hand alphabet for hex digits.
Filling a shape in does change the encoding. A filled blob with nothing inside is one region, worth 1. The same shape drawn as an outline is two regions, the ink line and the paper inside it, so it is worth 2 as a loop. That is why this page counts every pen loop as two regions.
Generated Claycodes, like the ones on tiptop labels, are drawn with solid fills in alternating black and white. Look closely, though. Every region is a thin band wrapped around the regions inside it, and only the innermost ones are small solid blobs. There are no big flat areas.
The scanner adds a twist. It compares each pixel with its neighbourhood, so the middle of a large solid area reads as paper. I tested a filled disc inside a box at phone-frame resolution (1728 px). Up to about 40 px across, roughly 1/40 of the frame, it read as one region. At 80 px and wider it read exactly like an outlined loop, worth 2. A Claycode from the kpos generator (“HELLO CLAYCODE”) passed the same test at three of four distances. It failed only when it filled three quarters of the frame.
So when drawing by hand, only fill small marks, or better, use dashes. Read any large filled shape as if it were an outline. Never rely on filling something in to change its value.
No, and the reason is in the library. The decoder only accepts a bit string whose last 16 bits are a valid CRC-16 (polynomial 0x4599, the CAN one) of the rest. Then the tiptop page turns the payload into text or hex a whole byte at a time. A plain 0110 fails the CRC, and even 0110 plus its CRC is under one byte, so it shows nothing.
The smallest drawing the stock scanner would display is one byte plus its CRC. Here are both, with the fewest regions I could find. Neither is out of reach for a pen.
Chaining several small drawings for a longer string works mathematically. Each frame decodes on its own. With the stock scanner, each one carries its own 16-bit CRC, so a 4-bit chunk costs 20 bits of drawing. A hand-mode scanner could drop the CRC, or use a 4-bit one, and reserve a couple of bits per frame as a sequence number so the order can be checked. That would be a small change to the tiptop scanner, not to the library.
Type bits (or 0x… hex). They are split into chunks, one frame per chunk, each drawn with the fewest regions found.
Every tree on this page was decoded with the Claycode library's own BitTreeConverter and BitsValidator (upstream marcomaida/claycode, webapp/common/bits/). Every drawing was then rendered and placed in a 1728 px camera-sized frame at five sizes, from 22% to 75% of the frame. Each was run through the same OpenCV steps as scan_claycode.html: grayscale, bilateral filter, adaptive mean threshold, contour tree. All 26 static drawings, and the tool's 8-bit + CRC frames, read back with the right bits at every size. These were clean renders, not photos of paper.
The copy the tiptop scanner used (kpos repo, public_html/claycode/webapp/common/) has the same maths as upstream. It adds logging, and one change that lets the scanner pass in a region directly. The scanner at claycode.kpay.uk/scan now carries its own copy of it.
Not checked yet: no drawing has been tried with a real phone camera.
Background: Maida, Crescini, Perronet and Camuffo, Claycode: Stylable and Deformable 2D Scannable Codes (arXiv 2505.08666) · github.com/marcomaida/claycode. The library is MIT with the Commons Clause, so commercial use needs a separate licence. That matters if tiptop goes beyond a pilot.