use ratatui::style::Color;
const ANSI16: [(u8, u8, u8); 16] = [
(0x00, 0x00, 0x00),
(0x80, 0x00, 0x00),
(0x00, 0x80, 0x00),
(0x80, 0x80, 0x00),
(0x00, 0x00, 0x80),
(0x80, 0x00, 0x80),
(0x00, 0x80, 0x80),
(0xc0, 0xc0, 0xc0),
(0x80, 0x80, 0x80),
(0xff, 0x00, 0x00),
(0x00, 0xff, 0x00),
(0xff, 0xff, 0x00),
(0x00, 0x00, 0xff),
(0xff, 0x00, 0xff),
(0x00, 0xff, 0xff),
(0xff, 0xff, 0xff),
];
fn cube_level(v: u8) -> u8 {
if v == 0 { 0 } else { 55 + 40 * v }
}
pub(crate) fn to_rgb(color: Color) -> Option<(u8, u8, u8)> {
let idx = match color {
Color::Rgb(r, g, b) => return Some((r, g, b)),
Color::Reset => return None,
Color::Black => 0,
Color::Red => 1,
Color::Green => 2,
Color::Yellow => 3,
Color::Blue => 4,
Color::Magenta => 5,
Color::Cyan => 6,
Color::Gray => 7,
Color::DarkGray => 8,
Color::LightRed => 9,
Color::LightGreen => 10,
Color::LightYellow => 11,
Color::LightBlue => 12,
Color::LightMagenta => 13,
Color::LightCyan => 14,
Color::White => 15,
Color::Indexed(i) => i,
};
Some(match idx {
0..=15 => ANSI16[idx as usize],
16..=231 => {
let n = idx - 16;
(
cube_level(n / 36),
cube_level((n % 36) / 6),
cube_level(n % 6),
)
}
_ => {
let v = 8 + 10 * (idx - 232);
(v, v, v)
}
})
}
pub(crate) fn luminance((r, g, b): (u8, u8, u8)) -> f32 {
fn chan(c: u8) -> f32 {
let s = f32::from(c) / 255.0;
if s <= 0.039_28 {
s / 12.92
} else {
((s + 0.055) / 1.055).powf(2.4)
}
}
0.2126 * chan(r) + 0.7152 * chan(g) + 0.0722 * chan(b)
}
pub(crate) fn contrast_ink(bg: Color) -> Color {
const DARK: Color = Color::Rgb(0x11, 0x11, 0x11);
const LIGHT: Color = Color::Rgb(0xee, 0xee, 0xee);
match to_rgb(bg) {
Some(rgb) if luminance(rgb) > 0.179 => DARK,
_ => LIGHT,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cube_colors_resolve_to_their_xterm_rgb() {
assert_eq!(to_rgb(Color::Indexed(107)), Some((0x87, 0xaf, 0x5f)));
assert_eq!(to_rgb(Color::Indexed(106)), Some((0x87, 0xaf, 0x00)));
assert_eq!(to_rgb(Color::Indexed(167)), Some((0xd7, 0x5f, 0x5f)));
}
#[test]
fn the_grey_ramp_resolves() {
assert_eq!(to_rgb(Color::Indexed(232)), Some((8, 8, 8)));
assert_eq!(to_rgb(Color::Indexed(255)), Some((238, 238, 238)));
}
#[test]
fn rgb_passes_through_and_reset_is_unknown() {
assert_eq!(to_rgb(Color::Rgb(1, 2, 3)), Some((1, 2, 3)));
assert_eq!(to_rgb(Color::Reset), None);
}
#[test]
fn ink_is_dark_on_light_cursors_and_light_on_dark_ones() {
assert_eq!(
contrast_ink(Color::Rgb(0xff, 0xff, 0xff)),
Color::Rgb(0x11, 0x11, 0x11)
);
assert_eq!(
contrast_ink(Color::Rgb(0x00, 0x00, 0x00)),
Color::Rgb(0xee, 0xee, 0xee)
);
assert_eq!(
contrast_ink(Color::Indexed(107)),
Color::Rgb(0x11, 0x11, 0x11)
);
}
#[test]
fn an_unresolvable_color_gets_light_ink() {
assert_eq!(contrast_ink(Color::Reset), Color::Rgb(0xee, 0xee, 0xee));
}
}