use crossterm::style::Color;
static VGA_LUMINANCE: [f32; 16] = [
0.0, 0.046770, 0.213066, 0.045910, 0.531007, 0.153787, 0.927127, 0.110974, 0.074821, 0.015209, 0.284858, 0.061119, 0.602799, 0.168996, 1.0, 0.296731, ];
#[inline]
fn get_vga_luminance(color: &Color) -> Option<f32> {
match color {
Color::Black => Some(VGA_LUMINANCE[0]),
Color::DarkGrey => Some(VGA_LUMINANCE[1]),
Color::Red => Some(VGA_LUMINANCE[2]),
Color::DarkRed => Some(VGA_LUMINANCE[3]),
Color::Green => Some(VGA_LUMINANCE[4]),
Color::DarkGreen => Some(VGA_LUMINANCE[5]),
Color::Yellow => Some(VGA_LUMINANCE[6]),
Color::DarkYellow => Some(VGA_LUMINANCE[7]),
Color::Blue => Some(VGA_LUMINANCE[8]),
Color::DarkBlue => Some(VGA_LUMINANCE[9]),
Color::Magenta => Some(VGA_LUMINANCE[10]),
Color::DarkMagenta => Some(VGA_LUMINANCE[11]),
Color::Cyan => Some(VGA_LUMINANCE[12]),
Color::DarkCyan => Some(VGA_LUMINANCE[13]),
Color::White => Some(VGA_LUMINANCE[14]),
Color::Grey => Some(VGA_LUMINANCE[15]),
Color::Reset => Some(VGA_LUMINANCE[15]), _ => None,
}
}
pub fn color_to_rgb(color: &Color) -> (u8, u8, u8) {
match color {
Color::Black => (0, 0, 0),
Color::DarkGrey => (85, 85, 85),
Color::Red => (255, 85, 85),
Color::DarkRed => (170, 0, 0),
Color::Green => (85, 255, 85),
Color::DarkGreen => (0, 170, 0),
Color::Yellow => (255, 255, 85),
Color::DarkYellow => (170, 85, 0),
Color::Blue => (85, 85, 255),
Color::DarkBlue => (0, 0, 170),
Color::Magenta => (255, 85, 255),
Color::DarkMagenta => (170, 0, 170),
Color::Cyan => (85, 255, 255),
Color::DarkCyan => (0, 170, 170),
Color::White => (255, 255, 255),
Color::Grey => (170, 170, 170),
Color::Rgb { r, g, b } => (*r, *g, *b),
Color::AnsiValue(value) => {
ansi_to_rgb(*value)
}
Color::Reset => (170, 170, 170), }
}
fn ansi_to_rgb(value: u8) -> (u8, u8, u8) {
match value {
0 => (0, 0, 0),
1 => (170, 0, 0),
2 => (0, 170, 0),
3 => (170, 85, 0),
4 => (0, 0, 170),
5 => (170, 0, 170),
6 => (0, 170, 170),
7 => (170, 170, 170),
8 => (85, 85, 85),
9 => (255, 85, 85),
10 => (85, 255, 85),
11 => (255, 255, 85),
12 => (85, 85, 255),
13 => (255, 85, 255),
14 => (85, 255, 255),
15 => (255, 255, 255),
16..=231 => {
let index = value - 16;
let r = (index / 36) * 51;
let g = ((index % 36) / 6) * 51;
let b = (index % 6) * 51;
(r, g, b)
}
232..=255 => {
let gray = 8 + (value - 232) * 10;
(gray, gray, gray)
}
}
}
#[inline]
pub fn calculate_luminance(color: &Color) -> f32 {
if let Some(luminance) = get_vga_luminance(color) {
return luminance;
}
calculate_luminance_slow(color)
}
#[cold]
fn calculate_luminance_slow(color: &Color) -> f32 {
let (r, g, b) = color_to_rgb(color);
let r = r as f32 / 255.0;
let g = g as f32 / 255.0;
let b = b as f32 / 255.0;
let r = if r <= 0.03928 {
r / 12.92
} else {
((r + 0.055) / 1.055).powf(2.4)
};
let g = if g <= 0.03928 {
g / 12.92
} else {
((g + 0.055) / 1.055).powf(2.4)
};
let b = if b <= 0.03928 {
b / 12.92
} else {
((b + 0.055) / 1.055).powf(2.4)
};
0.2126 * r + 0.7152 * g + 0.0722 * b
}
pub fn calculate_contrast_ratio(fg: &Color, bg: &Color) -> f32 {
let l1 = calculate_luminance(fg);
let l2 = calculate_luminance(bg);
let lighter = l1.max(l2);
let darker = l1.min(l2);
(lighter + 0.05) / (darker + 0.05)
}
pub fn ensure_contrast(fg: Color, bg: Color, min_ratio: f32) -> (Color, Color) {
let current_ratio = calculate_contrast_ratio(&fg, &bg);
if current_ratio >= min_ratio {
return (fg, bg);
}
let bg_luminance = calculate_luminance(&bg);
let white_ratio = calculate_contrast_ratio(&Color::White, &bg);
let black_ratio = calculate_contrast_ratio(&Color::Black, &bg);
let adjusted_fg = if white_ratio > black_ratio {
Color::White
} else {
Color::Black
};
let final_ratio = calculate_contrast_ratio(&adjusted_fg, &bg);
if final_ratio >= min_ratio {
return (adjusted_fg, bg);
}
if bg_luminance < 0.5 {
(Color::White, Color::Black)
} else {
(Color::Black, Color::White)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_color_to_rgb() {
assert_eq!(color_to_rgb(&Color::Black), (0, 0, 0));
assert_eq!(color_to_rgb(&Color::White), (255, 255, 255));
assert_eq!(
color_to_rgb(&Color::Rgb {
r: 128,
g: 64,
b: 32
}),
(128, 64, 32)
);
}
#[test]
fn test_luminance() {
let black_lum = calculate_luminance(&Color::Black);
let white_lum = calculate_luminance(&Color::White);
assert!(black_lum < white_lum);
assert!((0.0..=1.0).contains(&black_lum));
assert!((0.0..=1.0).contains(&white_lum));
}
#[test]
fn test_contrast_ratio() {
let ratio = calculate_contrast_ratio(&Color::Black, &Color::White);
assert!(ratio > 20.0);
let ratio = calculate_contrast_ratio(&Color::Blue, &Color::Blue);
assert!(ratio < 1.1);
}
#[test]
fn test_ensure_contrast() {
let (fg, bg) = ensure_contrast(Color::Blue, Color::Blue, 4.5);
let ratio = calculate_contrast_ratio(&fg, &bg);
assert!(ratio >= 4.5);
let (fg, bg) = ensure_contrast(Color::White, Color::Black, 4.5);
assert!(matches!(fg, Color::White));
assert!(matches!(bg, Color::Black));
}
}