ecolor/
rgba.rs

1use crate::Color32;
2
3/// 0-1 linear space `RGBA` color with premultiplied alpha.
4///
5/// See [`crate::Color32`] for explanation of what "premultiplied alpha" means.
6#[repr(C)]
7#[derive(Clone, Copy, Debug, Default, PartialEq)]
8#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
9#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
10pub struct Rgba(pub(crate) [f32; 4]);
11
12impl std::ops::Index<usize> for Rgba {
13    type Output = f32;
14
15    #[inline]
16    fn index(&self, index: usize) -> &f32 {
17        &self.0[index]
18    }
19}
20
21impl std::ops::IndexMut<usize> for Rgba {
22    #[inline]
23    fn index_mut(&mut self, index: usize) -> &mut f32 {
24        &mut self.0[index]
25    }
26}
27
28/// Deterministically hash an `f32`, treating all NANs as equal, and ignoring the sign of zero.
29#[inline]
30pub(crate) fn f32_hash<H: std::hash::Hasher>(state: &mut H, f: f32) {
31    if f == 0.0 {
32        state.write_u8(0);
33    } else if f.is_nan() {
34        state.write_u8(1);
35    } else {
36        use std::hash::Hash as _;
37        f.to_bits().hash(state);
38    }
39}
40
41impl std::hash::Hash for Rgba {
42    #[inline]
43    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
44        crate::f32_hash(state, self.0[0]);
45        crate::f32_hash(state, self.0[1]);
46        crate::f32_hash(state, self.0[2]);
47        crate::f32_hash(state, self.0[3]);
48    }
49}
50
51impl Rgba {
52    pub const TRANSPARENT: Self = Self::from_rgba_premultiplied(0.0, 0.0, 0.0, 0.0);
53    pub const BLACK: Self = Self::from_rgb(0.0, 0.0, 0.0);
54    pub const WHITE: Self = Self::from_rgb(1.0, 1.0, 1.0);
55    pub const RED: Self = Self::from_rgb(1.0, 0.0, 0.0);
56    pub const GREEN: Self = Self::from_rgb(0.0, 1.0, 0.0);
57    pub const BLUE: Self = Self::from_rgb(0.0, 0.0, 1.0);
58
59    #[inline]
60    pub const fn from_rgba_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
61        Self([r, g, b, a])
62    }
63
64    #[inline]
65    pub fn from_rgba_unmultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
66        Self([r * a, g * a, b * a, a])
67    }
68
69    #[inline]
70    pub fn from_srgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
71        Self::from(Color32::from_rgba_premultiplied(r, g, b, a))
72    }
73
74    #[inline]
75    pub fn from_srgba_unmultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
76        Self::from(Color32::from_rgba_unmultiplied(r, g, b, a))
77    }
78
79    #[inline]
80    pub const fn from_rgb(r: f32, g: f32, b: f32) -> Self {
81        Self([r, g, b, 1.0])
82    }
83
84    #[doc(alias = "from_grey")]
85    #[inline]
86    pub const fn from_gray(l: f32) -> Self {
87        Self([l, l, l, 1.0])
88    }
89
90    #[inline]
91    pub fn from_luminance_alpha(l: f32, a: f32) -> Self {
92        debug_assert!(
93            0.0 <= l && l <= 1.0,
94            "l should be in the range [0, 1], but was {l}"
95        );
96        debug_assert!(
97            0.0 <= a && a <= 1.0,
98            "a should be in the range [0, 1], but was {a}"
99        );
100        Self([l * a, l * a, l * a, a])
101    }
102
103    /// Transparent black
104    #[inline]
105    pub fn from_black_alpha(a: f32) -> Self {
106        debug_assert!(
107            0.0 <= a && a <= 1.0,
108            "a should be in the range [0, 1], but was {a}"
109        );
110        Self([0.0, 0.0, 0.0, a])
111    }
112
113    /// Transparent white
114    #[inline]
115    pub fn from_white_alpha(a: f32) -> Self {
116        debug_assert!(0.0 <= a && a <= 1.0, "a: {a}");
117        Self([a, a, a, a])
118    }
119
120    /// Return an additive version of this color (alpha = 0)
121    #[inline]
122    pub fn additive(self) -> Self {
123        let [r, g, b, _] = self.0;
124        Self([r, g, b, 0.0])
125    }
126
127    /// Is the alpha=0 ?
128    #[inline]
129    pub fn is_additive(self) -> bool {
130        self.a() == 0.0
131    }
132
133    /// Multiply with e.g. 0.5 to make us half transparent
134    #[inline]
135    pub fn multiply(self, alpha: f32) -> Self {
136        Self([
137            alpha * self[0],
138            alpha * self[1],
139            alpha * self[2],
140            alpha * self[3],
141        ])
142    }
143
144    #[inline]
145    pub fn r(&self) -> f32 {
146        self.0[0]
147    }
148
149    #[inline]
150    pub fn g(&self) -> f32 {
151        self.0[1]
152    }
153
154    #[inline]
155    pub fn b(&self) -> f32 {
156        self.0[2]
157    }
158
159    #[inline]
160    pub fn a(&self) -> f32 {
161        self.0[3]
162    }
163
164    /// How perceptually intense (bright) is the color?
165    #[inline]
166    pub fn intensity(&self) -> f32 {
167        0.3 * self.r() + 0.59 * self.g() + 0.11 * self.b()
168    }
169
170    /// Returns an opaque version of self
171    #[inline]
172    pub fn to_opaque(&self) -> Self {
173        if self.a() == 0.0 {
174            // Additive or fully transparent black.
175            Self::from_rgb(self.r(), self.g(), self.b())
176        } else {
177            // un-multiply alpha:
178            Self::from_rgb(
179                self.r() / self.a(),
180                self.g() / self.a(),
181                self.b() / self.a(),
182            )
183        }
184    }
185
186    /// Premultiplied RGBA
187    #[inline]
188    pub fn to_array(&self) -> [f32; 4] {
189        [self.r(), self.g(), self.b(), self.a()]
190    }
191
192    /// Premultiplied RGBA
193    #[inline]
194    pub fn to_tuple(&self) -> (f32, f32, f32, f32) {
195        (self.r(), self.g(), self.b(), self.a())
196    }
197
198    /// unmultiply the alpha
199    #[inline]
200    pub fn to_rgba_unmultiplied(&self) -> [f32; 4] {
201        let a = self.a();
202        if a == 0.0 {
203            // Additive, let's assume we are black
204            self.0
205        } else {
206            [self.r() / a, self.g() / a, self.b() / a, a]
207        }
208    }
209
210    /// unmultiply the alpha
211    #[inline]
212    pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
213        crate::Color32::from(*self).to_srgba_unmultiplied()
214    }
215
216    /// Blend two colors in linear space, so that `self` is behind the argument.
217    pub fn blend(self, on_top: Self) -> Self {
218        self.multiply(1.0 - on_top.a()) + on_top
219    }
220}
221
222impl std::ops::Add for Rgba {
223    type Output = Self;
224
225    #[inline]
226    fn add(self, rhs: Self) -> Self {
227        Self([
228            self[0] + rhs[0],
229            self[1] + rhs[1],
230            self[2] + rhs[2],
231            self[3] + rhs[3],
232        ])
233    }
234}
235
236impl std::ops::Mul for Rgba {
237    type Output = Self;
238
239    #[inline]
240    fn mul(self, other: Self) -> Self {
241        Self([
242            self[0] * other[0],
243            self[1] * other[1],
244            self[2] * other[2],
245            self[3] * other[3],
246        ])
247    }
248}
249
250impl std::ops::Mul<f32> for Rgba {
251    type Output = Self;
252
253    #[inline]
254    fn mul(self, factor: f32) -> Self {
255        Self([
256            self[0] * factor,
257            self[1] * factor,
258            self[2] * factor,
259            self[3] * factor,
260        ])
261    }
262}
263
264impl std::ops::Mul<Rgba> for f32 {
265    type Output = Rgba;
266
267    #[inline]
268    fn mul(self, rgba: Rgba) -> Rgba {
269        Rgba([
270            self * rgba[0],
271            self * rgba[1],
272            self * rgba[2],
273            self * rgba[3],
274        ])
275    }
276}
277
278#[cfg(test)]
279mod test {
280
281    use super::*;
282
283    fn test_rgba() -> impl Iterator<Item = [u8; 4]> {
284        [
285            [0, 0, 0, 0],
286            [0, 0, 0, 255],
287            [10, 0, 30, 0],
288            [10, 0, 30, 40],
289            [10, 100, 200, 0],
290            [10, 100, 200, 100],
291            [10, 100, 200, 200],
292            [10, 100, 200, 255],
293            [10, 100, 200, 40],
294            [10, 20, 0, 0],
295            [10, 20, 0, 255],
296            [10, 20, 30, 255],
297            [10, 20, 30, 40],
298            [255, 255, 255, 0],
299            [255, 255, 255, 255],
300        ]
301        .into_iter()
302    }
303
304    #[test]
305    fn test_rgba_blend() {
306        let opaque = Rgba::from_rgb(0.4, 0.5, 0.6);
307        let transparent = Rgba::from_rgb(1.0, 0.5, 0.0).multiply(0.3);
308        assert_eq!(
309            transparent.blend(opaque),
310            opaque,
311            "Opaque on top of transparent"
312        );
313        assert_eq!(
314            opaque.blend(transparent),
315            Rgba::from_rgb(
316                0.7 * 0.4 + 0.3 * 1.0,
317                0.7 * 0.5 + 0.3 * 0.5,
318                0.7 * 0.6 + 0.3 * 0.0
319            ),
320            "Transparent on top of opaque"
321        );
322    }
323
324    #[test]
325    fn test_rgba_roundtrip() {
326        for in_rgba in test_rgba() {
327            let [r, g, b, a] = in_rgba;
328            if a == 0 {
329                continue;
330            }
331            let rgba = Rgba::from_srgba_unmultiplied(r, g, b, a);
332            let out_rgba = rgba.to_srgba_unmultiplied();
333
334            if a == 255 {
335                assert_eq!(in_rgba, out_rgba);
336            } else {
337                // There will be small rounding errors whenever the alpha is not 0 or 255,
338                // because we multiply and then unmultiply the alpha.
339                for (&a, &b) in in_rgba.iter().zip(out_rgba.iter()) {
340                    assert!(a.abs_diff(b) <= 3, "{in_rgba:?} != {out_rgba:?}");
341                }
342            }
343        }
344    }
345}