#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl Color {
pub const TRANSPARENT: Self = Self::linear(0.0, 0.0, 0.0, 0.0);
pub const BLACK: Self = Self::linear(0.0, 0.0, 0.0, 1.0);
pub const WHITE: Self = Self::linear(1.0, 1.0, 1.0, 1.0);
pub const fn linear(r: f32, g: f32, b: f32, a: f32) -> Self {
Self { r, g, b, a }
}
pub fn srgb(hex: u32) -> Self {
let channel = |shift: u32| ((hex >> shift) & 0xFF) as u8;
Self::srgb8(channel(16), channel(8), channel(0))
}
pub fn srgb8(r: u8, g: u8, b: u8) -> Self {
Self::linear(to_linear(r), to_linear(g), to_linear(b), 1.0)
}
#[must_use]
pub fn with_alpha(self, alpha: f32) -> Self {
Self {
a: if alpha.is_nan() {
0.0
} else {
alpha.clamp(0.0, 1.0)
},
..self
}
}
pub fn to_srgb_hex(self) -> u32 {
(u32::from(to_srgb8(self.r)) << 16)
| (u32::from(to_srgb8(self.g)) << 8)
| u32::from(to_srgb8(self.b))
}
pub const fn to_array(self) -> [f32; 4] {
[self.r, self.g, self.b, self.a]
}
}
impl From<[f32; 4]> for Color {
fn from([r, g, b, a]: [f32; 4]) -> Self {
Self::linear(r, g, b, a)
}
}
impl From<Color> for [f32; 4] {
fn from(color: Color) -> Self {
color.to_array()
}
}
fn to_linear(channel: u8) -> f32 {
let c = f32::from(channel) / 255.0;
if c <= 0.04045 {
c / 12.92
} else {
((c + 0.055) / 1.055).powf(2.4)
}
}
fn to_srgb8(linear: f32) -> u8 {
let l = linear.clamp(0.0, 1.0);
let c = if l <= 0.003_130_8 {
l * 12.92
} else {
1.055 * l.powf(1.0 / 2.4) - 0.055
};
(c * 255.0).round() as u8
}