Bhinish Dhiman

How much of a QR code can a logo cover? We measured it

A centred logo can cover roughly 4.3% of a QR code's area at error-correction level L, 9.2% at M, 14.3% at Q and 19.3% at H — measured across about 5,000 decode attempts. The widely repeated 30% figure misreads the standard: ISO/IEC 18004 says level H recovers 30% of the symbol's codewords, which is not 30% of its visible area.

Search for how big a logo can be on a QR code and you will be told 30%, on almost every page, usually at level H. We could not find the measurement behind that number, so we made it.

What the standard actually says

ISO/IEC 18004 clause 5.1 defines the four error-correction levels by what they recover:

allowing the symbol codeword recovery of — L: 7%; M: 15%; Q: 25%; H: 30%.

Two words carry the whole misunderstanding: codeword recovery. A codeword is eight modules of encoded data. Thirty per cent of the codewords is not thirty per cent of the picture, and three things widen the gap.

The finder, timing and alignment patterns carry no error correction at all. They are how a scanner locates and orients the symbol before decoding begins. On a version-1 code they occupy 52.8% of the area; on a version-40 code, 5.4%. Cover any part of them and no level of error correction helps, because correction has not started yet.

A logo is a contiguous blob, and a partial hit is a total loss. Error correction works on whole codewords. A logo clipping the corner of a codeword destroys that codeword as completely as covering all of it.

Correction is applied per interleaved block, not pooled. A symbol is split into blocks, each with its own recovery budget. A blob concentrated over two blocks can exhaust them while the rest of the symbol sits untouched with budget to spare.

The measurement

We encoded four payloads at each of the four error-correction levels, overlaid a centred square knockout, grew it one module at a time, and attempted to decode after each step. Each attempt went through a camera model rather than a clean render: downsampled to a fixed number of pixels per module, blurred by 0.8px, given Gaussian noise at σ=6, and JPEG-compressed at quality 70.

Around 5,000 decode attempts in total. For a typical 54-character URL:

Error correction Largest logo that still decodes Nominal “recovery”
L 4.3% of area 7%
M 9.2% 15%
Q 14.3% 25%
H 19.3% 30%

Even at level H, on the longest payload we tested, the ceiling was 22.4%. Nothing we measured came close to 30%.

The part that should change how you test

Reed–Solomon correction has no graceful degradation. Across every level and every resolution we tested, a symbol decoded 100% of the time right up to the last survivable module, and 0% one module later. There is no band where it works sometimes.

That matters because of how people check QR codes: they scan it with their phone, it works, they send it to print. A successful scan tells you that you are on the correct side of the cliff. It tells you nothing about how close to the edge you are — and the edge is where a worn print, a cheaper camera sensor or a steeper angle puts you on the other side.

This is the argument for computing the margin rather than testing for it. Our QR code generator shows the proportion of the error-correction budget a logo is consuming, so you can see the distance to the cliff instead of discovering it after a print run.

Three more results that contradict common advice

Rounded and dot-shaped modules are free, down to about 0.7× diameter. The common warning that rounded modules hurt scannability is not supported by what we measured — provided the three corner finder patterns stay solid squares.

Styling the finder patterns is the real damage, and error correction cannot fix it. Dotted finder patterns scored an identical 60% scan rate at L, M, Q and H. Flat across all four levels, because it is a detection failure rather than a correction failure: the scanner cannot locate the symbol, so correction never runs. This is the strongest available argument against “just set it to H and it will be fine”.

Hue is irrelevant; only luminance counts. Green on red — a pairing that looks obviously bad — failed. Red on blue, which looks equally awkward, decoded reliably. The governing measure is ISO/IEC 15415 Symbol Contrast, a reflectance difference graded A to F, and it is not a WCAG ratio. WCAG measures text legibility for human eyes; a camera sensor is a different instrument. Most pages answering “what contrast does a QR code need” quote WCAG figures, which is the wrong unit for the question.

The 10:1 distance rule has nothing behind it

The rule that scanning distance can be ten times the symbol’s width appears everywhere and is in no standard — ISO/IEC 18004 sets no minimum module size at all. What we measured is a threshold in camera pixels per module: below about 2.5 pixels per module the decode rate collapses, sharply.

That makes the rule wrong in both directions, because it ignores module count. A version-2 code and a version-20 code at the same printed size have modules 2.3× different in size, and therefore very different scan distances. One number cannot describe both.

Worth knowing alongside it: phones decode from the camera preview stream, not a full-resolution still. A 12-megapixel sensor does not give you 12 megapixels of decoding, which is why the physical maths looks far more optimistic than everyday experience.

Caveats, stated plainly

Every decode above used one decoder implementation. It is measurably more tolerant than a print verifier and possibly more tolerant than the median phone, so treat these as an upper bound rather than a guarantee. The budget model behind our tool is deliberately set on the conservative side of what we measured — it predicts the cliff within one module, and errs toward telling you a logo is too big.

The method, the scripts and the raw figures are in the repository behind this site. If you repeat this with a different decoder and get different numbers, we would like to know.