1use bytemuck::{Pod, Zeroable};
4
5#[repr(C)]
11#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
12pub struct Color {
13 pub r: f32,
15 pub g: f32,
17 pub b: f32,
19 pub a: f32,
21}
22
23impl Color {
24 pub const TRANSPARENT: Self = Self::rgba(0.0, 0.0, 0.0, 0.0);
26 pub const BLACK: Self = Self::rgba(0.0, 0.0, 0.0, 1.0);
28 pub const WHITE: Self = Self::rgba(1.0, 1.0, 1.0, 1.0);
30 pub const RED: Self = Self::rgba(1.0, 0.0, 0.0, 1.0);
32 pub const GREEN: Self = Self::rgba(0.0, 1.0, 0.0, 1.0);
34 pub const BLUE: Self = Self::rgba(0.0, 0.0, 1.0, 1.0);
36
37 #[must_use]
39 pub const fn rgb(r: f32, g: f32, b: f32) -> Self {
40 Self { r, g, b, a: 1.0 }
41 }
42
43 #[must_use]
45 pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
46 Self { r, g, b, a }
47 }
48
49 #[must_use]
51 pub fn rgb_u8(r: u8, g: u8, b: u8) -> Self {
52 Self::rgba_u8(r, g, b, 255)
53 }
54
55 #[must_use]
57 pub fn rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self {
58 Self::rgba(
59 f32::from(r) / 255.0,
60 f32::from(g) / 255.0,
61 f32::from(b) / 255.0,
62 f32::from(a) / 255.0,
63 )
64 }
65
66 #[must_use]
68 pub const fn with_alpha(self, a: f32) -> Self {
69 Self { a, ..self }
70 }
71
72 #[must_use]
74 pub fn premultiplied(self) -> Self {
75 Self::rgba(self.r * self.a, self.g * self.a, self.b * self.a, self.a)
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use rstest::rstest;
82
83 use super::*;
84
85 fn approx(a: f32, b: f32) -> bool {
86 (a - b).abs() < f32::EPSILON
87 }
88
89 #[test]
90 fn rgb_sets_alpha_to_one() {
91 let c = Color::rgb(0.5, 0.6, 0.7);
92 assert!(approx(c.a, 1.0));
93 }
94
95 #[rstest]
97 #[case(255, 255, 255, 255, Color::WHITE)]
98 #[case(0, 0, 0, 0, Color::TRANSPARENT)]
99 #[case(0, 0, 0, 255, Color::BLACK)]
100 #[case(255, 0, 0, 255, Color::RED)]
101 #[case(0, 255, 0, 255, Color::GREEN)]
102 #[case(0, 0, 255, 255, Color::BLUE)]
103 fn rgba_u8_matches_constants(
104 #[case] r: u8,
105 #[case] g: u8,
106 #[case] b: u8,
107 #[case] a: u8,
108 #[case] expected: Color,
109 ) {
110 assert_eq!(Color::rgba_u8(r, g, b, a), expected);
111 }
112
113 #[test]
114 fn with_alpha_overrides_only_alpha() {
115 let c = Color::WHITE.with_alpha(0.3);
116 assert!(approx(c.a, 0.3));
117 assert!(approx(c.r, 1.0));
118 assert!(approx(c.g, 1.0));
119 assert!(approx(c.b, 1.0));
120 }
121
122 #[rstest]
123 #[case(Color::WHITE.with_alpha(0.0), Color::TRANSPARENT)]
124 #[case(Color::rgba(0.5, 0.5, 0.5, 1.0), Color::rgba(0.5, 0.5, 0.5, 1.0))]
125 #[case(Color::rgba(1.0, 0.5, 0.0, 0.5), Color::rgba(0.5, 0.25, 0.0, 0.5))]
126 fn premultiplied_cases(#[case] input: Color, #[case] expected: Color) {
127 assert_eq!(input.premultiplied(), expected);
128 }
129}