1#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
13#[repr(C)]
14pub struct Rgba {
15 pub r: u8,
17 pub g: u8,
19 pub b: u8,
21 pub a: u8,
23}
24
25impl Rgba {
26 pub const TRANSPARENT: Self = Self::new(0, 0, 0, 0);
28 pub const BLACK: Self = Self::new(0, 0, 0, 255);
30 pub const WHITE: Self = Self::new(255, 255, 255, 255);
32 pub const RED: Self = Self::new(255, 0, 0, 255);
34 pub const GREEN: Self = Self::new(0, 255, 0, 255);
36 pub const BLUE: Self = Self::new(0, 0, 255, 255);
38
39 #[must_use]
41 pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
42 Self { r, g, b, a }
43 }
44
45 #[must_use]
47 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
48 Self::new(r, g, b, 255)
49 }
50
51 #[must_use]
53 pub const fn with_alpha(self, a: u8) -> Self {
54 Self::new(self.r, self.g, self.b, a)
55 }
56
57 #[must_use]
59 pub const fn to_array(self) -> [u8; 4] {
60 [self.r, self.g, self.b, self.a]
61 }
62
63 #[must_use]
65 pub const fn from_array(arr: [u8; 4]) -> Self {
66 Self::new(arr[0], arr[1], arr[2], arr[3])
67 }
68
69 #[must_use]
71 pub fn lerp(self, other: Self, t: f32) -> Self {
72 let t = t.clamp(0.0, 1.0);
73 let inv_t = 1.0 - t;
74
75 Self::new(
76 (f32::from(self.r) * inv_t + f32::from(other.r) * t) as u8,
77 (f32::from(self.g) * inv_t + f32::from(other.g) * t) as u8,
78 (f32::from(self.b) * inv_t + f32::from(other.b) * t) as u8,
79 (f32::from(self.a) * inv_t + f32::from(other.a) * t) as u8,
80 )
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Default)]
86pub struct Hsla {
87 pub h: f32,
89 pub s: f32,
91 pub l: f32,
93 pub a: f32,
95}
96
97impl Hsla {
98 #[must_use]
100 pub const fn new(h: f32, s: f32, l: f32, a: f32) -> Self {
101 Self { h, s, l, a }
102 }
103
104 #[must_use]
106 pub const fn hsl(h: f32, s: f32, l: f32) -> Self {
107 Self::new(h, s, l, 1.0)
108 }
109
110 #[must_use]
112 pub fn to_rgba(self) -> Rgba {
113 let h = self.h / 360.0;
114 let s = self.s;
115 let l = self.l;
116
117 let (r, g, b) = if s == 0.0 {
118 (l, l, l)
119 } else {
120 let q = if l < 0.5 { l * (1.0 + s) } else { l + s - l * s };
121 let p = 2.0 * l - q;
122
123 (hue_to_rgb(p, q, h + 1.0 / 3.0), hue_to_rgb(p, q, h), hue_to_rgb(p, q, h - 1.0 / 3.0))
124 };
125
126 Rgba::new((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8, (self.a * 255.0) as u8)
127 }
128}
129
130fn hue_to_rgb(p: f32, q: f32, mut t: f32) -> f32 {
131 if t < 0.0 {
132 t += 1.0;
133 }
134 if t > 1.0 {
135 t -= 1.0;
136 }
137
138 if t < 1.0 / 6.0 {
139 p + (q - p) * 6.0 * t
140 } else if t < 1.0 / 2.0 {
141 q
142 } else if t < 2.0 / 3.0 {
143 p + (q - p) * (2.0 / 3.0 - t) * 6.0
144 } else {
145 p
146 }
147}
148
149impl From<Hsla> for Rgba {
150 fn from(hsla: Hsla) -> Self {
151 hsla.to_rgba()
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn test_rgba_constants() {
161 assert_eq!(Rgba::BLACK, Rgba::rgb(0, 0, 0));
162 assert_eq!(Rgba::WHITE, Rgba::rgb(255, 255, 255));
163 assert_eq!(Rgba::RED.r, 255);
164 assert_eq!(Rgba::GREEN.g, 255);
165 assert_eq!(Rgba::BLUE.b, 255);
166 }
167
168 #[test]
169 fn test_rgba_lerp() {
170 let black = Rgba::BLACK;
171 let white = Rgba::WHITE;
172
173 let mid = black.lerp(white, 0.5);
174 assert_eq!(mid.r, 127);
175 assert_eq!(mid.g, 127);
176 assert_eq!(mid.b, 127);
177 }
178
179 #[test]
180 fn test_hsla_to_rgba() {
181 let red = Hsla::hsl(0.0, 1.0, 0.5).to_rgba();
183 assert_eq!(red.r, 255);
184 assert_eq!(red.g, 0);
185 assert_eq!(red.b, 0);
186
187 let gray = Hsla::hsl(0.0, 0.0, 0.5).to_rgba();
189 assert_eq!(gray.r, 127);
190 assert_eq!(gray.g, 127);
191 assert_eq!(gray.b, 127);
192 }
193
194 #[test]
195 fn test_hsla_to_rgba_low_lightness() {
196 let dark_red = Hsla::hsl(0.0, 1.0, 0.25).to_rgba();
198 assert_eq!(dark_red.r, 127);
199 assert_eq!(dark_red.g, 0);
200 assert_eq!(dark_red.b, 0);
201 }
202
203 #[test]
204 fn test_hsla_to_rgba_high_hue() {
205 let magenta = Hsla::hsl(300.0, 1.0, 0.5).to_rgba();
209 assert!(magenta.r >= 254);
211 assert_eq!(magenta.g, 0);
212 assert!(magenta.b >= 254);
213 }
214
215 #[test]
216 fn test_hsla_to_rgba_cyan() {
217 let cyan = Hsla::hsl(180.0, 1.0, 0.5).to_rgba();
220 assert_eq!(cyan.r, 0);
221 assert!(cyan.g >= 254);
223 assert!(cyan.b >= 254);
224 }
225
226 #[test]
227 fn test_from_hsla_trait() {
228 let hsla = Hsla::hsl(0.0, 1.0, 0.5);
230 let rgba: Rgba = hsla.into();
231 assert_eq!(rgba.r, 255);
232 assert_eq!(rgba.g, 0);
233 assert_eq!(rgba.b, 0);
234 }
235
236 #[test]
237 fn test_rgba_with_alpha() {
238 let red = Rgba::RED;
239 let semi_red = red.with_alpha(128);
240 assert_eq!(semi_red.r, 255);
241 assert_eq!(semi_red.a, 128);
242 }
243
244 #[test]
245 fn test_rgba_to_array_from_array() {
246 let color = Rgba::new(10, 20, 30, 40);
247 let arr = color.to_array();
248 assert_eq!(arr, [10, 20, 30, 40]);
249 let restored = Rgba::from_array(arr);
250 assert_eq!(restored, color);
251 }
252
253 #[test]
254 fn test_hsla_new() {
255 let hsla = Hsla::new(180.0, 0.5, 0.5, 0.8);
256 assert!((hsla.h - 180.0).abs() < f32::EPSILON);
257 assert!((hsla.s - 0.5).abs() < f32::EPSILON);
258 assert!((hsla.l - 0.5).abs() < f32::EPSILON);
259 assert!((hsla.a - 0.8).abs() < f32::EPSILON);
260 }
261
262 #[test]
263 fn test_rgba_default() {
264 let color = Rgba::default();
265 assert_eq!(color, Rgba::new(0, 0, 0, 0));
266 }
267
268 #[test]
269 fn test_hsla_default() {
270 let color = Hsla::default();
271 assert!((color.h - 0.0).abs() < f32::EPSILON);
272 assert!((color.s - 0.0).abs() < f32::EPSILON);
273 }
274
275 #[test]
276 fn test_rgba_transparent() {
277 assert_eq!(Rgba::TRANSPARENT, Rgba::new(0, 0, 0, 0));
278 assert_eq!(Rgba::TRANSPARENT.a, 0);
279 }
280
281 #[test]
282 fn test_lerp_boundaries() {
283 let black = Rgba::BLACK;
284 let white = Rgba::WHITE;
285
286 let at_zero = black.lerp(white, 0.0);
288 assert_eq!(at_zero, black);
289
290 let at_one = black.lerp(white, 1.0);
292 assert_eq!(at_one, white);
293
294 let below = black.lerp(white, -0.5);
296 assert_eq!(below, black);
297
298 let above = black.lerp(white, 1.5);
299 assert_eq!(above, white);
300 }
301}