afterimageComplement function
The afterimage complement: the color the eye produces after adapting to
input.
Cone fatigue shifts perception of a neutral field in the direction opposite the stimulus chromaticity (Zaidi et al., "Neural locus of color afterimages", Current Biology 2012: the rebound is the chromatically opposite signal), so the afterimage chromaticity is the point reflection of the input's u'v' through the white point:
target = white + (white - input)
equal strength, opposite direction — rendered at the input's tone. This gives the pair its defining, physically checkable property: an additive mixture of input and complement is neutral. Verified across the RGB cube in test/effects/afterimage_test.dart (99.9% of colors mix to within one u'v' JND of white).
If the full-strength target is out of gamut at the input's tone, strength is clamped ALONG THE RAY from white: direction is physiology, only magnitude yields to the gamut. Consequently chroma is NOT preserved — yellow's complement is a far dimmer violet-blue than blue's complement is a gold — which is what distinguishes this from hue-rotation complements.
Achromatic inputs return themselves: with no chromatic fatigue there is no afterimage.
Implementation
Hct afterimageComplement(Hct input) {
final p = uvOfArgb(input.toInt());
if (p == null) return input; // black: no chromaticity to fatigue against
final (u, v) = p;
final (wu, wv) = whiteUvPoint;
final du = wu - u, dv = wv - v; // opposite direction; s=1 is full strength
final slice = SliceCache.of(input.tone);
final exit = slice.rayExit(du, dv);
final result = exit == null
? Hct.from(0.0, 0.0, input.tone)
// Clamp strength along the ray to just inside the boundary.
: slice.nearest(
wu + math.min(1.0, exit.$1 * 0.995) * du,
wv + math.min(1.0, exit.$1 * 0.995) * dv,
);
// Slice tone is quantized (0.5 steps); restore the exact input tone, and
// re-express in the input's color model.
final retoned = Hct.from(result.hue, result.chroma, input.tone);
return Hct.fromInt(retoned.toInt(), model: input.colorModel);
}