#![allow(clippy::many_single_char_names)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl Color {
pub const BLACK: Self = Self {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
pub const WHITE: Self = Self {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
};
pub const TRANSPARENT: Self = Self {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
};
#[must_use]
pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
let mut c = Self { r, g, b, a };
c.clamp();
c
}
pub fn set(&mut self, r: f32, g: f32, b: f32, a: f32) -> &mut Self {
self.r = r;
self.g = g;
self.b = b;
self.a = a;
self.clamp()
}
pub fn set_rgb(&mut self, r: f32, g: f32, b: f32) -> &mut Self {
self.r = r;
self.g = g;
self.b = b;
self.clamp()
}
pub fn set_from(&mut self, other: &Color) -> &mut Self {
self.r = other.r;
self.g = other.g;
self.b = other.b;
self.a = other.a;
self.clamp()
}
pub fn add(&mut self, r: f32, g: f32, b: f32, a: f32) -> &mut Self {
self.r += r;
self.g += g;
self.b += b;
self.a += a;
self.clamp()
}
pub fn add_rgb(&mut self, r: f32, g: f32, b: f32) -> &mut Self {
self.r += r;
self.g += g;
self.b += b;
self.clamp()
}
pub fn add_color(&mut self, other: &Color) -> &mut Self {
self.r += other.r;
self.g += other.g;
self.b += other.b;
self.a += other.a;
self.clamp()
}
pub fn clamp(&mut self) -> &mut Self {
self.r = self.r.clamp(0.0, 1.0);
self.g = self.g.clamp(0.0, 1.0);
self.b = self.b.clamp(0.0, 1.0);
self.a = self.a.clamp(0.0, 1.0);
self
}
}
impl Default for Color {
fn default() -> Self {
Self {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use proptest::prelude::*;
#[test]
fn default_matches_spine_cpp() {
let c = Color::default();
assert_eq!(
c,
Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0
}
);
}
#[test]
fn new_clamps_out_of_range() {
let c = Color::new(-0.5, 2.0, 0.3, 1.5);
assert_relative_eq!(c.r, 0.0);
assert_relative_eq!(c.g, 1.0);
assert_relative_eq!(c.b, 0.3);
assert_relative_eq!(c.a, 1.0);
}
#[test]
fn set_clamps() {
let mut c = Color::WHITE;
c.set(2.0, -1.0, 0.5, 10.0);
assert_eq!(
c,
Color {
r: 1.0,
g: 0.0,
b: 0.5,
a: 1.0
}
);
}
#[test]
fn set_rgb_preserves_alpha() {
let mut c = Color::new(0.1, 0.2, 0.3, 0.4);
c.set_rgb(0.9, 0.8, 0.7);
assert_relative_eq!(c.r, 0.9);
assert_relative_eq!(c.g, 0.8);
assert_relative_eq!(c.b, 0.7);
assert_relative_eq!(c.a, 0.4);
}
#[test]
fn add_clamps_overflow() {
let mut c = Color::new(0.8, 0.8, 0.8, 0.8);
c.add(0.5, 0.5, 0.5, 0.5);
assert_eq!(c, Color::WHITE);
}
#[test]
fn add_clamps_underflow() {
let mut c = Color::new(0.2, 0.2, 0.2, 0.2);
c.add(-0.5, -0.5, -0.5, -0.5);
assert_eq!(
c,
Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0
}
);
}
#[test]
fn add_rgb_preserves_alpha() {
let mut c = Color::new(0.1, 0.1, 0.1, 0.5);
c.add_rgb(0.2, 0.2, 0.2);
assert_relative_eq!(c.a, 0.5);
assert_relative_eq!(c.r, 0.3);
}
#[test]
fn add_color_matches_componentwise() {
let mut c = Color::new(0.1, 0.2, 0.3, 0.4);
let d = Color::new(0.2, 0.2, 0.2, 0.2);
c.add_color(&d);
assert_relative_eq!(c.r, 0.3);
assert_relative_eq!(c.g, 0.4);
assert_relative_eq!(c.b, 0.5);
assert_relative_eq!(c.a, 0.6);
}
proptest! {
#[test]
fn new_always_in_unit_range(
r in -10.0f32..10.0, g in -10.0f32..10.0,
b in -10.0f32..10.0, a in -10.0f32..10.0,
) {
let c = Color::new(r, g, b, a);
prop_assert!((0.0..=1.0).contains(&c.r));
prop_assert!((0.0..=1.0).contains(&c.g));
prop_assert!((0.0..=1.0).contains(&c.b));
prop_assert!((0.0..=1.0).contains(&c.a));
}
#[test]
fn add_preserves_unit_range(
r0 in 0.0f32..=1.0, g0 in 0.0f32..=1.0, b0 in 0.0f32..=1.0, a0 in 0.0f32..=1.0,
dr in -2.0f32..2.0, dg in -2.0f32..2.0, db in -2.0f32..2.0, da in -2.0f32..2.0,
) {
let mut c = Color::new(r0, g0, b0, a0);
c.add(dr, dg, db, da);
prop_assert!((0.0..=1.0).contains(&c.r));
prop_assert!((0.0..=1.0).contains(&c.g));
prop_assert!((0.0..=1.0).contains(&c.b));
prop_assert!((0.0..=1.0).contains(&c.a));
}
}
}