#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorMode {
Plain,
Ansi16,
Ansi256,
TrueColor,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum Color {
#[default]
Default,
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
Ansi256(u8),
Rgb(u8, u8, u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Resolved {
Default,
Indexed16(u8),
Indexed256(u8),
Rgb(u8, u8, u8),
}
impl Color {
pub(crate) fn resolve(self, mode: ColorMode) -> Resolved {
if mode == ColorMode::Plain {
return Resolved::Default;
}
match self {
Color::Default => Resolved::Default,
Color::Ansi256(index) => match mode {
ColorMode::Ansi16 => {
let (r, g, b) = ansi256_to_rgb(index);
Resolved::Indexed16(rgb_to_16(r, g, b))
}
_ => Resolved::Indexed256(index),
},
Color::Rgb(r, g, b) => match mode {
ColorMode::TrueColor => Resolved::Rgb(r, g, b),
ColorMode::Ansi256 => Resolved::Indexed256(rgb_to_256(r, g, b)),
_ => Resolved::Indexed16(rgb_to_16(r, g, b)),
},
named => Resolved::Indexed16(named.sgr()),
}
}
fn sgr(self) -> u8 {
match self {
Color::Black => 30,
Color::Red => 31,
Color::Green => 32,
Color::Yellow => 33,
Color::Blue => 34,
Color::Magenta => 35,
Color::Cyan => 36,
Color::White => 37,
Color::BrightBlack => 90,
Color::BrightRed => 91,
Color::BrightGreen => 92,
Color::BrightYellow => 93,
Color::BrightBlue => 94,
Color::BrightMagenta => 95,
Color::BrightCyan => 96,
Color::BrightWhite => 97,
_ => 39,
}
}
}
impl Resolved {
pub(crate) fn write_sgr(self, out: &mut String) {
use std::fmt::Write as _;
let _ = match self {
Resolved::Default => write!(out, "\x1b[39m"),
Resolved::Indexed16(code) => write!(out, "\x1b[{code}m"),
Resolved::Indexed256(index) => write!(out, "\x1b[38;5;{index}m"),
Resolved::Rgb(r, g, b) => write!(out, "\x1b[38;2;{r};{g};{b}m"),
};
}
}
const PALETTE16: [(u8, u8, u8); 16] = [
(0, 0, 0),
(205, 0, 0),
(0, 205, 0),
(205, 205, 0),
(0, 0, 238),
(205, 0, 205),
(0, 205, 205),
(229, 229, 229),
(127, 127, 127),
(255, 0, 0),
(0, 255, 0),
(255, 255, 0),
(92, 92, 255),
(255, 0, 255),
(0, 255, 255),
(255, 255, 255),
];
const CUBE: [u8; 6] = [0, 95, 135, 175, 215, 255];
pub(crate) fn rgb_to_256(r: u8, g: u8, b: u8) -> u8 {
if r == g && g == b {
return match r {
0..=3 => 16,
248..=255 => 231,
v => 232 + (v - 8) / 10,
};
}
let axis = |c: u8| match c {
0..=47 => 0u8,
48..=114 => 1,
c => (c - 35) / 40,
};
16 + 36 * axis(r) + 6 * axis(g) + axis(b)
}
pub(crate) fn ansi256_to_rgb(index: u8) -> (u8, u8, u8) {
match index {
0..=15 => PALETTE16[index as usize],
16..=231 => {
let n = index - 16;
(
CUBE[(n / 36) as usize],
CUBE[((n / 6) % 6) as usize],
CUBE[(n % 6) as usize],
)
}
gray => {
let v = 8 + 10 * (gray - 232);
(v, v, v)
}
}
}
pub(crate) fn rgb_to_16(r: u8, g: u8, b: u8) -> u8 {
let mut best = 0usize;
let mut best_distance = u32::MAX;
for (index, &(pr, pg, pb)) in PALETTE16.iter().enumerate() {
let distance = (i32::from(r) - i32::from(pr)).pow(2) as u32
+ (i32::from(g) - i32::from(pg)).pow(2) as u32
+ (i32::from(b) - i32::from(pb)).pow(2) as u32;
if distance < best_distance {
best_distance = distance;
best = index;
}
}
if best < 8 {
30 + best as u8
} else {
90 + (best as u8 - 8)
}
}
#[cfg(test)]
#[path = "tests/color_tests.rs"]
mod tests;