1use bytemuck::{Pod, Zeroable};
8
9#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
11#[repr(C)] pub struct Color {
13 pub r: f32,
15 pub g: f32,
17 pub b: f32,
19 pub a: f32,
22}
23
24impl Color {
25 pub const TRANSPARENT: Self = Self::new(0.0, 0.0, 0.0, 0.0);
27
28 pub const BLACK: Self = Self::new(0.0, 0.0, 0.0, 1.0);
30
31 pub const WHITE: Self = Self::new(1.0, 1.0, 1.0, 1.0);
33
34 pub const RED: Self = Self::new(1.0, 0.0, 0.0, 1.0);
36
37 pub const GREEN: Self = Self::new(0.0, 1.0, 0.0, 1.0);
39
40 pub const BLUE: Self = Self::new(0.0, 0.0, 1.0, 1.0);
42
43 pub const GRAY: Self = Self::new(0.12, 0.12, 0.12, 1.0);
45
46 pub const GREY: Self = Self::GRAY;
48
49 pub const TEAL: Self = Self::new(0.0, 0.5, 0.5, 1.0);
51
52 pub const ORANGE: Self = Self::new(1.0, 0.5, 0.0, 1.0);
54
55 #[inline]
57 #[must_use]
58 pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
59 Self { r, g, b, a }
60 }
61
62 #[inline]
64 #[must_use]
65 pub const fn from_rgb(r: f32, g: f32, b: f32) -> Self {
66 Self { r, g, b, a: 1.0 }
67 }
68
69 #[inline]
71 #[must_use]
72 pub const fn from_rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self {
73 Self {
74 r: r as f32 / 255.0,
75 g: g as f32 / 255.0,
76 b: b as f32 / 255.0,
77 a: a as f32 / 255.0,
78 }
79 }
80
81 #[inline]
83 #[must_use]
84 pub const fn from_rgb_u8(r: u8, g: u8, b: u8) -> Self {
85 Self::from_rgba_u8(r, g, b, 255)
86 }
87
88 #[inline]
90 #[must_use]
91 pub const fn to_array(self) -> [f32; 4] {
92 [self.r, self.g, self.b, self.a]
93 }
94
95 #[inline]
97 #[must_use]
98 pub const fn with_alpha(self, alpha: f32) -> Self {
99 Self {
100 r: self.r,
101 g: self.g,
102 b: self.b,
103 a: alpha,
104 }
105 }
106
107 #[inline]
122 #[must_use]
123 pub fn blend_over(self, overlay: Color, overlay_alpha: f32) -> Self {
124 let source_alpha = overlay.a * overlay_alpha.clamp(0.0, 1.0);
125 let destination_alpha = self.a * (1.0 - source_alpha);
126 let alpha = source_alpha + destination_alpha;
127 if alpha <= 0.0 {
128 return Self::TRANSPARENT;
129 }
130 Self::new(
131 overlay.r.mul_add(source_alpha, self.r * destination_alpha) / alpha,
132 overlay.g.mul_add(source_alpha, self.g * destination_alpha) / alpha,
133 overlay.b.mul_add(source_alpha, self.b * destination_alpha) / alpha,
134 alpha,
135 )
136 }
137 #[inline]
139 #[must_use]
140 pub fn lerp(&self, other: &Self, t: f32) -> Self {
141 let t = t.clamp(0.0, 1.0);
142 Self {
143 r: (other.r - self.r).mul_add(t, self.r),
144 g: (other.g - self.g).mul_add(t, self.g),
145 b: (other.b - self.b).mul_add(t, self.b),
146 a: (other.a - self.a).mul_add(t, self.a),
147 }
148 }
149}
150
151impl Default for Color {
153 #[inline]
154 fn default() -> Self {
155 Self::TRANSPARENT
156 }
157}
158
159impl From<[f32; 4]> for Color {
161 #[inline]
162 fn from([r, g, b, a]: [f32; 4]) -> Self {
163 Self { r, g, b, a }
164 }
165}
166
167impl From<Color> for [f32; 4] {
169 #[inline]
170 fn from(color: Color) -> Self {
171 [color.r, color.g, color.b, color.a]
172 }
173}
174
175impl From<[f32; 3]> for Color {
177 #[inline]
178 fn from([r, g, b]: [f32; 3]) -> Self {
179 Self { r, g, b, a: 1.0 }
180 }
181}
182
183impl From<[u8; 4]> for Color {
185 #[inline]
186 fn from([r, g, b, a]: [u8; 4]) -> Self {
187 Self::from_rgba_u8(r, g, b, a)
188 }
189}
190
191impl From<[u8; 3]> for Color {
193 #[inline]
194 fn from([r, g, b]: [u8; 3]) -> Self {
195 Self::from_rgb_u8(r, g, b)
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::Color;
202
203 fn assert_color(actual: Color, expected: Color) {
204 for (actual, expected) in actual.to_array().into_iter().zip(expected.to_array()) {
205 assert!(
206 (actual - expected).abs() <= 1.0e-6,
207 "{actual} != {expected}"
208 );
209 }
210 }
211
212 #[test]
213 fn state_layer_over_a_transparent_container_keeps_its_rgb() {
214 let _runtime_session = crate::RuntimeSession::new();
215 let _runtime_scope = _runtime_session.enter();
216 let layer = Color::new(0.2, 0.4, 0.8, 1.0);
217 assert_color(
218 Color::TRANSPARENT.blend_over(layer, 0.1),
219 layer.with_alpha(0.1),
220 );
221 assert_color(
222 Color::new(1.0, 0.0, 0.0, 0.0).blend_over(layer, 0.1),
223 layer.with_alpha(0.1),
224 );
225 }
226
227 #[test]
228 fn source_over_combines_both_alphas_instead_of_interpolating_them() {
229 let _runtime_session = crate::RuntimeSession::new();
230 let _runtime_scope = _runtime_session.enter();
231 assert_color(
232 Color::RED
233 .with_alpha(0.5)
234 .blend_over(Color::BLUE.with_alpha(0.5), 0.5),
235 Color::new(0.6, 0.0, 0.4, 0.625),
236 );
237 assert_color(
238 Color::RED.blend_over(Color::BLUE.with_alpha(0.5), 0.5),
239 Color::new(0.75, 0.0, 0.25, 1.0),
240 );
241 }
242
243 #[test]
244 fn transparent_and_opaque_source_endpoints_are_preserved() {
245 let _runtime_session = crate::RuntimeSession::new();
246 let _runtime_scope = _runtime_session.enter();
247 let base = Color::RED.with_alpha(0.4);
248 assert_color(base.blend_over(Color::BLUE, 0.0), base);
249 assert_color(base.blend_over(Color::BLUE.with_alpha(0.0), 1.0), base);
250 assert_color(base.blend_over(Color::BLUE, 1.0), Color::BLUE);
251 assert_color(
252 Color::TRANSPARENT.blend_over(Color::TRANSPARENT, 1.0),
253 Color::TRANSPARENT,
254 );
255 }
256}