ecolor/
color32.rs

1use crate::{fast_round, linear_f32_from_linear_u8, Rgba};
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/// Alpha channel is in linear space.
10///
11/// The special value of alpha=0 means the color is to be treated as an additive color.
12#[repr(C)]
13#[derive(Clone, Copy, Default, Eq, Hash, PartialEq)]
14#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
15#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
16pub struct Color32(pub(crate) [u8; 4]);
17
18impl std::fmt::Debug for Color32 {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        let [r, g, b, a] = self.0;
21        write!(f, "#{r:02X}_{g:02X}_{b:02X}_{a:02X}")
22    }
23}
24
25impl std::ops::Index<usize> for Color32 {
26    type Output = u8;
27
28    #[inline]
29    fn index(&self, index: usize) -> &u8 {
30        &self.0[index]
31    }
32}
33
34impl std::ops::IndexMut<usize> for Color32 {
35    #[inline]
36    fn index_mut(&mut self, index: usize) -> &mut u8 {
37        &mut self.0[index]
38    }
39}
40
41impl Color32 {
42    // Mostly follows CSS names:
43
44    pub const TRANSPARENT: Self = Self::from_rgba_premultiplied(0, 0, 0, 0);
45    pub const BLACK: Self = Self::from_rgb(0, 0, 0);
46    #[doc(alias = "DARK_GREY")]
47    pub const DARK_GRAY: Self = Self::from_rgb(96, 96, 96);
48    #[doc(alias = "GREY")]
49    pub const GRAY: Self = Self::from_rgb(160, 160, 160);
50    #[doc(alias = "LIGHT_GREY")]
51    pub const LIGHT_GRAY: Self = Self::from_rgb(220, 220, 220);
52    pub const WHITE: Self = Self::from_rgb(255, 255, 255);
53
54    pub const BROWN: Self = Self::from_rgb(165, 42, 42);
55    pub const DARK_RED: Self = Self::from_rgb(0x8B, 0, 0);
56    pub const RED: Self = Self::from_rgb(255, 0, 0);
57    pub const LIGHT_RED: Self = Self::from_rgb(255, 128, 128);
58
59    pub const CYAN: Self = Self::from_rgb(0, 255, 255);
60    pub const MAGENTA: Self = Self::from_rgb(255, 0, 255);
61    pub const YELLOW: Self = Self::from_rgb(255, 255, 0);
62
63    pub const ORANGE: Self = Self::from_rgb(255, 165, 0);
64    pub const LIGHT_YELLOW: Self = Self::from_rgb(255, 255, 0xE0);
65    pub const KHAKI: Self = Self::from_rgb(240, 230, 140);
66
67    pub const DARK_GREEN: Self = Self::from_rgb(0, 0x64, 0);
68    pub const GREEN: Self = Self::from_rgb(0, 255, 0);
69    pub const LIGHT_GREEN: Self = Self::from_rgb(0x90, 0xEE, 0x90);
70
71    pub const DARK_BLUE: Self = Self::from_rgb(0, 0, 0x8B);
72    pub const BLUE: Self = Self::from_rgb(0, 0, 255);
73    pub const LIGHT_BLUE: Self = Self::from_rgb(0xAD, 0xD8, 0xE6);
74
75    pub const PURPLE: Self = Self::from_rgb(0x80, 0, 0x80);
76
77    pub const GOLD: Self = Self::from_rgb(255, 215, 0);
78
79    pub const DEBUG_COLOR: Self = Self::from_rgba_premultiplied(0, 200, 0, 128);
80
81    /// An ugly color that is planned to be replaced before making it to the screen.
82    ///
83    /// This is an invalid color, in that it does not correspond to a valid multiplied color,
84    /// nor to an additive color.
85    ///
86    /// This is used as a special color key,
87    /// i.e. often taken to mean "no color".
88    pub const PLACEHOLDER: Self = Self::from_rgba_premultiplied(64, 254, 0, 128);
89
90    #[deprecated = "Renamed to PLACEHOLDER"]
91    pub const TEMPORARY_COLOR: Self = Self::PLACEHOLDER;
92
93    #[inline]
94    pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
95        Self([r, g, b, 255])
96    }
97
98    #[inline]
99    pub const fn from_rgb_additive(r: u8, g: u8, b: u8) -> Self {
100        Self([r, g, b, 0])
101    }
102
103    /// From `sRGBA` with premultiplied alpha.
104    #[inline]
105    pub const fn from_rgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
106        Self([r, g, b, a])
107    }
108
109    /// From `sRGBA` WITHOUT premultiplied alpha.
110    #[inline]
111    pub fn from_rgba_unmultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
112        use std::sync::OnceLock;
113        match a {
114            // common-case optimization
115            0 => Self::TRANSPARENT,
116            // common-case optimization
117            255 => Self::from_rgb(r, g, b),
118            a => {
119                static LOOKUP_TABLE: OnceLock<Box<[u8]>> = OnceLock::new();
120                let lut = LOOKUP_TABLE.get_or_init(|| {
121                    use crate::{gamma_u8_from_linear_f32, linear_f32_from_gamma_u8};
122                    (0..=u16::MAX)
123                        .map(|i| {
124                            let [value, alpha] = i.to_ne_bytes();
125                            let value_lin = linear_f32_from_gamma_u8(value);
126                            let alpha_lin = linear_f32_from_linear_u8(alpha);
127                            gamma_u8_from_linear_f32(value_lin * alpha_lin)
128                        })
129                        .collect()
130                });
131
132                let [r, g, b] =
133                    [r, g, b].map(|value| lut[usize::from(u16::from_ne_bytes([value, a]))]);
134                Self::from_rgba_premultiplied(r, g, b, a)
135            }
136        }
137    }
138
139    #[doc(alias = "from_grey")]
140    #[inline]
141    pub const fn from_gray(l: u8) -> Self {
142        Self([l, l, l, 255])
143    }
144
145    #[inline]
146    pub const fn from_black_alpha(a: u8) -> Self {
147        Self([0, 0, 0, a])
148    }
149
150    #[inline]
151    pub fn from_white_alpha(a: u8) -> Self {
152        Rgba::from_white_alpha(linear_f32_from_linear_u8(a)).into()
153    }
154
155    #[inline]
156    pub const fn from_additive_luminance(l: u8) -> Self {
157        Self([l, l, l, 0])
158    }
159
160    #[inline]
161    pub const fn is_opaque(&self) -> bool {
162        self.a() == 255
163    }
164
165    #[inline]
166    pub const fn r(&self) -> u8 {
167        self.0[0]
168    }
169
170    #[inline]
171    pub const fn g(&self) -> u8 {
172        self.0[1]
173    }
174
175    #[inline]
176    pub const fn b(&self) -> u8 {
177        self.0[2]
178    }
179
180    #[inline]
181    pub const fn a(&self) -> u8 {
182        self.0[3]
183    }
184
185    /// Returns an opaque version of self
186    #[inline]
187    pub fn to_opaque(self) -> Self {
188        Rgba::from(self).to_opaque().into()
189    }
190
191    /// Returns an additive version of self
192    #[inline]
193    pub const fn additive(self) -> Self {
194        let [r, g, b, _] = self.to_array();
195        Self([r, g, b, 0])
196    }
197
198    /// Is the alpha=0 ?
199    #[inline]
200    pub fn is_additive(self) -> bool {
201        self.a() == 0
202    }
203
204    /// Premultiplied RGBA
205    #[inline]
206    pub const fn to_array(&self) -> [u8; 4] {
207        [self.r(), self.g(), self.b(), self.a()]
208    }
209
210    /// Premultiplied RGBA
211    #[inline]
212    pub const fn to_tuple(&self) -> (u8, u8, u8, u8) {
213        (self.r(), self.g(), self.b(), self.a())
214    }
215
216    #[inline]
217    pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
218        Rgba::from(*self).to_srgba_unmultiplied()
219    }
220
221    /// Multiply with 0.5 to make color half as opaque, perceptually.
222    ///
223    /// Fast multiplication in gamma-space.
224    ///
225    /// This is perceptually even, and faster that [`Self::linear_multiply`].
226    #[inline]
227    pub fn gamma_multiply(self, factor: f32) -> Self {
228        debug_assert!(0.0 <= factor && factor.is_finite());
229        let Self([r, g, b, a]) = self;
230        Self([
231            (r as f32 * factor + 0.5) as u8,
232            (g as f32 * factor + 0.5) as u8,
233            (b as f32 * factor + 0.5) as u8,
234            (a as f32 * factor + 0.5) as u8,
235        ])
236    }
237
238    /// Multiply with 127 to make color half as opaque, perceptually.
239    ///
240    /// Fast multiplication in gamma-space.
241    ///
242    /// This is perceptually even, and faster that [`Self::linear_multiply`].
243    #[inline]
244    pub fn gamma_multiply_u8(self, factor: u8) -> Self {
245        let Self([r, g, b, a]) = self;
246        let factor = factor as u32;
247        Self([
248            ((r as u32 * factor + 127) / 255) as u8,
249            ((g as u32 * factor + 127) / 255) as u8,
250            ((b as u32 * factor + 127) / 255) as u8,
251            ((a as u32 * factor + 127) / 255) as u8,
252        ])
253    }
254
255    /// Multiply with 0.5 to make color half as opaque in linear space.
256    ///
257    /// This is using linear space, which is not perceptually even.
258    /// You likely want to use [`Self::gamma_multiply`] instead.
259    #[inline]
260    pub fn linear_multiply(self, factor: f32) -> Self {
261        debug_assert!(0.0 <= factor && factor.is_finite());
262        // As an unfortunate side-effect of using premultiplied alpha
263        // we need a somewhat expensive conversion to linear space and back.
264        Rgba::from(self).multiply(factor).into()
265    }
266
267    /// Converts to floating point values in the range 0-1 without any gamma space conversion.
268    ///
269    /// Use this with great care! In almost all cases, you want to convert to [`crate::Rgba`] instead
270    /// in order to obtain linear space color values.
271    #[inline]
272    pub fn to_normalized_gamma_f32(self) -> [f32; 4] {
273        let Self([r, g, b, a]) = self;
274        [
275            r as f32 / 255.0,
276            g as f32 / 255.0,
277            b as f32 / 255.0,
278            a as f32 / 255.0,
279        ]
280    }
281
282    /// Lerp this color towards `other` by `t` in gamma space.
283    pub fn lerp_to_gamma(&self, other: Self, t: f32) -> Self {
284        use emath::lerp;
285
286        Self::from_rgba_premultiplied(
287            fast_round(lerp((self[0] as f32)..=(other[0] as f32), t)),
288            fast_round(lerp((self[1] as f32)..=(other[1] as f32), t)),
289            fast_round(lerp((self[2] as f32)..=(other[2] as f32), t)),
290            fast_round(lerp((self[3] as f32)..=(other[3] as f32), t)),
291        )
292    }
293
294    /// Blend two colors, so that `self` is behind the argument.
295    pub fn blend(self, on_top: Self) -> Self {
296        self.gamma_multiply_u8(255 - on_top.a()) + on_top
297    }
298}
299
300impl std::ops::Mul for Color32 {
301    type Output = Self;
302
303    /// Fast gamma-space multiplication.
304    #[inline]
305    fn mul(self, other: Self) -> Self {
306        Self([
307            fast_round(self[0] as f32 * other[0] as f32 / 255.0),
308            fast_round(self[1] as f32 * other[1] as f32 / 255.0),
309            fast_round(self[2] as f32 * other[2] as f32 / 255.0),
310            fast_round(self[3] as f32 * other[3] as f32 / 255.0),
311        ])
312    }
313}
314
315impl std::ops::Add for Color32 {
316    type Output = Self;
317
318    #[inline]
319    fn add(self, other: Self) -> Self {
320        Self([
321            self[0].saturating_add(other[0]),
322            self[1].saturating_add(other[1]),
323            self[2].saturating_add(other[2]),
324            self[3].saturating_add(other[3]),
325        ])
326    }
327}