Skip to main content

ecolor/
color32.rs

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