Photo halftone codes, explained
A photo halftone code is the one style on this site where the picture and the data occupy the same surface. The engine rebuilds the code cell by cell, giving the scanner the part of each cell it needs and the photo the rest. This guide explains that split, the three-tone quantisation that keeps the photo from inventing modules, the measured limits the engine enforces, and what the "Verified scannable" badge proves for a code with a picture woven through it. Every figure below is read from the engine when the page is built, so it cannot disagree with the photo code tool it describes; the rules behind it are on the methodology page, and the other guides cover the sibling styles.
A worked example, rendered by the real engine
The photo preset's own example, produced when this page was built by the same halftone pipeline the tool runs in your browser. The "photo" is a synthetic 240 × 240 pixel radial gradient: no photograph is shipped with the site, and yours never leave your browser either. It encodes a fictional link:
https://example.com/us
Version 3, 29×29 modules, error correction H; 37×37 modules once the 4-module quiet zone is counted; 444×444 pixels as rendered. Scan it from the screen.
Two jobs in every cell
A plain code paints each module as one solid square. The halftone engine samples every data cell on a grid of 3 × 3 subcells — 9 to the cell — and gives the cell two jobs. The centre is a square whose side is 0.5 of the cell's side — 50% of the cell in each direction — drawn last, above everything else, in the pure module colour: the foreground for a dark module, the background for a light one. The engine calls this centre authoritative because decoders sample near the centre of each cell, and whatever they sample there must be the module's true colour. The 8 subcells of the ring around it — every subcell not wholly hidden beneath the centre — carry the photo. Neither grid figure is typed into this page: both are read back from the example's rendered artwork and the centre ratio when the page is built.
The 50% figure is a measured floor, not a design preference. The methodology records the result that fixed it: at 50% of the cell the code decodes; at 40% it does not. The constant is pinned by the test suite, so a change that broke decoding would fail the build.
How the photo becomes three tones
The photo is never drawn as a photo. The engine takes a centred square crop of the image — the shorter side wins — and maps it onto the subcell grid of the whole symbol. For each subcell it averages the luminance of every source pixel beneath it, using the Rec. 709 weighting with any transparency composited over white, and compares that single average with two thresholds. Below 85 on the 0–255 scale the subcell is painted in the foreground colour; above 175 it is left as background; anything between is painted in a mid tone. The methodology gives the purpose: quantised to three tones, the picture stays visible without inventing false modules. What the decoder reads is the authoritative centre, drawn in pure module colour above the ring.
The synthetic gradient in the example exercises all three bands. Of its 57,600 pixels, 51% fall below 85, 42% sit between the two thresholds, and 8% lie above 175. A real photograph is quantised by exactly the same two numbers.
What stays untouched
The split applies to data cells only. The engine's classifyModule function sorts every
module into one of 4 zones — finder, alignment, timing or data — and only the data zone
receives photo subcells; the rest are drawn as full pure squares. In the example above it returns
finder for
147 modules, alignment for 25, timing for 30 and data for
639, so 76% of the 841 modules carry any photo at all.
For a version-3 symbol the alignment-pattern centres fall at module coordinates 6 and 22 on each axis, which is how the classifier knows which cells to protect.
The methodology's anatomy rule is the reason: restyling alignment patterns broke decoding outright in testing, so no style on this site decorates a finder or an alignment pattern. Timing tracks may be styled elsewhere; the halftone style leaves them pure as well. The 4-module quiet zone is always drawn and never receives photo. The anatomy guide walks through the classifier for several versions.
The version cap, and the two character budgets
Density is the other limit. A longer payload needs a higher QR version — more modules in the same square — and every module shrinks accordingly, taking its authoritative centre with it. The engine's measurements found that past a certain density those centres fall below what a decoder can sample reliably with a photo in the ring, so it enforces a hard cap at version 4: a payload that encodes above it is refused outright, with an error rather than a warning.
The cap is stated in versions because that is what the decoder measures, but the tool translates
it into characters. The engine's own guidance is 34 characters of plain ASCII —
the byte-mode capacity of a version-4 symbol at error correction H — with emoji
and accented characters each costing several bytes. A fictional
34-character link, https://example.com/us/first-dance, encodes at version 4
and the renderer accepts it. A fictional longer one,
https://example.com/albums/summer-2026/shared?guest=1, is 53 characters and encodes at version
6; handed to the halftone renderer with the same synthetic photo, it produces
this refusal, verbatim:
This content needs a version-6 code, and photo weaving is decode-proven only to version 4 (about 34 characters, fewer with emoji or accents). Three ways forward: pick a different style, use a short link instead of a message, or switch to an inscription — uppercase letters, digits and simple punctuation fit 50 characters in the same photo code.
The second budget is the one that message ends on. QR alphanumeric mode encodes a restricted
character set — digits, capital letters, space and a few punctuation marks — at 5.5 bits per
character against byte mode's 8, so the same version-4 symbol holds
50 characters instead of 34 when the payload stays
inside that set. The engine's toInscription function folds text into it: uppercase, accents
flattened, a few meaning-preserving substitutions, anything else removed. Take a fictional note, Ella & Sam — our first summer, 2026. As a plain text payload it is
35 characters and 37 bytes, encodes at version 5,
and the renderer refuses it. As an inscription it becomes
ELLA + SAM - OUR FIRST SUMMER 2026: 34 characters, version
3, and the renderer accepts it. The
function also reports whether it had to change anything; here it reports that it did.
An inscription is still bare text, with the same display caveat as any offline note. A QR code containing bare text carries no scheme, so a phone has nothing to act on: Apple's Camera and Google's ML Kit recognise links, phone numbers, email addresses, contact cards, calendar events and Wi-Fi joins, and no plain-text type. Faced with bare text, an iPhone offers to copy or search it and Google Lens runs a web search; Samsung phones and dedicated scanner apps do display it, but a keepsake cannot depend on which phone opens it. The inscription is a capacity feature, not a fix for that — the message keepsakes guide covers the reveal link that is.
No logo, and the contrast gate
Two more refusals. A halftone code cannot carry a logo: the engine rejects the option outright, because a logo knocks out modules that error correction must reconstruct and the photo has already spent that margin. Every style here that redraws modules — hearts and the photo halftone — encodes at level H, roughly 30% recovery, and the halftone renderer rejects a model at any lower level before it looks at the photo. Foreground and background must also measure at least 4.5:1 in WCAG contrast and must not be inverted; the artistic engine rejects weaker pairs rather than warning. The example's default black on white measures 21.0:1. The mid tone is not part of that gate; the engine's default mid tone is a grey, between its default black foreground and white background. The heart-module guide shows the gate accepting and rejecting several pairs.
What verification proves for a photo code
After every render, the browser rasterises the finished artwork and runs jsQR on the pixels at 3 raster sizes — 800, 1200, 1600 pixels — and compares the decoded bytes with the exact payload. The badge is earned when at least 2 of them match. More than one size is needed because of a measured effect the engine calls sampling resonance: a render can fail at exactly one raster size and decode cleanly at the others, and a halftone render is where it was observed. A single fixed raster could false-negative a good code; a 2-of-3 rule cannot be flipped by one miss. Anything short of it shows a warning and keeps the downloads locked. What the badge cannot see is anything after the download: it proves that the rendered pixels decode at screen resolution, not what your printer, your paper or the scanning phone will do. The decode verification guide covers the loop.
Printing and testing a photo code
The methodology's print guidance applies unchanged: at least 2 × 2 cm for arm's-length scanning, the quiet zone uncropped, and a test print scanned with a real phone before the moment that matters. The halftone style adds one engine-computed fact. Because only the central 50% of each cell carries the module colour, the feature a scanner has to resolve is narrower than the cell, so the engine's minimum-print-size rule divides its usual module floor of 0.5 mm by the centre ratio, giving 1.0 mm per module for this style. For the 37-module example above the rule returns "print at least 37.0 mm (1.46 in) wide", against "print at least 18.5 mm (0.73 in) wide" for the same symbol drawn as a plain code. More in the printing guide.
Common misreadings
- Expecting the photo to be crisp. It is quantised to three tones on purpose; detail finer than a subcell is gone by design.
- Cropping the quiet zone. The 4-module margin is part of the code; trimming it in a layout tool removes something the engine always draws.
- Expecting an inscription to display on every phone. It is bare text and carries the caveat above — a capacity feature, not a delivery one.
- Choosing a soft colour pair. The gate is 4.5:1, enforced by rejection, and an inverted pair is rejected too.
- Wanting a logo on top. Refused for this style, without exception.
- Reading the badge as a print guarantee. It verifies pixels at screen resolution; the test print is the only verification of paper.
Photo halftone code FAQ
Why does my photo look so coarse inside the code?
Because the engine only ever draws three tones. Each ring subcell's average luminance is compared with two fixed thresholds — 85 and 175 on the 0–255 scale — and painted dark, mid or light. The methodology's reason: quantised this way, the picture stays visible without inventing false modules, and the decoder reads the pure-colour centre of each cell, not the ring.
Why does the tool refuse my link or message in the photo style?
The halftone renderer rejects any payload that encodes above version 4, a measured limit: denser symbols shrink the authoritative centres below what a decoder can sample reliably with a photo in the ring. That is about 34 characters of plain ASCII, fewer with emoji or accents, or 50 characters as an inscription in QR alphanumeric mode.
Can I add a logo on top of a photo code?
No. The engine rejects the logo option outright for the halftone style rather than warning: a logo knocks out modules that error correction has to reconstruct, and the photo has already spent that margin.
Does my photo get uploaded?
No. The photo is read by your browser, sampled on your device, quantised into the artwork locally and discarded when you close the page. If you save the keepsake to this device, what the library in your browser keeps is the finished artwork — the three-tone rendering that passed verification — and never the photo itself. The worked example on this page uses a synthetic gradient for the same reason: no photograph is shipped with the site or passes through its build.
Why should a photo code be printed larger than a plain one?
Only the central 50% of each cell carries the module colour, so the feature a scanner must resolve is narrower than the cell. The engine's minimum-print-size rule divides its 0.5 mm module floor by the centre ratio for this style, giving 1.0 mm per module. The methodology's guidance still applies: at least 2 × 2 cm, the quiet zone uncropped, and a test print scanned with a real phone.
What does the "Verified scannable" badge prove for a photo code?
That the finished artwork, rasterised at 800, 1200, 1600 pixels, decoded with jsQR to an exact byte match at no fewer than 2 of those sizes. It proves the rendered pixels decode at screen resolution; it cannot see your printer, your paper or the phone that will do the scanning.
Every render is decode-verified in your browser before a download is offered, and every rule on this page is a measured rule of the engine, pinned by its tests. The full set, including what verification cannot promise, is on the methodology page.