QR code with a logo: the error budget, and how not to blow it
2026-07-31
A logo is not placed on a QR code, it amputates it
Putting a logo in the middle of a QR code does not cover empty space. The matrix is full: every black or white module carries information. The logo simply destroys the modules it hides.
If the code still scans, it is not because the centre matters less — it is because ISO/IEC 18004 builds in redundancy. Understanding that redundancy is understanding exactly how much logo you can afford.
The error budget: four levels
A QR code encodes its data with a Reed-Solomon error-correcting code. You choose how much redundancy at encoding time, from four levels:
- L — roughly 7% of the data recoverable
- M — roughly 15%
- Q — roughly 25%
- H — roughly 30%
That percentage is the code's total degradation budget, and it is not spent at the centre alone. Smudged printing, a fold, a scratch, glare on a screen: everything draws on the same budget. A logo that consumes the full 30% leaves nothing for the real world, and the code only scans in perfect conditions.
This is why web3qr forces level H whenever a logo is present, regardless of the setting chosen elsewhere. The requested level is honoured only on a code without a logo.
The real maximum size: 30%
The question always asked is "how big can it be?". The answer is bounded by the budget above.
In web3qr, logo size is expressed as a fraction of the QR code's width. The default is 0.22, or 22% of the width — about 4.8% of the area. The engine clamps it to a floor of 0.05 and a ceiling of 0.30.
That ceiling is not decorative. Beyond 30% of the width, the destroyed area exceeds what Reed-Solomon can rebuild even at level H, and scanning becomes a lottery: it works on a recent phone in good light and fails on another. The clamp lives in the engine, not the interface, precisely so that an imported design — an old history JSON, an external template — cannot bypass it.
A useful reminder: think in area, not width. A logo at 30% of the width covers 9% of the code's area. That is already substantial once the mask margin is added.
Masking: why a logo pasted "as is" is a mistake
Dropping an image on top of the matrix creates a problem scanners handle badly: the modules underneath the logo's semi-transparent edges become ambiguous. They are neither clearly black nor clearly white, and the decoder binarises them at random.
The correct practice is the negative mask: clear a crisp area first, then draw the logo inside it. The decoder then sees a uniformly empty region, which it treats as a clean erasure — exactly what Reed-Solomon is built to repair. An ambiguous region, by contrast, produces unstable reads.
web3qr offers three mask shapes:
- circle — an exclusion disc around the logo. The safest and most conventional.
- square — a box. Useful for a logo that is itself rectangular.
- padded — the feathered mask: it follows the logo's exact alpha outline, widened by a radial dilation. The silhouette is stamped as discs at every position of a dilation disc, producing a soft halo that hugs the logo's real shape instead of a blunt box.
The feathered mask looks best, and it also consumes the fewest modules: it erases only what is needed, where a square wastes the corners.
The mask margin, often forgotten
Between the logo and the surviving modules there must be margin. Without it, the scanner reads a partially covered module and gets it wrong.
web3qr scales that margin with the render size, against a 300-pixel reference: a QR exported at 1024 px gets a margin about 3.4 times larger than a 300 px preview. The apparent margin stays constant at any export size, which avoids the classic trap of a logo that looks perfect on screen and fails once printed large.
The quiet zone, never to be trimmed
Independently of the logo, a QR code requires a blank margin around it — the *quiet zone*. The standard asks for a minimum of 4 modules. web3qr reserves 8% of the total width, which lands in that range.
This is the most common layout mistake: a QR pushed against the edge of a flyer, or sitting on a photo that bleeds into its margin. The scanner can no longer find the symbol's boundaries and gives up before attempting to decode. No level of error correction compensates for a missing quiet zone, because the failure happens before error correction is reached.
Contrast, the other half of the problem
A well-sized logo will not save a code with insufficient contrast. The decoder starts by binarising the image: each pixel becomes black or white against a threshold. If modules and background are too close in luminance, binarisation produces noise before Reed-Solomon ever gets involved.
Two practical rules:
- Modules must be darker than the background. A light code on a dark background works on many modern scanners, but not all, and nothing in the standard requires support for it.
- A gradient is fine as long as its lightest tone stays clearly darker than the background. A gradient that crosses the background's luminance creates unreadable bands through the middle of the code.
The method that avoids surprises
- Generate with the logo at the intended size, staying under 25% of the width if the code will be printed.
- Confirm the error correction level is H.
- Test on at least two phones, one iOS and one Android: their decoders differ and forgive different flaws.
- Test at the actual usage distance, not with your nose on the screen.
- For a print run, print one copy and scan it before committing the rest. An ink defect invisible to the eye still spends error budget.
If the code passes these five steps, the logo will not cause trouble. If it fails one, the first thing to reduce is the logo size — it is the only parameter that gives budget straight back to error correction.