#[cfg(feature = "with_bytemuck")]
use bytemuck::Pod;
#[cfg(feature = "with_bytemuck")]
use bytemuck::Zeroable;
use crate::Vec4;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with_speedy", derive(speedy::Writable, speedy::Readable))]
#[repr(transparent)]
pub struct ColorRgba8(pub [u8; 4]);
impl From<ColorRgba8> for u32 {
fn from(c: ColorRgba8) -> Self {
Self::from(c.0[0]) << 24
| Self::from(c.0[1]) << 16
| Self::from(c.0[2]) << 8
| Self::from(c.0[3])
}
}
impl From<u32> for ColorRgba8 {
fn from(c: u32) -> Self {
Self([(c >> 24) as u8, (c >> 16) as u8, (c >> 8) as u8, c as u8])
}
}
impl From<[u8; 4]> for ColorRgba8 {
fn from(srgba: [u8; 4]) -> Self {
Self(srgba)
}
}
impl From<ColorRgba8> for [u8; 4] {
fn from(srgba: ColorRgba8) -> Self {
srgba.0
}
}
impl From<Vec4> for ColorRgba8 {
fn from(v: Vec4) -> Self {
let a = v.w; Self([
srgb_byte_from_linear(v.x),
srgb_byte_from_linear(v.y),
srgb_byte_from_linear(v.z),
if a > 1.0 {
255
} else if a <= 0.0 {
0
} else {
(a * 255.0).round() as u8
},
])
}
}
impl From<[f32; 4]> for ColorRgba8 {
fn from(c: [f32; 4]) -> Self {
let a = c[3]; Self([
srgb_byte_from_linear(c[0]),
srgb_byte_from_linear(c[1]),
srgb_byte_from_linear(c[2]),
if a > 1.0 {
255
} else if a <= 0.0 {
0
} else {
(a * 255.0).round() as u8
},
])
}
}
impl From<ColorRgba8> for Vec4 {
fn from(c: ColorRgba8) -> Self {
Self::new(
linear_from_srgb_byte(c.0[0]),
linear_from_srgb_byte(c.0[1]),
linear_from_srgb_byte(c.0[2]),
f32::from(c.0[3]) / 255.0,
)
}
}
#[inline]
fn linear_from_srgb_byte(s: u8) -> f32 {
if s <= 10 {
f32::from(s) / 3_294.6
} else {
((f32::from(s) + 14.025) / 269.025).powf(2.4)
}
}
#[inline]
fn srgb_byte_from_linear(l: f32) -> u8 {
if l <= 0.0 {
0
} else if l <= 0.003_130_8 {
(3_294.6 * l).round() as u8
} else if l <= 1.0 {
(269.025 * l.powf(1.0 / 2.4) - 14.025).round() as u8
} else {
255
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn color_convert() {
let colors = [
ColorRgba8([255, 0, 127, 1]),
ColorRgba8([1, 2, 3, 4]),
ColorRgba8([0, 0, 0, 0]),
ColorRgba8([255, 254, 253, 252]),
];
for c in &colors {
let b = u32::from(*c);
assert_eq!(*c, ColorRgba8::from(b));
let v = Vec4::from(*c);
assert!(v.min_element() >= 0.0 && v.max_element() <= 1.0); assert_eq!(*c, ColorRgba8::from(v));
}
assert_eq!(
ColorRgba8::from([-5.0, 0.0, 1.0, 100.0]),
ColorRgba8([0, 0, 255, 255])
);
assert_eq!(
ColorRgba8::from([100.0, 0.0, 0.1, 1.0]),
ColorRgba8([255, 0, srgb_byte_from_linear(0.1), 255])
);
assert_eq!(
ColorRgba8::from([100.0, 0.0, -0.1, -0.1]),
ColorRgba8([255, 0, 0, 0])
);
}
#[test]
fn test_srgba() {
#![allow(clippy::float_cmp)]
assert_eq!(linear_from_srgb_byte(0), 0.0);
assert!(linear_from_srgb_byte(1) > 0.0);
assert!(linear_from_srgb_byte(254) < 1.0);
assert_eq!(linear_from_srgb_byte(255), 1.0);
assert_eq!(srgb_byte_from_linear(-1.0), 0);
assert_eq!(srgb_byte_from_linear(0.0), 0);
assert_eq!(srgb_byte_from_linear(1.0), 255);
assert_eq!(srgb_byte_from_linear(1.1), 255);
assert_eq!(srgb_byte_from_linear(2.0), 255);
for b in 0..=255_u8 {
let l = linear_from_srgb_byte(b);
assert!((0.0..=1.0).contains(&l));
assert_eq!(srgb_byte_from_linear(l), b);
}
}
}
#[cfg(feature = "with_bytemuck")]
unsafe impl Pod for ColorRgba8 {}
#[cfg(feature = "with_bytemuck")]
unsafe impl Zeroable for ColorRgba8 {}