On a clean screenshot, every colour works. That is why most colour advice is wrong.

We tested a QR code at a contrast ratio of 1.36:1 - pale grey on white, almost invisible to a human - and both decoders read it perfectly from the exported PNG. Colour does not start costing you anything until the image stops being perfect. So we made it imperfect, on purpose, and measured how much abuse each colour could take.

What we did

We generated 48 QR codes with the live generator on this site: 24 colour pairs, each at error correction L and H. Every code was then normalised to 6 pixels per module - a realistic printed size - and put through a simulated bad scan: increasing defocus blur, then sensor noise on top, the way a real camera degrades in poor light.

Each severity level adds 0.35 of blur sigma and 6.0 grey levels of noise. A code “survives” a level only if it decodes on all three independent noise draws, because a single random draw is a coin flip rather than a measurement.

The score for each colour is the worst level it survives. Higher is tougher.

Finding 1: contrast predicts toughness, closely

Across all 48 codes, the correlation between contrast ratio (log scale) and the amount of degradation survived is r = 0.898. That is a tight relationship for a physical measurement, and it means contrast ratio is a genuinely useful proxy for “will this survive the real world”.

Grouped the way this site’s own contrast warning groups them:

  • 4.5:1 and above (36 codes) survived to a median of level 4, up to level 6.
  • 3.0:1 to 4.5:1 (2 codes) reached levels 3 and 4.
  • Below 3.0:1 (10 codes) managed a median of level 2, up to level 3, and the worst never got past level 1.

A code below 3:1 takes roughly half the abuse of one above 4.5:1 before it stops reading. The thresholds the generator already warns at turn out to sit in the right place.

Every colour we tested

Error correction L, the generator’s default. “Survives to” is the worst degradation level that still decoded on all three noise draws.

24 colour pairs, ranked by contrast ratio. The last column is whether OpenCV could read the code at all.
Colour pair Contrast Survives to Blur at limit OpenCV reads it
black on white21.00:162.1yes
near-black on white18.88:151.75yes
white on near-black18.88:151.75no
brand lime on brand black17.19:151.75no
near-black on cream16.96:151.75yes
dim lime on near-black11.25:141.4no
dark grey on white10.44:151.75yes
navy on white10.36:151.75yes
off-white on navy9.39:141.4no
dark brown on white9.37:151.75yes
navy on pale blue8.49:141.4yes
slate on white7.56:151.75yes
brown on peach6.92:141.4yes
deep red on white5.67:141.4yes
teal on white5.47:151.75yes
teal on pale cyan5.26:151.75yes
mid grey on white4.83:131.05yes
magenta on white4.54:141.4yes
red on white3.76:131.05yes
hot pink on white2.65:131.05yes
light slate on white2.56:120.7yes
yellow on white1.92:120.7yes
pale grey on white1.48:110.35yes
near-white on white1.36:110.35yes

Finding 2: light-on-dark codes are a gamble on the scanner

Look down the last column. Of the 40 dark-on-light codes, OpenCV read 40. Of the 8 light-on-dark codes, it read 0.

That is not a contrast problem. White on near-black is a contrast ratio of 18.9:1, about as strong as a QR code can be, and OpenCV still refused it at full size on a perfectly clean image. Invert the picture and it decodes instantly. ZXing, which tries both polarities by default, read every one of them.

So an inverted QR code is not broken and it is not low quality. It simply depends on whether the scanner on the other end bothers to try the other polarity, and a meaningful share of them do not. If the code is going somewhere you cannot control - a poster, a package, a stranger’s phone - dark modules on a light background remains the safe choice.

Finding 3: raising error correction does not rescue a weak colour

The standard advice for a coloured or low-contrast code is to crank error correction up to H. We tested every colour pair at both L and H. Going from L to H made things slightly worse on average: -0.25 levels.

The reason is geometric rather than mysterious. Error correction does not add resilience for free - it adds data, and more data means a bigger code. Our test payload went from 29 modules to 41 when we switched from L to H. At the same printed size, those 41 modules are each smaller than the 29 were, so every module is more fragile to exactly the blur and noise the error correction was supposed to defend against. The two effects very nearly cancel.

Error correction is the right tool for damage that covers part of a code, such as a logo in the middle. It is close to useless against damage spread evenly over the whole code, which is what poor contrast and a bad camera produce.

If a colour is too weak, the fix is a better colour, a bigger print, or more light. Not level H.

Method

  • Codes. Generated by the live generator on this site, exported through the normal download path, so each includes the four-module quiet zone.
  • Normalisation. Every sample resized to 6 pixels per module, so the only variable is colour, not code size.
  • Degradation. Gaussian defocus then additive sensor noise, in that order, because that is the order a real camera applies them. 15 severity levels, 3 independent noise seeds each.
  • Primary decoder: ZXing. Chosen for the contrast ladder because it handles both polarities, so it measures colour rather than polarity.
  • Compatibility decoder: OpenCV. Reported separately, which is how the inverted-code finding surfaced at all.
  • Contrast ratio is the standard WCAG relative-luminance ratio, the same formula the generator uses for its own live warning.

Download the full dataset (CSV, 48 rows)

What this does not tell you

Blur and noise are a stand-in for a bad scan, not a substitute for one. Real failures also involve uneven lighting, glare, motion, print dot gain and ink bleed, and a phone camera has autofocus and exposure control working in your favour. Treat the levels as a way to rank colours against each other, not as an absolute prediction of whether a specific code will scan.

We also held one thing fixed that matters: everything was tested at 6 pixels per module. Print bigger and a weak colour buys back some of what it lost.

Pick your colours here

The generator warns you live when a colour pair falls below these thresholds, and it checks both ends of a gradient rather than just one. Free, no sign-up, no watermark.

Open the QR generator →

Keep reading

Related pages on QR Studio.