#![allow(clippy::multiple_bound_locations)] /// stored in linear for sane manipulation #[repr(C)] #[derive(Clone, Copy, Hash, PartialEq, Eq, bytemuck::Zeroable, Debug)] pub struct Color { pub r: T, pub g: T, pub b: T, pub a: T, } impl Color { pub const BLACK: Self = Self::rgb(T::MIN, T::MIN, T::MIN); pub const WHITE: Self = Self::rgb(T::MAX, T::MAX, T::MAX); pub const GRAY: Self = Self::rgb(T::MID, T::MID, T::MID); pub const RED: Self = Self::rgb(T::MAX, T::MIN, T::MIN); pub const ORANGE: Self = Self::rgb(T::MAX, T::MID, T::MIN); pub const YELLOW: Self = Self::rgb(T::MAX, T::MAX, T::MIN); pub const LIME: Self = Self::rgb(T::MID, T::MAX, T::MIN); pub const GREEN: Self = Self::rgb(T::MIN, T::MAX, T::MIN); pub const TURQUOISE: Self = Self::rgb(T::MIN, T::MAX, T::MID); pub const CYAN: Self = Self::rgb(T::MIN, T::MAX, T::MAX); pub const SKY: Self = Self::rgb(T::MIN, T::MID, T::MAX); pub const BLUE: Self = Self::rgb(T::MIN, T::MIN, T::MAX); pub const PURPLE: Self = Self::rgb(T::MID, T::MIN, T::MAX); pub const MAGENTA: Self = Self::rgb(T::MAX, T::MIN, T::MAX); pub const NONE: Self = Self::new(T::MIN, T::MIN, T::MIN, T::MIN); pub const fn new(r: T, g: T, b: T, a: T) -> Self { Self { r, g, b, a } } pub const fn rgb(r: T, g: T, b: T) -> Self { Self { r, g, b, a: T::MAX } } pub fn alpha(mut self, a: T) -> Self { self.a = a; self } pub fn as_arr(self) -> [T; 4] { [self.r, self.g, self.b, self.a] } } pub const trait F32Conversion { fn to(self) -> f32; fn from(x: f32) -> Self; } pub trait ColorNum { const MIN: Self; const MID: Self; const MAX: Self; } impl Color { pub fn mul_rgb(self, amt: impl F32Conversion) -> Self { let amt = amt.to(); self.map_rgb(|x| T::from(x.to() * amt)) } pub fn add_rgb(self, amt: impl F32Conversion) -> Self { let amt = amt.to(); self.map_rgb(|x| T::from(x.to() + amt)) } pub fn darker(self, amt: f32) -> Self { self.mul_rgb(1.0 - amt) } pub fn brighter(self, amt: f32) -> Self { self.map_rgb(|x| { let x = x.to(); T::from(x + (1.0 - x) * amt) }) } pub fn map_rgb(self, f: impl Fn(T) -> T) -> Self { Self { r: f(self.r), g: f(self.g), b: f(self.b), a: self.a, } } pub fn srgb(r: T, g: T, b: T) -> Self { Self { r: s_to_l(r), g: s_to_l(g), b: s_to_l(b), a: T::MAX, } } } fn s_to_l(x: T) -> T { let x = x.to(); T::from(if x <= 0.0405 { x / 12.92 } else { ((x + 0.055) / 1.055).powf(2.4) }) } impl ColorNum for u8 { const MIN: Self = u8::MIN; const MID: Self = u8::MAX / 2; const MAX: Self = u8::MAX; } impl ColorNum for f32 { const MIN: Self = 0.0; const MID: Self = 0.5; const MAX: Self = 1.0; } unsafe impl bytemuck::Pod for Color {} impl const F32Conversion for f32 { fn to(self) -> f32 { self } fn from(x: f32) -> Self { x } } impl const F32Conversion for u8 { fn to(self) -> f32 { self as f32 / 255.0 } fn from(x: f32) -> Self { (x * 255.0).clamp(0.0, 255.0) as Self } }