use oxideav_core::Rgba;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl Color {
pub const TRANSPARENT: Self = Self {
r: 0,
g: 0,
b: 0,
a: 0,
};
pub const BLACK: Self = Self::rgb(0, 0, 0);
pub const WHITE: Self = Self::rgb(255, 255, 255);
#[inline]
pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b, a: 255 }
}
#[inline]
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a }
}
pub fn from_hex(s: &str) -> Option<Self> {
let s = s.strip_prefix('#').unwrap_or(s);
let h = |i: usize| u8::from_str_radix(&s[i..i + 2], 16).ok();
let n = |i: usize| u8::from_str_radix(&s[i..i + 1], 16).ok().map(|v| v * 17);
match s.len() {
3 => Some(Self::rgb(n(0)?, n(1)?, n(2)?)),
4 => Some(Self::rgba(n(0)?, n(1)?, n(2)?, n(3)?)),
6 => Some(Self::rgb(h(0)?, h(2)?, h(4)?)),
8 => Some(Self::rgba(h(0)?, h(2)?, h(4)?, h(6)?)),
_ => None,
}
}
#[inline]
pub const fn with_alpha(self, a: u8) -> Self {
Self { a, ..self }
}
pub fn mix(self, other: Color, t: f64) -> Color {
let t = t.clamp(0.0, 1.0);
let lerp = |a: u8, b: u8| (a as f64 + (b as f64 - a as f64) * t).round() as u8;
Color {
r: lerp(self.r, other.r),
g: lerp(self.g, other.g),
b: lerp(self.b, other.b),
a: lerp(self.a, other.a),
}
}
pub fn lighten(self, amount: f64) -> Color {
self.mix(Color::rgb(255, 255, 255).with_alpha(self.a), amount)
}
pub fn darken(self, amount: f64) -> Color {
self.mix(Color::rgb(0, 0, 0).with_alpha(self.a), amount)
}
pub fn luminance(self) -> f64 {
(0.2126 * self.r as f64 + 0.7152 * self.g as f64 + 0.0722 * self.b as f64) / 255.0
}
pub fn on_color(self) -> Color {
if self.luminance() > 0.5 {
Color::BLACK
} else {
Color::WHITE
}
}
#[inline]
pub const fn to_oxideav(self) -> Rgba {
Rgba {
r: self.r,
g: self.g,
b: self.b,
a: self.a,
}
}
}
impl From<Color> for Rgba {
#[inline]
fn from(c: Color) -> Rgba {
c.to_oxideav()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hex_parsing() {
assert_eq!(Color::from_hex("#ff0000"), Some(Color::rgb(255, 0, 0)));
assert_eq!(Color::from_hex("0f0"), Some(Color::rgb(0, 255, 0)));
assert_eq!(
Color::from_hex("#11223344"),
Some(Color::rgba(0x11, 0x22, 0x33, 0x44))
);
assert_eq!(Color::from_hex("#abc"), Some(Color::rgb(0xaa, 0xbb, 0xcc)));
assert_eq!(Color::from_hex("zzz"), None);
assert_eq!(Color::from_hex("#12345"), None);
}
#[test]
fn mix_lighten_darken() {
let c = Color::rgb(100, 100, 100);
assert_eq!(
c.mix(Color::rgb(200, 200, 200), 0.5),
Color::rgb(150, 150, 150)
);
assert_eq!(c.mix(Color::rgb(200, 200, 200), 0.0), c);
assert_eq!(
Color::rgb(100, 100, 100).lighten(0.5),
Color::rgb(178, 178, 178)
);
assert_eq!(
Color::rgb(100, 100, 100).darken(0.5),
Color::rgb(50, 50, 50)
);
}
#[test]
fn on_color_contrasts() {
assert_eq!(Color::WHITE.on_color(), Color::BLACK);
assert_eq!(Color::rgb(20, 20, 20).on_color(), Color::WHITE);
}
}