#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Color {
r: f64,
g: f64,
b: f64,
a: f64,
}
impl Eq for Color {}
impl Color {
#[must_use]
pub fn rgba(r: f64, g: f64, b: f64, a: f64) -> Self {
Self { r, g, b, a }
}
#[must_use]
pub fn rgb(r: f64, g: f64, b: f64) -> Self {
Self::rgba(r, g, b, 1.0)
}
#[must_use]
pub fn transparent() -> Self {
Self::rgba(0.0, 0.0, 0.0, 0.0)
}
#[must_use]
pub fn red(&self) -> f64 {
self.r
}
#[must_use]
pub fn green(&self) -> f64 {
self.g
}
#[must_use]
pub fn blue(&self) -> f64 {
self.b
}
#[must_use]
pub fn alpha(&self) -> f64 {
self.a
}
#[must_use]
pub fn from_hex(hex: &str) -> Option<Self> {
let bytes = hex.as_bytes();
match bytes.len() {
3 => parse_short(bytes, false),
4 => parse_short(bytes, true),
6 => parse_long(bytes, false),
8 => parse_long(bytes, true),
_other => None,
}
}
}
fn parse_short(bytes: &[u8], with_alpha: bool) -> Option<Color> {
let r = hex_digit(*bytes.first()?)?;
let g = hex_digit(*bytes.get(1)?)?;
let b = hex_digit(*bytes.get(2)?)?;
let a = if with_alpha {
hex_digit(*bytes.get(3)?)?
} else {
15
};
Some(Color::rgba(
f64::from(r * 17) / 255.0,
f64::from(g * 17) / 255.0,
f64::from(b * 17) / 255.0,
f64::from(a * 17) / 255.0,
))
}
fn parse_long(bytes: &[u8], with_alpha: bool) -> Option<Color> {
let r = pair(bytes, 0)?;
let g = pair(bytes, 2)?;
let b = pair(bytes, 4)?;
let a = if with_alpha { pair(bytes, 6)? } else { 255 };
Some(Color::rgba(
f64::from(r) / 255.0,
f64::from(g) / 255.0,
f64::from(b) / 255.0,
f64::from(a) / 255.0,
))
}
fn pair(bytes: &[u8], idx: usize) -> Option<u8> {
let hi = hex_digit(*bytes.get(idx)?)?;
let lo = hex_digit(*bytes.get(idx + 1)?)?;
Some(hi * 16 + lo)
}
fn hex_digit(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_other => None,
}
}
#[must_use]
pub fn named(name: &str) -> Option<Color> {
match name.to_ascii_lowercase().as_str() {
"black" => Some(Color::rgb(0.0, 0.0, 0.0)),
"white" => Some(Color::rgb(1.0, 1.0, 1.0)),
"red" => Some(Color::rgb(1.0, 0.0, 0.0)),
"green" => Some(Color::rgb(0.0, 0.5, 0.0)),
"blue" => Some(Color::rgb(0.0, 0.0, 1.0)),
"yellow" => Some(Color::rgb(1.0, 1.0, 0.0)),
"cyan" | "aqua" => Some(Color::rgb(0.0, 1.0, 1.0)),
"magenta" | "fuchsia" => Some(Color::rgb(1.0, 0.0, 1.0)),
"silver" => Some(Color::rgb(0.75, 0.75, 0.75)),
"gray" | "grey" => Some(Color::rgb(0.5, 0.5, 0.5)),
"maroon" => Some(Color::rgb(0.5, 0.0, 0.0)),
"olive" => Some(Color::rgb(0.5, 0.5, 0.0)),
"purple" => Some(Color::rgb(0.5, 0.0, 0.5)),
"teal" => Some(Color::rgb(0.0, 0.5, 0.5)),
"navy" => Some(Color::rgb(0.0, 0.0, 0.5)),
"lime" => Some(Color::rgb(0.0, 1.0, 0.0)),
"orange" => Some(Color::rgb(1.0, 0.647, 0.0)),
"transparent" => Some(Color::transparent()),
_other => None,
}
}