Make any photo perceptually ambiguous like #theDress — by collapsing its colours onto the CIE daylight axis
#theDress went viral because its colours vary along a single direction in colour space — the blue↔yellow daylight axis. Our visual system can't tell whether that variation comes from the dress or from the light falling on it, so each brain silently "subtracts" an assumed illuminant: subtract blue and you see white&gold; subtract yellow and you see blue&black. This tool reproduces that ambiguity for any image by forcing its chroma onto the daylight axis, then shows you both readings of the very same pixels.
1. Compute the CIE daylight locus direction in perceptual CIELUV colour
(the line natural light travels as it goes from warm sunset to cool blue sky).
2. For every pixel, split its colour into the part along that daylight axis and the
part across it (the red↔green part).
3. Remove the across-axis (red↔green) part. Red-green shifts are never caused
by daylight, so the brain won't blame them on the light — keeping them would give the true
colour away. Removing them is what makes the image ambiguous.
4. Pull the average colour toward neutral and gently desaturate, placing the palette in the
"could be either light" zone.
5. Keep the original lightness so the subject stays recognisable.
6. To show the two readings, apply real von Kries / Bradford chromatic
adaptation: assume a cool bluish light and discount it → White/Gold; assume a warm
light and discount it → Blue/Black. Same pixels, two illuminant priors.
Daylight axis d (fixed, computed once):
Sample CIE daylight locus at 4000 K (warm) and 20000 K (cool) via Judd's xy(T),
convert each xy -> XYZ -> CIELUV (u*,v*) relative to D65, take the unit
difference vector. Result d ~= (0.44, 0.90); orthogonal p = (-d_v, d_u).
Per pixel (entire image treated as object):
1. sRGB -> linear -> XYZ (sRGB/D65 matrix) -> CIELUV (L*,u*,v*).
2. along = (u*,v*) . d across = (u*,v*) . p
3. across' = across * (1 - axisLock) // suppress red-green -> ambiguity
4. along' = (along - meanAlong*centerPull) * desat // recenter + soften
5. (u_map,v_map) = along'*d + across'*p ; L_map = L* (lightness preserved)
6. Blend: Luv_out = Luv_orig + intensity*(Luv_map - Luv_orig)
7. Luv -> XYZ -> linear sRGB -> gamma -> 8-bit (clipped).
Two readings (color-constancy demonstration), applied to the ambiguous result:
M_read = M(XYZ->sRGB) . CAT_Bradford(assumedWhite -> D65) . M(sRGB->XYZ)
White/Gold = assume cool 15000 K light, discount it (image warms).
Blue/Black = assume warm 4000 K light, discount it (image cools).