use crate::base::Rgba;
use crate::theme::contrast::contrast_ratio;
pub fn mix(a: Rgba, b: Rgba, t: f32) -> Rgba {
a.lerp(b, t)
}
pub fn lighten(c: Rgba, t: f32) -> Rgba {
c.lerp(Rgba::WHITE, t)
}
pub fn darken(c: Rgba, t: f32) -> Rgba {
c.lerp(Rgba::BLACK, t)
}
pub fn mix_until_contrast(
base: Rgba,
ink: Rgba,
anchor: Rgba,
t0: f32,
step: f32,
floor: f32,
) -> Rgba {
debug_assert!(step > 0.0, "mix_until_contrast requires a positive step");
let mut t = t0.clamp(0.0, 1.0);
loop {
let candidate = base.lerp(ink, t);
if contrast_ratio(candidate, anchor) >= floor || t >= 1.0 {
return candidate;
}
t = (t + step).min(1.0);
}
}
pub fn tint_until_readable(
base: Rgba,
tint: Rgba,
fg: Rgba,
t0: f32,
step: f32,
t_min: f32,
floor: f32,
) -> Rgba {
debug_assert!(step > 0.0, "tint_until_readable requires a positive step");
let mut t = t0.clamp(0.0, 1.0);
loop {
let candidate = base.lerp(tint, t);
if contrast_ratio(fg, candidate) >= floor || t <= t_min {
return candidate;
}
t = (t - step).max(t_min);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mix_endpoints_are_exact() {
let a = Rgba::rgb(0x1a, 0x1a, 0x2e);
let b = Rgba::rgb(0xee, 0xee, 0xee);
assert_eq!(mix(a, b, 0.0), a);
assert_eq!(mix(a, b, 1.0), b);
}
#[test]
fn lighten_darken_move_luminance_monotonically() {
let c = Rgba::rgb(0x88, 0x40, 0xa0);
assert!(lighten(c, 0.3).luminance() > c.luminance());
assert!(darken(c, 0.3).luminance() < c.luminance());
}
#[test]
fn upward_walk_reaches_floor_from_a_dark_ground() {
let bg = Rgba::rgb(0x1a, 0x1a, 0x2e);
let text = Rgba::rgb(0xee, 0xee, 0xee);
let border = mix_until_contrast(bg, text, bg, 0.12, 0.02, 1.5);
assert!(contrast_ratio(border, bg) >= 1.5);
assert!(contrast_ratio(border, bg) < 3.0);
}
#[test]
fn upward_walk_returns_ink_when_floor_is_unreachable() {
let g = Rgba::rgb(0x80, 0x80, 0x80);
let ink = Rgba::rgb(0x90, 0x90, 0x90);
assert_eq!(mix_until_contrast(g, ink, g, 0.1, 0.05, 21.0), ink);
}
#[test]
fn downward_walk_keeps_fg_readable() {
let bg = Rgba::rgb(0xef, 0xf1, 0xf5);
let accent = Rgba::rgb(0x88, 0x39, 0xef);
let fg = Rgba::rgb(0x4c, 0x4f, 0x69);
let sel = tint_until_readable(bg, accent, fg, 0.60, 0.02, 0.04, 4.5);
assert!(contrast_ratio(fg, sel) >= 4.5);
assert!(contrast_ratio(sel, bg) > 1.05);
}
}