This was fun: A "Where Is Waldo" game for QR codes.
Can you find it? Your phone can!
The basic idea
QR scanners don't see color. The first thing a decoder does is convert the image to grayscale — collapsing each pixel to a single brightness value via roughly 0.299·R + 0.587·G + 0.114·B — then binarize and hunt for the finder patterns (the three corner squares) and module grid. Everything downstream operates on luminance alone.
That gap is the whole trick: luminance carries the code; hue and saturation are free to do whatever you want. You have two perceptual channels the scanner is blind to, and humans hunting for a pattern rely heavily on exactly those channels.
So the code is rendered as a luminance difference — "dark" modules low-luma, "light" modules high-luma — but each module is painted a vivid random color at its assigned brightness. The key subtlety is that a fully saturated color's true luminance depends on its hue: pure blue sits around 0.11, pure red around 0.30, pure yellow near 0.89. So "dark" modules don't have to be dark and muddy — you can use electric blues, reds, and violets that are naturally low-luminance at full saturation, and reserve the bright hues (yellow, cyan, green) for "light" modules.
To the hyman eye it's a saturated confetti, but converted to grayscale it's a scannable QR.
The code
#! /usr/bin/env python
import cv2
import numpy as np
PAYLOAD = "https://blog.vrypan.net/2026/07/07/where-is-qrdo/"
MOD = 8
GRID_W, GRID_H = 192, 96
N_DECOYS = 180
enc = cv2.QRCodeEncoder.create()
qr = enc.encode(PAYLOAD)[2:-2, 2:-2]
n = qr.shape[0]
det = cv2.QRCodeDetector()
LUMA = np.array([0.114, 0.587, 0.299]) # BGR
# --- curated DARK palette (BGR), all low-luma, balanced across families ----
# reds need V<255 to stay low-luma; blues/violets are low-luma at full V.
DARK = np.array([
[0, 0, 220], # strong red
[0, 0, 180], # deep red
[30, 0, 200], # red-magenta
[255, 0, 0], # pure blue
[200, 0, 0], # deep blue
[255, 40, 0], # blue-cyan lean
[200, 0, 120], # indigo
[180, 0, 180], # violet
[140, 0, 200], # purple-red
], np.float32)
# --- LIGHT palette (BGR), all high-luma, vivid + bright ---
LIGHT = np.array([
[0, 255, 255], # yellow
[0, 255, 150], # yellow-green
[0, 255, 0], # green
[255, 255, 0], # cyan
[255, 255, 150], # pale cyan
[150, 255, 255], # pale yellow
[200, 255, 200], # mint
], np.float32)
print("dark palette lumas:", np.round(DARK @ LUMA / 255, 2))
print("light palette lumas:", np.round(LIGHT @ LUMA / 255, 2))
def build(seed):
r = np.random.default_rng(seed)
# --- random QR placement (keep a 1-module margin for the finder halos) ---
qx = int(r.integers(1, GRID_W - n))
qy = int(r.integers(1, GRID_H - n))
is_dark = r.random((GRID_H, GRID_W)) < 0.40
for cx, cy in [(qx, qy), (qx+n-7, qy), (qx, qy+n-7)]:
is_dark[cy-1:cy+8, cx-1:cx+8] = False
for y in range(n):
for x in range(n):
is_dark[qy+y, qx+x] = qr[y, x] < 128
for _ in range(N_DECOYS):
dx = int(r.integers(1, GRID_W-9)); dy = int(r.integers(1, GRID_H-9))
if (qx-10 < dx < qx+n+1) and (qy-10 < dy < qy+n+1):
continue
s = int(r.integers(6, 9))
is_dark[dy:dy+s, dx:dx+s] = True
is_dark[dy+1:dy+s-1, dx+1:dx+s-1] = False
inner = int(r.integers(1, 3))
is_dark[dy+2:dy+s-2-(inner-1), dx+2:dx+s-2-(inner-1)] = True
canvas = np.zeros((GRID_H, GRID_W, 3), np.float32)
di = r.integers(0, len(DARK), (GRID_H, GRID_W))
li = r.integers(0, len(LIGHT), (GRID_H, GRID_W))
for y in range(GRID_H):
for x in range(GRID_W):
canvas[y, x] = DARK[di[y, x]] if is_dark[y, x] else LIGHT[li[y, x]]
img = cv2.resize(canvas.astype(np.uint8), (GRID_W*MOD, GRID_H*MOD),
interpolation=cv2.INTER_NEAREST)
return img, (qx, qy)
import secrets
# random search order (fresh OS entropy each run) so the QR lands in a
# different spot every time, rather than always picking the first seed that works
seeds = list(range(1, 4000))
np.random.default_rng(secrets.randbits(64)).shuffle(seeds)
for seed in seeds:
img, (qx, qy) = build(seed)
if det.detectAndDecode(img)[0] == PAYLOAD:
cv2.imwrite("qr_redblue.png", img)
print(f"seed {seed}: decodes OK -> qr_redblue.png (QR at module {qx},{qy})")
break
else:
raise SystemExit("no working seed found")