1#[derive(Clone, Copy, Debug, PartialEq)]
6pub struct Color {
7 pub r: f32,
8 pub g: f32,
9 pub b: f32,
10 pub a: f32,
11}
12
13impl Color {
14 pub const TRANSPARENT: Self = Self::rgba(0.0, 0.0, 0.0, 0.0);
15 pub const BLACK: Self = Self::rgb(0.0, 0.0, 0.0);
16 pub const WHITE: Self = Self::rgb(1.0, 1.0, 1.0);
17 pub const RED: Self = Self::rgb(1.0, 0.0, 0.0);
18 pub const GREEN: Self = Self::rgb(0.0, 1.0, 0.0);
19 pub const BLUE: Self = Self::rgb(0.0, 0.0, 1.0);
20
21 #[must_use]
22 pub const fn rgb(r: f32, g: f32, b: f32) -> Self {
23 Self { r, g, b, a: 1.0 }
24 }
25
26 #[must_use]
27 pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
28 Self { r, g, b, a }
29 }
30
31 #[must_use]
33 pub fn from_rgb8(r: u8, g: u8, b: u8) -> Self {
34 Self::rgb(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0)
35 }
36
37 #[must_use]
38 pub fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
39 Self::rgba(
40 r as f32 / 255.0,
41 g as f32 / 255.0,
42 b as f32 / 255.0,
43 a as f32 / 255.0,
44 )
45 }
46
47 #[must_use]
49 pub fn from_hex(hex: u32) -> Self {
50 if hex > 0xFFFFFF {
51 Self::from_rgba8(
52 ((hex >> 24) & 0xFF) as u8,
53 ((hex >> 16) & 0xFF) as u8,
54 ((hex >> 8) & 0xFF) as u8,
55 (hex & 0xFF) as u8,
56 )
57 } else {
58 Self::from_rgb8(
59 ((hex >> 16) & 0xFF) as u8,
60 ((hex >> 8) & 0xFF) as u8,
61 (hex & 0xFF) as u8,
62 )
63 }
64 }
65
66 #[must_use]
67 pub fn with_alpha(self, a: f32) -> Self {
68 Self { a, ..self }
69 }
70
71 #[must_use]
72 pub fn is_opaque(self) -> bool {
73 self.a >= 1.0
74 }
75
76 #[must_use]
77 pub fn is_transparent(self) -> bool {
78 self.a <= 0.0
79 }
80
81 #[must_use]
83 pub fn lerp(self, other: Self, t: f32) -> Self {
84 Self {
85 r: self.r + (other.r - self.r) * t,
86 g: self.g + (other.g - self.g) * t,
87 b: self.b + (other.b - self.b) * t,
88 a: self.a + (other.a - self.a) * t,
89 }
90 }
91
92 #[must_use]
94 pub fn to_rgba8(self) -> [u8; 4] {
95 [
96 (self.r.clamp(0.0, 1.0) * 255.0 + 0.5) as u8,
97 (self.g.clamp(0.0, 1.0) * 255.0 + 0.5) as u8,
98 (self.b.clamp(0.0, 1.0) * 255.0 + 0.5) as u8,
99 (self.a.clamp(0.0, 1.0) * 255.0 + 0.5) as u8,
100 ]
101 }
102}
103
104impl Default for Color {
105 fn default() -> Self {
106 Self::BLACK
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn from_rgb8_roundtrip() {
116 let c = Color::from_rgb8(128, 64, 255);
117 let [r, g, b, a] = c.to_rgba8();
118 assert_eq!(r, 128);
119 assert_eq!(g, 64);
120 assert_eq!(b, 255);
121 assert_eq!(a, 255);
122 }
123
124 #[test]
125 fn from_hex_rgb() {
126 let c = Color::from_hex(0xFF8000);
127 let [r, g, b, _] = c.to_rgba8();
128 assert_eq!(r, 255);
129 assert_eq!(g, 128);
130 assert_eq!(b, 0);
131 }
132
133 #[test]
134 fn from_hex_rgba() {
135 let c = Color::from_hex(0xFF800080);
136 let [r, g, b, a] = c.to_rgba8();
137 assert_eq!(r, 255);
138 assert_eq!(g, 128);
139 assert_eq!(b, 0);
140 assert_eq!(a, 128);
141 }
142
143 #[test]
144 fn with_alpha() {
145 let c = Color::RED.with_alpha(0.5);
146 assert_eq!(c.r, 1.0);
147 assert!((c.a - 0.5).abs() < f32::EPSILON);
148 }
149
150 #[test]
151 fn lerp_midpoint() {
152 let mid = Color::BLACK.lerp(Color::WHITE, 0.5);
153 assert!((mid.r - 0.5).abs() < f32::EPSILON);
154 assert!((mid.g - 0.5).abs() < f32::EPSILON);
155 assert!((mid.b - 0.5).abs() < f32::EPSILON);
156 }
157
158 #[test]
159 fn opaque_and_transparent() {
160 assert!(Color::RED.is_opaque());
161 assert!(!Color::RED.is_transparent());
162 assert!(Color::TRANSPARENT.is_transparent());
163 assert!(!Color::TRANSPARENT.is_opaque());
164 }
165}