use crate::platform::math::f32::abs;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Copy, Clone, Default, PartialEq)]
#[repr(C)]
pub struct Color {
pub red: f32,
pub green: f32,
pub blue: f32,
pub alpha: f32,
}
impl Color {
pub const fn rgba(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
Self {
red,
green,
blue,
alpha,
}
}
pub fn rgba8(red: u8, green: u8, blue: u8, alpha: u8) -> Self {
const RECIP_255: f32 = 1.0 / 255.0;
Self {
red: RECIP_255 * red as f32,
green: RECIP_255 * green as f32,
blue: RECIP_255 * blue as f32,
alpha: RECIP_255 * alpha as f32,
}
}
pub fn to_rgba8(&self) -> [u8; 4] {
[
(255.0 * self.red + 0.5) as u8,
(255.0 * self.green + 0.5) as u8,
(255.0 * self.blue + 0.5) as u8,
(255.0 * self.alpha + 0.5) as u8,
]
}
pub const fn transparent() -> Self {
Self {
red: 0.0,
green: 0.0,
blue: 0.0,
alpha: 0.0,
}
}
pub const fn white() -> Self {
Self {
red: 1.0,
green: 1.0,
blue: 1.0,
alpha: 1.0,
}
}
pub const fn black() -> Self {
Self {
red: 0.0,
green: 0.0,
blue: 0.0,
alpha: 1.0,
}
}
pub const fn to_array(&self) -> [f32; 4] {
[self.red, self.green, self.blue, self.alpha]
}
pub fn hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Self {
const RECIP_60: f32 = 1.0 / 60.0;
let c = (1.0 - abs(2.0 * lightness - 1.0)) * saturation;
let x = c * (1.0 - abs((hue * RECIP_60) % 2.0 - 1.0));
let m = lightness - 0.5 * c;
let (red, green, blue) = if hue < 60.0 {
(m + c, m + x, m)
} else if hue < 120.0 {
(m + x, m + c, m)
} else if hue < 180.0 {
(m, m + c, m + x)
} else if hue < 240.0 {
(m, m + x, m + c)
} else if hue < 300.0 {
(m + x, m, m + c)
} else {
(m + c, m, m + x)
};
Self {
red,
green,
blue,
alpha,
}
}
pub fn hsva(hue: f32, saturation: f32, value: f32, alpha: f32) -> Self {
const RECIP_60: f32 = 1.0 / 60.0;
let c = value * saturation;
let x = c * (1.0 - abs((hue * RECIP_60) % 2.0 - 1.0));
let m = value - c;
let (red, green, blue) = if hue < 60.0 {
(m + c, m + x, m)
} else if hue < 120.0 {
(m + x, m + c, m)
} else if hue < 180.0 {
(m, m + c, m + x)
} else if hue < 240.0 {
(m, m + x, m + c)
} else if hue < 300.0 {
(m + x, m, m + c)
} else {
(m + c, m, m + x)
};
Self {
red,
green,
blue,
alpha,
}
}
pub fn to_hsva(&self) -> [f32; 4] {
let [r, g, b, a] = self.to_array();
let (shift, max, diff, min) = if r > g {
if r > b {
if b > g {
(360.0, r, g - b, g)
} else {
(0.0, r, g - b, b)
}
} else {
(240.0, b, r - g, g)
}
} else if b > g {
(240.0, b, r - g, r)
} else {
(120.0, g, b - r, if r > b { b } else { r })
};
let delta = max - min;
let hue = if delta == 0.0 {
0.0
} else {
60.0 * diff / delta + shift
};
let saturation = if max == 0.0 { 0.0 } else { delta / max };
let value = max;
[hue, saturation, value, a]
}
}
impl From<[f32; 4]> for Color {
fn from([red, green, blue, alpha]: [f32; 4]) -> Self {
Self {
red,
green,
blue,
alpha,
}
}
}
impl From<Color> for [f32; 4] {
fn from(c: Color) -> Self {
c.to_array()
}
}
impl From<[u8; 4]> for Color {
fn from([red, green, blue, alpha]: [u8; 4]) -> Self {
Self::rgba8(red, green, blue, alpha)
}
}
impl Serialize for Color {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.to_array().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Color {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let rgba = <[f32; 4]>::deserialize(deserializer)?;
Ok(rgba.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(clippy::float_cmp)]
#[test]
fn to_hsva() {
assert_eq!(
Color::from([1.0, 0.0, 0.0, 1.0]).to_hsva(),
[0.0, 1.0, 1.0, 1.0],
);
assert_eq!(
Color::from([1.0, 1.0, 0.0, 1.0]).to_hsva(),
[60.0, 1.0, 1.0, 1.0],
);
assert_eq!(
Color::from([0.0, 1.0, 0.0, 1.0]).to_hsva(),
[120.0, 1.0, 1.0, 1.0],
);
assert_eq!(
Color::from([0.0, 1.0, 1.0, 1.0]).to_hsva(),
[180.0, 1.0, 1.0, 1.0],
);
assert_eq!(
Color::from([0.0, 0.0, 1.0, 1.0]).to_hsva(),
[240.0, 1.0, 1.0, 1.0],
);
assert_eq!(
Color::from([1.0, 0.0, 1.0, 1.0]).to_hsva(),
[300.0, 1.0, 1.0, 1.0],
);
}
#[test]
fn from_hsva() {
assert_eq!(Color::hsva(0.0, 1.0, 1.0, 1.0).to_rgba8(), [255, 0, 0, 255]);
assert_eq!(
Color::hsva(60.0, 1.0, 1.0, 1.0).to_rgba8(),
[255, 255, 0, 255],
);
assert_eq!(
Color::hsva(120.0, 1.0, 1.0, 1.0).to_rgba8(),
[0, 255, 0, 255],
);
assert_eq!(
Color::hsva(180.0, 1.0, 1.0, 1.0).to_rgba8(),
[0, 255, 255, 255],
);
assert_eq!(
Color::hsva(240.0, 1.0, 1.0, 1.0).to_rgba8(),
[0, 0, 255, 255],
);
assert_eq!(
Color::hsva(300.0, 1.0, 1.0, 1.0).to_rgba8(),
[255, 0, 255, 255],
);
}
}