use ratatui::style::Color;
pub fn rgb(hex: u32) -> Color {
Color::Rgb(
((hex >> 16) & 0xff) as u8,
((hex >> 8) & 0xff) as u8,
(hex & 0xff) as u8,
)
}
pub fn blend(a: Color, b: Color, t: f32) -> Color {
match (a, b) {
(Color::Rgb(ar, ag, ab), Color::Rgb(br, bg, bb)) => {
let mix = |x: u8, y: u8| {
(x as f32 * (1.0 - t) + y as f32 * t)
.round()
.clamp(0.0, 255.0) as u8
};
Color::Rgb(mix(ar, br), mix(ag, bg), mix(ab, bb))
}
_ => a,
}
}
pub fn saturate(c: Color, amount: f32) -> Color {
let Color::Rgb(r, g, b) = c else { return c };
let avg = (r as f32 + g as f32 + b as f32) / 3.0;
let push = |x: u8| {
let d = x as f32 - avg;
(avg + d * (1.0 + amount)).round().clamp(0.0, 255.0) as u8
};
Color::Rgb(push(r), push(g), push(b))
}
pub fn luminance(c: Color) -> Option<f32> {
let Color::Rgb(r, g, b) = c else { return None };
Some((0.2126 * r as f32 + 0.7152 * g as f32 + 0.0722 * b as f32) / 255.0)
}
pub fn best_contrast(bg: Color, a: Color, b: Color) -> Color {
match (luminance(bg), luminance(a), luminance(b)) {
(Some(l_bg), Some(l_a), Some(l_b)) => {
if (l_a - l_bg).abs() >= (l_b - l_bg).abs() {
a
} else {
b
}
}
_ => a,
}
}
pub fn contrast_ratio(a: Color, b: Color) -> Option<f32> {
fn linear(c: Color) -> Option<f32> {
let Color::Rgb(r, g, b) = c else { return None };
let f = |v: u8| {
let v = v as f32 / 255.0;
if v <= 0.03928 {
v / 12.92
} else {
((v + 0.055) / 1.055).powf(2.4)
}
};
Some(0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b))
}
let (x, y) = (linear(a)?, linear(b)?);
let (hi, lo) = if x > y { (x, y) } else { (y, x) };
Some((hi + 0.05) / (lo + 0.05))
}
const LEGIBILITY_STEP: f32 = 0.1;
pub fn legible_on(bg: Color, fg: Color, ink: Color, min: f32) -> Color {
if !matches!(
(bg, fg, ink),
(Color::Rgb(..), Color::Rgb(..), Color::Rgb(..))
) {
return fg;
}
let mut out = fg;
let mut t = 0.0;
while contrast_ratio(out, bg).is_some_and(|c| c < min) && t < 1.0 {
t += LEGIBILITY_STEP;
out = blend(fg, ink, t);
}
out
}
const CHROME_SATURATION_BOOST: f32 = 1.0;
pub fn chrome(bg: Color, ink: Color, t: f32) -> Color {
saturate(blend(bg, ink, t), CHROME_SATURATION_BOOST)
}