use bytemuck::{Pod, Zeroable};
pub type Rgb = Color<3>;
pub type Rgba = Color<4>;
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct Color<const N: usize>(pub [f32; N]);
impl<const N: usize> Color<N> {
pub fn from_standard(col: [f32; N]) -> Self {
fn to_linear(c: f32) -> f32 {
if c > 0.04045 {
((c + 0.055) / 1.055).powf(2.4)
} else {
c / 12.92
}
}
Self(col.map(to_linear))
}
pub fn from_bytes(col: [u8; N]) -> Self {
Self(col.map(to_f32_color))
}
pub fn from_standard_bytes(col: [u8; N]) -> Self {
Self::from_standard(col.map(to_f32_color))
}
}
impl Color<4> {
pub(crate) fn wgpu(self) -> wgpu::Color {
let [r, g, b, a] = self.0.map(f64::from);
wgpu::Color { r, g, b, a }
}
}
unsafe impl<const N: usize> Zeroable for Color<N> where [f32; N]: Zeroable {}
unsafe impl<const N: usize> Pod for Color<N> where [f32; N]: Pod {}
fn to_f32_color(c: u8) -> f32 {
f32::from(c) / f32::from(u8::MAX)
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Format {
#[default]
SrgbAlpha,
SbgrAlpha,
RgbAlpha,
BgrAlpha,
Depth,
Byte,
}
impl Format {
pub const fn bytes(self) -> u32 {
match self {
Self::SrgbAlpha | Self::SbgrAlpha | Self::RgbAlpha | Self::BgrAlpha | Self::Depth => 4,
Self::Byte => 1,
}
}
pub const fn is_standard(self) -> bool {
matches!(self, Self::SrgbAlpha | Self::SbgrAlpha)
}
pub(crate) const fn wgpu(self) -> wgpu::TextureFormat {
match self {
Self::SrgbAlpha => wgpu::TextureFormat::Rgba8UnormSrgb,
Self::SbgrAlpha => wgpu::TextureFormat::Bgra8UnormSrgb,
Self::RgbAlpha => wgpu::TextureFormat::Rgba8Unorm,
Self::BgrAlpha => wgpu::TextureFormat::Bgra8Unorm,
Self::Depth => wgpu::TextureFormat::Depth32Float,
Self::Byte => wgpu::TextureFormat::R8Uint,
}
}
pub(crate) const fn from_wgpu(format: wgpu::TextureFormat) -> Self {
match format {
wgpu::TextureFormat::Rgba8UnormSrgb => Self::SrgbAlpha,
wgpu::TextureFormat::Bgra8UnormSrgb => Self::SbgrAlpha,
wgpu::TextureFormat::Rgba8Unorm => Self::RgbAlpha,
wgpu::TextureFormat::Bgra8Unorm => Self::BgrAlpha,
wgpu::TextureFormat::Depth32Float => Self::Depth,
wgpu::TextureFormat::R8Uint => Self::Byte,
_ => panic!("unsupported format"),
}
}
}
pub trait ColorExt {
fn rgb_from_bytes(self, rgb: [u8; 3]) -> Rgb;
fn rgba_from_bytes(self, rgba: [u8; 4]) -> Rgba;
}
impl ColorExt for Format {
fn rgb_from_bytes(self, rgb: [u8; 3]) -> Rgb {
if self.is_standard() {
Rgb::from_standard_bytes(rgb)
} else {
Rgb::from_bytes(rgb)
}
}
fn rgba_from_bytes(self, rgba: [u8; 4]) -> Rgba {
if self.is_standard() {
Rgba::from_standard_bytes(rgba)
} else {
Rgba::from_bytes(rgba)
}
}
}