use crate::{
algebra::{Vector3, Vector4},
reflect::prelude::*,
visitor::{Visit, VisitResult, Visitor},
};
use num_traits::Zero;
use std::ops::{Add, AddAssign, Sub, SubAssign};
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Eq, Visit, Reflect)]
#[repr(C)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl Default for Color {
#[inline]
fn default() -> Self {
Self::WHITE
}
}
impl Into<u32> for Color {
#[inline]
fn into(self) -> u32 {
((self.a as u32) << 24) | ((self.b as u32) << 16) | ((self.g as u32) << 8) | (self.r as u32)
}
}
impl From<Vector3<f32>> for Color {
fn from(v: Vector3<f32>) -> Self {
Self {
r: (v.x.clamp(0.0, 1.0) * 255.0) as u8,
g: (v.y.clamp(0.0, 1.0) * 255.0) as u8,
b: (v.z.clamp(0.0, 1.0) * 255.0) as u8,
a: 255,
}
}
}
impl From<Vector4<f32>> for Color {
fn from(v: Vector4<f32>) -> Self {
Self {
r: (v.x.clamp(0.0, 1.0) * 255.0) as u8,
g: (v.y.clamp(0.0, 1.0) * 255.0) as u8,
b: (v.z.clamp(0.0, 1.0) * 255.0) as u8,
a: (v.w.clamp(0.0, 1.0) * 255.0) as u8,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Hsv {
hue: f32,
saturation: f32,
brightness: f32,
}
impl Hsv {
#[inline]
pub fn new(hue: f32, saturation: f32, brightness: f32) -> Self {
Self {
hue: hue.clamp(0.0, 360.0),
saturation: saturation.clamp(0.0, 100.0),
brightness: brightness.clamp(0.0, 100.0),
}
}
#[inline]
pub fn hue(&self) -> f32 {
self.hue
}
#[inline]
pub fn set_hue(&mut self, hue: f32) {
self.hue = hue.clamp(0.0, 360.0);
}
#[inline]
pub fn saturation(&self) -> f32 {
self.saturation
}
#[inline]
pub fn set_saturation(&mut self, saturation: f32) {
self.saturation = saturation.clamp(0.0, 100.0);
}
#[inline]
pub fn brightness(&self) -> f32 {
self.brightness
}
#[inline]
pub fn set_brightness(&mut self, brightness: f32) {
self.brightness = brightness.clamp(0.0, 100.0);
}
}
impl From<Color> for Hsv {
#[inline]
fn from(color: Color) -> Self {
let r = color.r as f32 / 255.0;
let g = color.g as f32 / 255.0;
let b = color.b as f32 / 255.0;
let max = r.max(g).max(b);
let min = r.min(g).min(b);
let hue = if max.eq(&min) {
0.0 } else if max.eq(&r) && g >= b {
60.0 * (g - b) / (max - min)
} else if max.eq(&r) && g < b {
60.0 * (g - b) / (max - min) + 360.0
} else if max.eq(&g) {
60.0 * (b - r) / (max - min) + 120.0
} else if max.eq(&b) {
60.0 * (r - g) / (max - min) + 240.0
} else {
0.0 };
let saturation = if max.eq(&0.0) { 0.0 } else { 1.0 - min / max };
let brightness = max;
Self {
hue,
saturation: saturation * 100.0,
brightness: brightness * 100.0,
}
}
}
impl From<Hsv> for Color {
#[inline]
fn from(hsv: Hsv) -> Self {
let hi = ((hsv.hue / 60.0) % 6.0) as i32;
let vmin = ((100.0 - hsv.saturation) * hsv.brightness) / 100.0;
let a = (hsv.brightness - vmin) * ((hsv.hue % 60.0) / 60.0);
let vinc = vmin + a;
let vdec = hsv.brightness - a;
Self::from(
match hi {
0 => Vector3::new(hsv.brightness, vinc, vmin),
1 => Vector3::new(vdec, hsv.brightness, vmin),
2 => Vector3::new(vmin, hsv.brightness, vinc),
3 => Vector3::new(vmin, vdec, hsv.brightness),
4 => Vector3::new(vinc, vmin, hsv.brightness),
5 => Vector3::new(hsv.brightness, vmin, vdec),
_ => unreachable!(),
}
.scale(1.0 / 100.0),
)
}
}
#[derive(PartialEq, Debug, Copy, Clone)]
pub struct Hsl {
hue: f32,
saturation: f32,
lightness: f32,
}
impl Hsl {
pub fn new(hue: f32, saturation: f32, lightness: f32) -> Self {
Self {
hue: hue.abs() % 360.0,
saturation: saturation.clamp(0.0, 1.0),
lightness: lightness.clamp(0.0, 1.0),
}
}
pub fn hue(&self) -> f32 {
self.hue
}
pub fn set_hue(&mut self, hue: f32) {
self.hue = hue.abs() % 360.0;
}
pub fn saturation(&self) -> f32 {
self.saturation
}
pub fn set_saturation(&mut self, saturation: f32) {
self.saturation = saturation.clamp(0.0, 1.0)
}
pub fn lightness(&self) -> f32 {
self.lightness
}
pub fn set_lightness(&mut self, lightness: f32) {
self.lightness = lightness.clamp(0.0, 1.0)
}
}
impl From<Hsl> for Color {
#[allow(clippy::manual_range_contains)]
fn from(v: Hsl) -> Self {
let h = v.hue;
let s = v.saturation;
let l = v.lightness;
let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
let m = l - c / 2.0;
let (r, g, b) = if h >= 0.0 && h < 60.0 {
(c, x, 0.0)
} else if h >= 60.0 && h < 120.0 {
(x, c, 0.0)
} else if h >= 120.0 && h < 180.0 {
(0.0, c, x)
} else if h >= 180.0 && h < 240.0 {
(0.0, x, c)
} else if h >= 240.0 && h < 300.0 {
(x, 0.0, c)
} else if h >= 300.0 && h < 360.0 {
(c, 0.0, x)
} else {
(0.0, 0.0, 0.0)
};
Color::from(Vector4::new(r + m, g + m, b + m, 1.0))
}
}
impl From<Color> for Hsl {
fn from(v: Color) -> Self {
let f = v.as_frgb();
let r = f.x;
let g = f.y;
let b = f.z;
let cmax = r.max(g).max(b);
let cmin = r.min(g).min(b);
let d = cmax - cmin;
let h = if d.is_zero() {
0.0
} else if cmax.eq(&r) {
let k = 60.0 * (((g - b) / d) % 6.0);
if g >= b {
k
} else {
k + 360.0
}
} else if cmax.eq(&g) {
60.0 * ((b - r) / d + 2.0)
} else if cmax.eq(&b) {
60.0 * ((r - g) / d + 4.0)
} else {
0.0
};
let l = (cmax + cmin) / 2.0;
let s = if d.is_zero() {
0.0
} else {
d / (1.0 - (2.0 * l - 1.0).abs())
};
Hsl {
hue: h,
saturation: s,
lightness: l,
}
}
}
impl Color {
pub const WHITE: Self = Self {
r: 255,
g: 255,
b: 255,
a: 255,
};
pub const BLACK: Self = Self {
r: 0,
g: 0,
b: 0,
a: 255,
};
pub const RED: Self = Self {
r: 255,
g: 0,
b: 0,
a: 255,
};
pub const GREEN: Self = Self {
r: 0,
g: 255,
b: 0,
a: 255,
};
pub const BLUE: Self = Self {
r: 0,
g: 0,
b: 255,
a: 255,
};
pub const TRANSPARENT: Self = Self {
r: 0,
g: 0,
b: 0,
a: 0,
};
pub const ORANGE: Self = Self {
r: 255,
g: 69,
b: 0,
a: 255,
};
#[inline]
pub const fn opaque(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b, a: 255 }
}
#[inline]
pub const fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a }
}
#[must_use]
#[inline]
pub fn srgb_to_linear(self) -> Self {
let r = ((self.r as f32 / 255.0).powf(2.2).clamp(0.0, 1.0) * 255.0) as u8;
let g = ((self.g as f32 / 255.0).powf(2.2).clamp(0.0, 1.0) * 255.0) as u8;
let b = ((self.b as f32 / 255.0).powf(2.2).clamp(0.0, 1.0) * 255.0) as u8;
Self::from_rgba(r, g, b, self.a)
}
#[must_use]
#[inline]
pub fn srgb_to_linear_f32(self) -> Vector4<f32> {
let r = (self.r as f32 / 255.0).powf(2.2).clamp(0.0, 1.0);
let g = (self.g as f32 / 255.0).powf(2.2).clamp(0.0, 1.0);
let b = (self.b as f32 / 255.0).powf(2.2).clamp(0.0, 1.0);
Vector4::new(r, g, b, self.a as f32 / 255.0)
}
#[must_use]
#[inline]
pub fn linear_to_srgb(self) -> Self {
let r = ((self.r as f32 / 255.0).powf(1.0 / 2.2).clamp(0.0, 1.0) * 255.0) as u8;
let g = ((self.g as f32 / 255.0).powf(1.0 / 2.2).clamp(0.0, 1.0) * 255.0) as u8;
let b = ((self.b as f32 / 255.0).powf(1.0 / 2.2).clamp(0.0, 1.0) * 255.0) as u8;
Self::from_rgba(r, g, b, self.a)
}
#[inline]
pub fn as_frgba(self) -> Vector4<f32> {
Vector4::new(
f32::from(self.r) / 255.0,
f32::from(self.g) / 255.0,
f32::from(self.b) / 255.0,
f32::from(self.a) / 255.0,
)
}
#[inline]
pub fn as_frgb(self) -> Vector3<f32> {
Vector3::new(
f32::from(self.r) / 255.0,
f32::from(self.g) / 255.0,
f32::from(self.b) / 255.0,
)
}
#[inline]
pub fn to_opaque(self) -> Self {
Self {
r: self.r,
g: self.g,
b: self.b,
a: 255,
}
}
#[inline]
pub fn lerp(self, other: Self, t: f32) -> Self {
let dr = (t * (i32::from(other.r) - i32::from(self.r)) as f32) as i32;
let dg = (t * (i32::from(other.g) - i32::from(self.g)) as f32) as i32;
let db = (t * (i32::from(other.b) - i32::from(self.b)) as f32) as i32;
let da = (t * (i32::from(other.a) - i32::from(self.a)) as f32) as i32;
let red = (i32::from(self.r) + dr) as u8;
let green = (i32::from(self.g) + dg) as u8;
let blue = (i32::from(self.b) + db) as u8;
let alpha = (i32::from(self.a) + da) as u8;
Self {
r: red,
g: green,
b: blue,
a: alpha,
}
}
#[inline]
pub fn with_new_alpha(self, a: u8) -> Self {
Self {
r: self.r,
g: self.g,
b: self.b,
a,
}
}
}
impl Add for Color {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
r: self.r.saturating_add(rhs.r),
g: self.g.saturating_add(rhs.g),
b: self.b.saturating_add(rhs.b),
a: self.a.saturating_add(rhs.a),
}
}
}
impl AddAssign for Color {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl Sub for Color {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
r: self.r.saturating_sub(rhs.r),
g: self.g.saturating_sub(rhs.g),
b: self.b.saturating_sub(rhs.b),
a: self.a.saturating_sub(rhs.a),
}
}
}
impl SubAssign for Color {
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
#[cfg(test)]
mod test {
use crate::color::{Color, Hsl};
#[test]
fn test_hsl() {
assert_eq!(Color::from(Hsl::new(0.0, 0.0, 0.0)), Color::opaque(0, 0, 0));
assert_eq!(
Color::from(Hsl::new(0.0, 0.0, 1.0)),
Color::opaque(255, 255, 255)
);
assert_eq!(
Color::from(Hsl::new(0.0, 1.0, 0.5)),
Color::opaque(255, 0, 0)
);
assert_eq!(
Color::from(Hsl::new(120.0, 1.0, 0.5)),
Color::opaque(0, 255, 0)
);
assert_eq!(
Color::from(Hsl::new(240.0, 1.0, 0.5)),
Color::opaque(0, 0, 255)
);
assert_eq!(
Color::from(Hsl::new(60.0, 1.0, 0.5)),
Color::opaque(255, 255, 0)
);
assert_eq!(
Color::from(Hsl::new(180.0, 1.0, 0.5)),
Color::opaque(0, 255, 255)
);
assert_eq!(
Color::from(Hsl::new(300.0, 1.0, 0.5)),
Color::opaque(255, 0, 255)
);
assert_eq!(
Color::from(Hsl::new(0.0, 0.0, 0.75)),
Color::opaque(191, 191, 191)
);
assert_eq!(Hsl::from(Color::opaque(0, 0, 0)), Hsl::new(0.0, 0.0, 0.0));
assert_eq!(
Hsl::from(Color::opaque(255, 255, 255)),
Hsl::new(0.0, 0.0, 1.0)
);
assert_eq!(Hsl::from(Color::opaque(255, 0, 0)), Hsl::new(0.0, 1.0, 0.5));
assert_eq!(
Hsl::from(Color::opaque(0, 255, 0)),
Hsl::new(120.0, 1.0, 0.5)
);
assert_eq!(
Hsl::from(Color::opaque(0, 0, 255)),
Hsl::new(240.0, 1.0, 0.5)
);
assert_eq!(
Hsl::from(Color::opaque(255, 255, 0)),
Hsl::new(60.0, 1.0, 0.5)
);
assert_eq!(
Hsl::from(Color::opaque(0, 255, 255)),
Hsl::new(180.0, 1.0, 0.5)
);
assert_eq!(
Hsl::from(Color::opaque(255, 0, 255)),
Hsl::new(300.0, 1.0, 0.5)
);
assert_eq!(
Hsl::from(Color::opaque(191, 191, 191)),
Hsl::new(0.0, 0.0, 0.7490196)
);
}
}