use crate::animations::core::Animatable;
use wide::f32x4;
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl Color {
pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
Self {
r: r.clamp(0.0, 1.0),
g: g.clamp(0.0, 1.0),
b: b.clamp(0.0, 1.0),
a: a.clamp(0.0, 1.0),
}
}
pub fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Color::new(
r as f32 / 255.0,
g as f32 / 255.0,
b as f32 / 255.0,
a as f32 / 255.0,
)
}
pub fn to_rgba(&self) -> (u8, u8, u8, u8) {
(
(self.r * 255.0 + 0.5) as u8,
(self.g * 255.0 + 0.5) as u8,
(self.b * 255.0 + 0.5) as u8,
(self.a * 255.0 + 0.5) as u8,
)
}
}
impl Default for Color {
fn default() -> Self {
Color::new(0.0, 0.0, 0.0, 1.0) }
}
impl std::ops::Add for Color {
type Output = Self;
fn add(self, other: Self) -> Self {
Color::new(
(self.r + other.r).clamp(0.0, 1.0),
(self.g + other.g).clamp(0.0, 1.0),
(self.b + other.b).clamp(0.0, 1.0),
(self.a + other.a).clamp(0.0, 1.0),
)
}
}
impl std::ops::Sub for Color {
type Output = Self;
fn sub(self, other: Self) -> Self {
Color::new(
(self.r - other.r).clamp(0.0, 1.0),
(self.g - other.g).clamp(0.0, 1.0),
(self.b - other.b).clamp(0.0, 1.0),
(self.a - other.a).clamp(0.0, 1.0),
)
}
}
impl std::ops::Mul<f32> for Color {
type Output = Self;
fn mul(self, factor: f32) -> Self {
Color::new(
(self.r * factor).clamp(0.0, 1.0),
(self.g * factor).clamp(0.0, 1.0),
(self.b * factor).clamp(0.0, 1.0),
(self.a * factor).clamp(0.0, 1.0),
)
}
}
impl Animatable for Color {
fn interpolate(&self, target: &Self, t: f32) -> Self {
let a = [self.r, self.g, self.b, self.a];
let b = [target.r, target.g, target.b, target.a];
let va = f32x4::new(a);
let vb = f32x4::new(b);
let vt = f32x4::splat(t.clamp(0.0, 1.0));
let result = va + (vb - va) * vt;
let out = result.to_array();
Color::new(out[0], out[1], out[2], out[3])
}
fn magnitude(&self) -> f32 {
(self.r * self.r + self.g * self.g + self.b * self.b + self.a * self.a).sqrt()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_color_new() {
let color = Color::new(1.0, 0.5, 0.0, 1.0);
assert_eq!(color.r, 1.0);
assert_eq!(color.g, 0.5);
assert_eq!(color.b, 0.0);
assert_eq!(color.a, 1.0);
}
#[test]
fn test_color_from_rgba() {
let color = Color::from_rgba(255, 128, 0, 255);
assert!((color.r - 1.0).abs() < f32::EPSILON);
assert!((color.g - 0.5019608).abs() < 0.000001);
assert!((color.b - 0.0).abs() < f32::EPSILON);
assert!((color.a - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_color_lerp() {
let start = Color::new(0.0, 0.0, 0.0, 1.0);
let end = Color::new(1.0, 1.0, 1.0, 1.0);
let mid = start.interpolate(&end, 0.5);
assert!((mid.r - 0.5).abs() < f32::EPSILON);
assert!((mid.g - 0.5).abs() < f32::EPSILON);
assert!((mid.b - 0.5).abs() < f32::EPSILON);
assert!((mid.a - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_color_to_rgba() {
let color = Color::new(1.0, 0.5, 0.0, 1.0);
let (r, g, b, a) = color.to_rgba();
assert_eq!(r, 255);
assert_eq!(g, 128);
assert_eq!(b, 0);
assert_eq!(a, 255);
}
}