solveLightness function

int? solveLightness({
  1. required double l0,
  2. required double c,
  3. required double hDeg,
  4. required int againstArgb,
  5. required double target,
  6. bool? preferLighter,
  7. double? lighterFrom,
  8. double? darkerFrom,
})

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;
}