solveLightness function
Bisects the OKLCH lightness of a colour with chroma c and hue hDeg,
starting at l0, until its quantised ARGB reaches target against
againstArgb. Returns null if neither direction can reach it.
Direction: toward white first when the background is darker than
yEqualContrast (or preferLighter when given), then the other way.
lighterFrom / darkerFrom override the bisection start for that
direction. The result always passes, even where quantisation makes
Y(L) locally non-monotone, because only verified L values are kept.
Implementation
int? solveLightness({
required double l0,
required double c,
required double hDeg,
required int againstArgb,
required double target,
bool? preferLighter,
double? lighterFrom,
double? darkerFrom,
}) {
final bgY = luminanceArgb(againstArgb);
int argbAt(double l) => linToArgb(mapOklch(l, c, hDeg));
bool ok(double l) => contrastY(luminanceArgb(argbAt(l)), bgY) >= target;
final lighterFirst = preferLighter ?? bgY < yEqualContrast;
for (final lighter in [lighterFirst, !lighterFirst]) {
final start = (lighter ? lighterFrom : darkerFrom) ?? l0;
if (ok(start)) {
// Only accept the start if it is on the requested side, or it is l0.
if (start == l0 || lighter == (luminanceArgb(argbAt(start)) > bgY)) {
return argbAt(start);
}
}
final end = lighter ? 1.0 : 0.0;
if (!ok(end)) continue;
var bad = start;
var good = end;
for (var i = 0; i < 30; i++) {
final mid = (bad + good) * 0.5;
if (ok(mid)) {
good = mid;
} else {
bad = mid;
}
}
return argbAt(good);
}
return null;
}