140 lines
3.4 KiB
Rust
140 lines
3.4 KiB
Rust
#![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<T: ColorNum> {
|
|
pub r: T,
|
|
pub g: T,
|
|
pub b: T,
|
|
pub a: T,
|
|
}
|
|
|
|
impl<T: ColorNum> Color<T> {
|
|
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<T: ColorNum + F32Conversion> Color<T> {
|
|
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<T: F32Conversion>(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<u8> {}
|
|
|
|
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
|
|
}
|
|
}
|