1#[derive(Clone, Copy, Debug, Default, PartialEq)]
4#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
5pub struct Color {
6 pub r: f32,
8 pub g: f32,
10 pub b: f32,
12 pub a: f32,
14}
15
16#[cfg_attr(feature = "live", ravenrs_live::expose)]
17impl Color {
18 pub const TRANSPARENT: Self = Self::rgba(0.0, 0.0, 0.0, 0.0);
20 pub const BLACK: Self = Self::rgb(0.0, 0.0, 0.0);
22 pub const WHITE: Self = Self::rgb(1.0, 1.0, 1.0);
24
25 pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
27 Self { r, g, b, a }
28 }
29
30 pub const fn rgb(r: f32, g: f32, b: f32) -> Self {
32 Self::rgba(r, g, b, 1.0)
33 }
34
35 pub const fn hex(rgb: u32) -> Self {
37 Self::rgb(
38 ((rgb >> 16) & 0xff) as f32 / 255.0,
39 ((rgb >> 8) & 0xff) as f32 / 255.0,
40 (rgb & 0xff) as f32 / 255.0,
41 )
42 }
43
44 pub const fn hex_alpha(rgba: u32) -> Self {
46 Self::rgba(
47 ((rgba >> 24) & 0xff) as f32 / 255.0,
48 ((rgba >> 16) & 0xff) as f32 / 255.0,
49 ((rgba >> 8) & 0xff) as f32 / 255.0,
50 (rgba & 0xff) as f32 / 255.0,
51 )
52 }
53
54 pub const fn faded(self, factor: f32) -> Self {
66 Self::rgba(self.r, self.g, self.b, self.a * factor)
67 }
68
69 pub const fn with_alpha(self, a: f32) -> Self {
71 Self::rgba(self.r, self.g, self.b, a)
72 }
73
74 pub fn lerp(self, other: Self, t: f32) -> Self {
76 let t = t.clamp(0.0, 1.0);
77 Self::rgba(
78 self.r + (other.r - self.r) * t,
79 self.g + (other.g - self.g) * t,
80 self.b + (other.b - self.b) * t,
81 self.a + (other.a - self.a) * t,
82 )
83 }
84
85 pub fn to_linear(self) -> [f32; 4] {
87 fn channel(c: f32) -> f32 {
88 if c <= 0.04045 { c / 12.92 } else { ((c + 0.055) / 1.055).powf(2.4) }
89 }
90 [channel(self.r), channel(self.g), channel(self.b), self.a]
91 }
92
93 pub fn to_array(self) -> [f32; 4] {
95 [self.r, self.g, self.b, self.a]
96 }
97
98 pub fn luminance(self) -> f32 {
100 let [r, g, b, _] = self.to_linear();
101 0.2126 * r + 0.7152 * g + 0.0722 * b
102 }
103
104 pub fn contrast_ratio(self, other: Self) -> f32 {
106 let (a, b) = (self.luminance(), other.luminance());
107 let (hi, lo) = if a > b { (a, b) } else { (b, a) };
108 (hi + 0.05) / (lo + 0.05)
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn hex_parsing() {
118 assert_eq!(Color::hex(0xff0000), Color::rgb(1.0, 0.0, 0.0));
119 let c = Color::hex_alpha(0xffffff80);
120 assert!((c.a - 128.0 / 255.0).abs() < 1e-6);
121 }
122
123 #[test]
124 fn contrast_black_white_is_21() {
125 assert!((Color::BLACK.contrast_ratio(Color::WHITE) - 21.0).abs() < 0.01);
126 }
127
128 #[test]
129 fn lerp_clamps() {
130 assert_eq!(Color::BLACK.lerp(Color::WHITE, 2.0), Color::WHITE);
131 }
132}