nearest method
In-gamut color at tone whose chromaticity is nearest (tu, tv).
Implementation
Hct nearest(double tu, double tv) {
final (wu, wv) = whiteUvPoint;
final du = tu - wu, dv = tv - wv;
final dist = math.sqrt(du * du + dv * dv);
if (dist < 1e-9) return Hct.from(0.0, 0.0, tone);
final exit = rayExit(du, dv);
if (exit == null) return Hct.from(0.0, 0.0, tone);
final hue = exit.$2 % 360.0;
if (exit.$1 <= 1.0) {
// Target at/outside boundary: nearest point on the cached polyline,
// pure arithmetic (no solver), then a short boundary polish.
var bestD = double.infinity;
var bestHue = hue;
for (var i = 0; i < 360; i++) {
final j = (i + 1) % 360;
final eu = us[j] - us[i], ev = vs[j] - vs[i];
final len2 = eu * eu + ev * ev;
var t = 0.0;
if (len2 > 1e-18) {
t = (((tu - us[i]) * eu + (tv - vs[i]) * ev) / len2).clamp(0.0, 1.0);
}
final qu = us[i] + t * eu - tu, qv = vs[i] + t * ev - tv;
final d = qu * qu + qv * qv;
if (d < bestD) {
bestD = d;
bestHue = (i + t) % 360.0;
}
}
// Polish: polyline is ~1 degree coarse; ternary-search true boundary.
var lo = bestHue - 1.0, hi = bestHue + 1.0;
for (var i = 0; i < _polishIters; i++) {
final m1 = lo + (hi - lo) / 3.0, m2 = hi - (hi - lo) / 3.0;
if (_dist2(Hct.from(m1 % 360.0, 250.0, tone), tu, tv) <=
_dist2(Hct.from(m2 % 360.0, 250.0, tone), tu, tv)) {
hi = m2;
} else {
lo = m1;
}
}
return Hct.from(((lo + hi) / 2.0) % 360.0, 250.0, tone);
}
// Inside: refine chroma, then hue, then chroma again. HCT hue lines
// curve in u'v', so the best hue can sit a few degrees off the
// ray-exit hue.
var bestHue = hue;
var best = _chromaSearch(bestHue, tu, tv);
var lo = bestHue - _hueWidth, hi = bestHue + _hueWidth;
for (var i = 0; i < _hueIters; i++) {
final m1 = lo + (hi - lo) / 3.0, m2 = hi - (hi - lo) / 3.0;
if (_dist2(Hct.from(m1 % 360.0, best.chroma, tone), tu, tv) <=
_dist2(Hct.from(m2 % 360.0, best.chroma, tone), tu, tv)) {
hi = m2;
} else {
lo = m1;
}
}
bestHue = ((lo + hi) / 2.0) % 360.0;
return _chromaSearch(bestHue, tu, tv);
}