ecolor/
color32.rs

1use crate::{Rgba, fast_round, linear_f32_from_linear_u8};
2
3/// This format is used for space-efficient color representation (32 bits).
4///
5/// Instead of manipulating this directly it is often better
6/// to first convert it to either [`Rgba`] or [`crate::Hsva`].
7///
8/// Internally this uses 0-255 gamma space `sRGBA` color with _premultiplied alpha_.
9///
10/// It's the non-linear ("gamma") values that are multiplied with the alpha.
11///
12/// Premultiplied alpha means that the color values have been pre-multiplied with the alpha (opacity).
13/// This is in contrast with "normal" RGBA, where the alpha is _separate_ (or "unmultiplied").
14/// Using premultiplied alpha has some advantages:
15/// * It allows encoding additive colors
16/// * It is the better way to blend colors, e.g. when filtering texture colors
17/// * Because the above, it is the better way to encode colors in a GPU texture
18///
19/// The color space is assumed to be [sRGB](https://en.wikipedia.org/wiki/SRGB).
20///
21/// All operations on `Color32` are done in "gamma space" (see <https://en.wikipedia.org/wiki/SRGB>).
22/// This is not physically correct, but it is fast and sometimes more perceptually even than linear space.
23/// If you instead want to perform these operations in linear-space color, use [`Rgba`].
24///
25/// An `alpha=0` means the color is to be treated as an additive color.
26#[repr(C)]
27#[derive(Clone, Copy, Default, Eq, Hash, PartialEq)]
28#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
29#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
30pub struct Color32(pub(crate) [u8; 4]);
31
32impl std::fmt::Debug for Color32 {
33    /// Prints the contents with premultiplied alpha!
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        let [r, g, b, a] = self.0;
36        write!(f, "#{r:02X}_{g:02X}_{b:02X}_{a:02X}")
37    }
38}
39
40impl std::ops::Index<usize> for Color32 {
41    type Output = u8;
42
43    #[inline]
44    fn index(&self, index: usize) -> &u8 {
45        &self.0[index]
46    }
47}
48
49impl std::ops::IndexMut<usize> for Color32 {
50    #[inline]
51    fn index_mut(&mut self, index: usize) -> &mut u8 {
52        &mut self.0[index]
53    }
54}
55
56impl Color32 {
57    // Mostly follows CSS names:
58
59    pub const TRANSPARENT: Self = Self::from_rgba_premultiplied(0, 0, 0, 0);
60    pub const BLACK: Self = Self::from_rgb(0, 0, 0);
61    #[doc(alias = "DARK_GREY")]
62    pub const DARK_GRAY: Self = Self::from_rgb(96, 96, 96);
63    #[doc(alias = "GREY")]
64    pub const GRAY: Self = Self::from_rgb(160, 160, 160);
65    #[doc(alias = "LIGHT_GREY")]
66    pub const LIGHT_GRAY: Self = Self::from_rgb(220, 220, 220);
67    pub const WHITE: Self = Self::from_rgb(255, 255, 255);
68
69    pub const BROWN: Self = Self::from_rgb(165, 42, 42);
70    pub const DARK_RED: Self = Self::from_rgb(0x8B, 0, 0);
71    pub const RED: Self = Self::from_rgb(255, 0, 0);
72    pub const LIGHT_RED: Self = Self::from_rgb(255, 128, 128);
73
74    pub const CYAN: Self = Self::from_rgb(0, 255, 255);
75    pub const MAGENTA: Self = Self::from_rgb(255, 0, 255);
76    pub const YELLOW: Self = Self::from_rgb(255, 255, 0);
77
78    pub const ORANGE: Self = Self::from_rgb(255, 165, 0);
79    pub const LIGHT_YELLOW: Self = Self::from_rgb(255, 255, 0xE0);
80    pub const KHAKI: Self = Self::from_rgb(240, 230, 140);
81
82    pub const DARK_GREEN: Self = Self::from_rgb(0, 0x64, 0);
83    pub const GREEN: Self = Self::from_rgb(0, 255, 0);
84    pub const LIGHT_GREEN: Self = Self::from_rgb(0x90, 0xEE, 0x90);
85
86    pub const DARK_BLUE: Self = Self::from_rgb(0, 0, 0x8B);
87    pub const BLUE: Self = Self::from_rgb(0, 0, 255);
88    pub const LIGHT_BLUE: Self = Self::from_rgb(0xAD, 0xD8, 0xE6);
89
90    pub const PURPLE: Self = Self::from_rgb(0x80, 0, 0x80);
91
92    pub const GOLD: Self = Self::from_rgb(255, 215, 0);
93
94    pub const DEBUG_COLOR: Self = Self::from_rgba_premultiplied(0, 200, 0, 128);
95
96    /// An ugly color that is planned to be replaced before making it to the screen.
97    ///
98    /// This is an invalid color, in that it does not correspond to a valid multiplied color,
99    /// nor to an additive color.
100    ///
101    /// This is used as a special color key,
102    /// i.e. often taken to mean "no color".
103    pub const PLACEHOLDER: Self = Self::from_rgba_premultiplied(64, 254, 0, 128);
104
105    /// From RGB with alpha of 255 (opaque).
106    #[inline]
107    pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
108        Self([r, g, b, 255])
109    }
110
111    /// From RGB into an additive color (will make everything it blend with brighter).
112    #[inline]
113    pub const fn from_rgb_additive(r: u8, g: u8, b: u8) -> Self {
114        Self([r, g, b, 0])
115    }
116
117    /// From `sRGBA` with premultiplied alpha.
118    ///
119    /// You likely want to use [`Self::from_rgba_unmultiplied`] instead.
120    #[inline]
121    pub const fn from_rgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
122        Self([r, g, b, a])
123    }
124
125    /// From `sRGBA` with separate alpha.
126    ///
127    /// This is a "normal" RGBA value that you would find in a color picker or a table somewhere.
128    ///
129    /// You can use [`Self::to_srgba_unmultiplied`] to get back these values,
130    /// but for transparent colors what you get back might be slightly different (rounding errors).
131    #[inline]
132    pub fn from_rgba_unmultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
133        use std::sync::OnceLock;
134        match a {
135            // common-case optimization:
136            0 => Self::TRANSPARENT,
137
138            // common-case optimization:
139            255 => Self::from_rgb(r, g, b),
140
141            a => {
142                static LOOKUP_TABLE: OnceLock<Box<[u8]>> = OnceLock::new();
143                let lut = LOOKUP_TABLE.get_or_init(|| {
144                    (0..=u16::MAX)
145                        .map(|i| {
146                            let [value, alpha] = i.to_ne_bytes();
147                            fast_round(value as f32 * linear_f32_from_linear_u8(alpha))
148                        })
149                        .collect()
150                });
151
152                let [r, g, b] =
153                    [r, g, b].map(|value| lut[usize::from(u16::from_ne_bytes([value, a]))]);
154                Self::from_rgba_premultiplied(r, g, b, a)
155            }
156        }
157    }
158
159    /// Opaque gray.
160    #[doc(alias = "from_grey")]
161    #[inline]
162    pub const fn from_gray(l: u8) -> Self {
163        Self([l, l, l, 255])
164    }
165
166    /// Black with the given opacity.
167    #[inline]
168    pub const fn from_black_alpha(a: u8) -> Self {
169        Self([0, 0, 0, a])
170    }
171
172    /// White with the given opacity.
173    #[inline]
174    pub fn from_white_alpha(a: u8) -> Self {
175        Self([a, a, a, a])
176    }
177
178    /// Additive white.
179    #[inline]
180    pub const fn from_additive_luminance(l: u8) -> Self {
181        Self([l, l, l, 0])
182    }
183
184    #[inline]
185    pub const fn is_opaque(&self) -> bool {
186        self.a() == 255
187    }
188
189    /// Red component multiplied by alpha.
190    #[inline]
191    pub const fn r(&self) -> u8 {
192        self.0[0]
193    }
194
195    /// Green component multiplied by alpha.
196    #[inline]
197    pub const fn g(&self) -> u8 {
198        self.0[1]
199    }
200
201    /// Blue component multiplied by alpha.
202    #[inline]
203    pub const fn b(&self) -> u8 {
204        self.0[2]
205    }
206
207    /// Alpha (opacity).
208    #[inline]
209    pub const fn a(&self) -> u8 {
210        self.0[3]
211    }
212
213    /// Returns an opaque version of self
214    #[inline]
215    pub fn to_opaque(self) -> Self {
216        Rgba::from(self).to_opaque().into()
217    }
218
219    /// Returns an additive version of self
220    #[inline]
221    pub const fn additive(self) -> Self {
222        let [r, g, b, _] = self.to_array();
223        Self([r, g, b, 0])
224    }
225
226    /// Is the alpha=0 ?
227    #[inline]
228    pub fn is_additive(self) -> bool {
229        self.a() == 0
230    }
231
232    /// Premultiplied RGBA
233    #[inline]
234    pub const fn to_array(&self) -> [u8; 4] {
235        [self.r(), self.g(), self.b(), self.a()]
236    }
237
238    /// Premultiplied RGBA
239    #[inline]
240    pub const fn to_tuple(&self) -> (u8, u8, u8, u8) {
241        (self.r(), self.g(), self.b(), self.a())
242    }
243
244    /// Convert to a normal "unmultiplied" RGBA color (i.e. with separate alpha).
245    ///
246    /// This will unmultiply the alpha.
247    ///
248    /// This is the inverse of [`Self::from_rgba_unmultiplied`],
249    /// but due to precision problems it may return slightly different values for transparent colors.
250    #[inline]
251    pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
252        let [r, g, b, a] = self.to_array();
253        match a {
254            // Common-case optimization.
255            0 | 255 => self.to_array(),
256            a => {
257                let factor = 255.0 / a as f32;
258                let r = fast_round(factor * r as f32);
259                let g = fast_round(factor * g as f32);
260                let b = fast_round(factor * b as f32);
261                [r, g, b, a]
262            }
263        }
264    }
265
266    /// Multiply with 0.5 to make color half as opaque, perceptually.
267    ///
268    /// Fast multiplication in gamma-space.
269    ///
270    /// This is perceptually even, and faster that [`Self::linear_multiply`].
271    #[inline]
272    pub fn gamma_multiply(self, factor: f32) -> Self {
273        debug_assert!(
274            0.0 <= factor && factor.is_finite(),
275            "factor should be finite, but was {factor}"
276        );
277        let Self([r, g, b, a]) = self;
278        Self([
279            (r as f32 * factor + 0.5) as u8,
280            (g as f32 * factor + 0.5) as u8,
281            (b as f32 * factor + 0.5) as u8,
282            (a as f32 * factor + 0.5) as u8,
283        ])
284    }
285
286    /// Multiply with 127 to make color half as opaque, perceptually.
287    ///
288    /// Fast multiplication in gamma-space.
289    ///
290    /// This is perceptually even, and faster that [`Self::linear_multiply`].
291    #[inline]
292    pub fn gamma_multiply_u8(self, factor: u8) -> Self {
293        let Self([r, g, b, a]) = self;
294        let factor = factor as u32;
295        Self([
296            ((r as u32 * factor + 127) / 255) as u8,
297            ((g as u32 * factor + 127) / 255) as u8,
298            ((b as u32 * factor + 127) / 255) as u8,
299            ((a as u32 * factor + 127) / 255) as u8,
300        ])
301    }
302
303    /// Multiply with 0.5 to make color half as opaque in linear space.
304    ///
305    /// This is using linear space, which is not perceptually even.
306    /// You likely want to use [`Self::gamma_multiply`] instead.
307    #[inline]
308    pub fn linear_multiply(self, factor: f32) -> Self {
309        debug_assert!(
310            0.0 <= factor && factor.is_finite(),
311            "factor should be finite, but was {factor}"
312        );
313        // As an unfortunate side-effect of using premultiplied alpha
314        // we need a somewhat expensive conversion to linear space and back.
315        Rgba::from(self).multiply(factor).into()
316    }
317
318    /// Converts to floating point values in the range 0-1 without any gamma space conversion.
319    ///
320    /// Use this with great care! In almost all cases, you want to convert to [`crate::Rgba`] instead
321    /// in order to obtain linear space color values.
322    #[inline]
323    pub fn to_normalized_gamma_f32(self) -> [f32; 4] {
324        let Self([r, g, b, a]) = self;
325        [
326            r as f32 / 255.0,
327            g as f32 / 255.0,
328            b as f32 / 255.0,
329            a as f32 / 255.0,
330        ]
331    }
332
333    /// Lerp this color towards `other` by `t` in gamma space.
334    pub fn lerp_to_gamma(&self, other: Self, t: f32) -> Self {
335        use emath::lerp;
336
337        Self::from_rgba_premultiplied(
338            fast_round(lerp((self[0] as f32)..=(other[0] as f32), t)),
339            fast_round(lerp((self[1] as f32)..=(other[1] as f32), t)),
340            fast_round(lerp((self[2] as f32)..=(other[2] as f32), t)),
341            fast_round(lerp((self[3] as f32)..=(other[3] as f32), t)),
342        )
343    }
344
345    /// Blend two colors in gamma space, so that `self` is behind the argument.
346    pub fn blend(self, on_top: Self) -> Self {
347        self.gamma_multiply_u8(255 - on_top.a()) + on_top
348    }
349
350    /// Intensity of the color.
351    ///
352    /// Returns a value in the range 0-1.
353    /// The brighter the color, the closer to 1.
354    pub fn intensity(&self) -> f32 {
355        (self.r() as f32 * 0.299 + self.g() as f32 * 0.587 + self.b() as f32 * 0.114) / 255.0
356    }
357}
358
359impl std::ops::Mul for Color32 {
360    type Output = Self;
361
362    /// Fast gamma-space multiplication.
363    #[inline]
364    fn mul(self, other: Self) -> Self {
365        Self([
366            fast_round(self[0] as f32 * other[0] as f32 / 255.0),
367            fast_round(self[1] as f32 * other[1] as f32 / 255.0),
368            fast_round(self[2] as f32 * other[2] as f32 / 255.0),
369            fast_round(self[3] as f32 * other[3] as f32 / 255.0),
370        ])
371    }
372}
373
374impl std::ops::Add for Color32 {
375    type Output = Self;
376
377    #[inline]
378    fn add(self, other: Self) -> Self {
379        Self([
380            self[0].saturating_add(other[0]),
381            self[1].saturating_add(other[1]),
382            self[2].saturating_add(other[2]),
383            self[3].saturating_add(other[3]),
384        ])
385    }
386}
387
388#[cfg(test)]
389mod test {
390    use super::*;
391
392    fn test_rgba() -> impl Iterator<Item = [u8; 4]> {
393        [
394            [0, 0, 0, 0],
395            [0, 0, 0, 255],
396            [10, 0, 30, 0],
397            [10, 0, 30, 40],
398            [10, 100, 200, 0],
399            [10, 100, 200, 100],
400            [10, 100, 200, 200],
401            [10, 100, 200, 255],
402            [10, 100, 200, 40],
403            [10, 20, 0, 0],
404            [10, 20, 0, 255],
405            [10, 20, 30, 255],
406            [10, 20, 30, 40],
407            [255, 255, 255, 0],
408            [255, 255, 255, 255],
409        ]
410        .into_iter()
411    }
412
413    #[test]
414    fn test_color32_additive() {
415        let opaque = Color32::from_rgb(40, 50, 60);
416        let additive = Color32::from_rgb(255, 127, 10).additive();
417        assert_eq!(additive.blend(opaque), opaque, "opaque on top of additive");
418        assert_eq!(
419            opaque.blend(additive),
420            Color32::from_rgb(255, 177, 70),
421            "additive on top of opaque"
422        );
423    }
424
425    #[test]
426    fn test_color32_blend_vs_gamma_blend() {
427        let opaque = Color32::from_rgb(0x60, 0x60, 0x60);
428        let transparent = Color32::from_rgba_unmultiplied(168, 65, 65, 79);
429        assert_eq!(
430            transparent.blend(opaque),
431            opaque,
432            "Opaque on top of transparent"
433        );
434        // Blending in gamma-space is the de-facto standard almost everywhere.
435        // Browsers and most image editors do it, and so it is what users expect.
436        assert_eq!(
437            opaque.blend(transparent),
438            Color32::from_rgb(
439                blend(0x60, 168, 79),
440                blend(0x60, 65, 79),
441                blend(0x60, 65, 79)
442            ),
443            "Transparent on top of opaque"
444        );
445
446        fn blend(dest: u8, src: u8, alpha: u8) -> u8 {
447            let src = src as f32 / 255.0;
448            let dest = dest as f32 / 255.0;
449            let alpha = alpha as f32 / 255.0;
450            fast_round((src * alpha + dest * (1.0 - alpha)) * 255.0)
451        }
452    }
453
454    #[test]
455    fn color32_unmultiplied_round_trip() {
456        for in_rgba in test_rgba() {
457            let [r, g, b, a] = in_rgba;
458            if a == 0 {
459                continue;
460            }
461
462            let c = Color32::from_rgba_unmultiplied(r, g, b, a);
463            let out_rgba = c.to_srgba_unmultiplied();
464
465            if a == 255 {
466                assert_eq!(in_rgba, out_rgba);
467            } else {
468                // There will be small rounding errors whenever the alpha is not 0 or 255,
469                // because we multiply and then unmultiply the alpha.
470                for (&a, &b) in in_rgba.iter().zip(out_rgba.iter()) {
471                    assert!(a.abs_diff(b) <= 3, "{in_rgba:?} != {out_rgba:?}");
472                }
473            }
474        }
475    }
476
477    #[test]
478    fn from_black_white_alpha() {
479        for a in 0..=255 {
480            assert_eq!(
481                Color32::from_white_alpha(a),
482                Color32::from_rgba_unmultiplied(255, 255, 255, a)
483            );
484            assert_eq!(
485                Color32::from_white_alpha(a),
486                Color32::WHITE.gamma_multiply_u8(a)
487            );
488
489            assert_eq!(
490                Color32::from_black_alpha(a),
491                Color32::from_rgba_unmultiplied(0, 0, 0, a)
492            );
493            assert_eq!(
494                Color32::from_black_alpha(a),
495                Color32::BLACK.gamma_multiply_u8(a)
496            );
497        }
498    }
499
500    #[test]
501    fn to_from_rgba() {
502        for [r, g, b, a] in test_rgba() {
503            let original = Color32::from_rgba_unmultiplied(r, g, b, a);
504            let rgba = Rgba::from(original);
505            let back = Color32::from(rgba);
506            assert_eq!(back, original);
507        }
508
509        assert_eq!(
510            Color32::from(Rgba::from_rgba_unmultiplied(1.0, 0.0, 0.0, 0.5)),
511            Color32::from_rgba_unmultiplied(255, 0, 0, 128)
512        );
513    }
514}