#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BaseColor {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
}
impl BaseColor {
pub fn dark(self) -> Color {
Color::Dark(self)
}
pub fn light(self) -> Color {
Color::Light(self)
}
}
impl From<u8> for BaseColor {
fn from(n: u8) -> Self {
match n % 8 {
0 => BaseColor::Black,
1 => BaseColor::Red,
2 => BaseColor::Green,
3 => BaseColor::Yellow,
4 => BaseColor::Blue,
5 => BaseColor::Magenta,
6 => BaseColor::Cyan,
7 => BaseColor::White,
_ => unreachable!(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Color {
TerminalDefault,
Dark(BaseColor),
Light(BaseColor),
Rgb(u8, u8, u8),
RgbLowRes(u8, u8, u8),
}
impl Color {
pub fn from_256colors(n: u8) -> Self {
if n < 8 {
Color::Dark(BaseColor::from(n))
} else if n < 16 {
Color::Light(BaseColor::from(n))
} else if n >= 232 {
let n = n - 232;
let value = 8 + 10 * n;
Color::Rgb(value, value, value)
} else {
let n = n - 16;
assert!(n < 216);
let r = n / 36;
let g = (n % 36) / 6;
let b = n % 6;
assert!(r < 6);
assert!(g < 6);
assert!(b < 6);
Color::RgbLowRes(r, g, b)
}
}
pub fn low_res(r: u8, g: u8, b: u8) -> Option<Self> {
if r <= 5 && g <= 5 && b <= 5 {
Some(Color::RgbLowRes(r, g, b))
} else {
None
}
}
pub fn parse(value: &str) -> Option<Self> {
Some(match value {
"dark black" | "black" => Color::Dark(BaseColor::Black),
"dark red" | "red" => Color::Dark(BaseColor::Red),
"dark green" | "green" => Color::Dark(BaseColor::Green),
"dark yellow" | "yellow" => Color::Dark(BaseColor::Yellow),
"dark blue" | "blue" => Color::Dark(BaseColor::Blue),
"dark magenta" | "magenta" => Color::Dark(BaseColor::Magenta),
"dark cyan" | "cyan" => Color::Dark(BaseColor::Cyan),
"dark white" | "white" => Color::Dark(BaseColor::White),
"light black" => Color::Light(BaseColor::Black),
"light red" => Color::Light(BaseColor::Red),
"light green" => Color::Light(BaseColor::Green),
"light yellow" => Color::Light(BaseColor::Yellow),
"light blue" => Color::Light(BaseColor::Blue),
"light magenta" => Color::Light(BaseColor::Magenta),
"light cyan" => Color::Light(BaseColor::Cyan),
"light white" => Color::Light(BaseColor::White),
"default" => Color::TerminalDefault,
value => {
return parse_special(value).or_else(|| {
log::warn!("Could not parse color `{}`.", value);
None
})
}
})
}
}
fn parse_special(value: &str) -> Option<Color> {
if value.starts_with('#') {
parse_hex(&value[1..])
} else if value.starts_with("0x") {
parse_hex(&value[2..])
} else if value.len() == 6 {
parse_hex(value)
} else if value.len() == 3 {
let rgb: Vec<_> =
value.chars().map(|c| c as i16 - '0' as i16).collect();
assert_eq!(rgb.len(), 3);
if rgb.iter().all(|&i| i >= 0 && i < 6) {
Some(Color::RgbLowRes(rgb[0] as u8, rgb[1] as u8, rgb[2] as u8))
} else {
None
}
} else {
None
}
}
fn parse_hex(value: &str) -> Option<Color> {
let (l, multiplier) = match value.len() {
6 => (2, 1),
3 => (1, 17),
_ => return None,
};
let r = load_hex(&value[0..l]) * multiplier;
let g = load_hex(&value[l..2 * l]) * multiplier;
let b = load_hex(&value[2 * l..3 * l]) * multiplier;
Some(Color::Rgb(r as u8, g as u8, b as u8))
}
fn load_hex(s: &str) -> u16 {
s.chars()
.rev()
.filter_map(|c| {
Some(match c {
'0'..='9' => c as u16 - '0' as u16,
'a'..='f' => c as u16 - 'a' as u16 + 10,
'A'..='F' => c as u16 - 'A' as u16 + 10,
other => {
log::warn!(
"Invalid character `{}` in hexadecimal value `{}`.",
other,
s
);
return None;
}
})
})
.enumerate()
.map(|(i, c)| c * 16u16.pow(i as u32))
.sum()
}
#[cfg(test)]
mod tests {
use super::Color;
#[test]
fn test_256_colors() {
for i in 0..=255u8 {
Color::from_256colors(i);
}
}
#[test]
fn test_parse() {
assert_eq!(Color::parse("#fff"), Some(Color::Rgb(255, 255, 255)));
assert_eq!(
Color::parse("#abcdef"),
Some(Color::Rgb(0xab, 0xcd, 0xef))
);
assert_eq!(
Color::parse("0xFEDCBA"),
Some(Color::Rgb(0xfe, 0xdc, 0xba))
);
}
#[test]
fn test_low_res() {
for r in 0..=5 {
for g in 0..=5 {
for b in 0..=5 {
assert!(
Color::low_res(r, g, b).is_some(),
"Could not create lowres color {}:{}:{}",
r,
g,
b,
);
}
}
}
for r in 6..=10 {
for g in 6..=10 {
for b in 6..=10 {
assert_eq!(
Color::low_res(r, g, b),
None,
"Created invalid lowres color {}:{}:{}",
r,
g,
b,
);
}
}
}
}
}