ecolor/
color32.rs

1use crate::{
2    fast_round, gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_f32_from_linear_u8, Rgba,
3};
4
5/// This format is used for space-efficient color representation (32 bits).
6///
7/// Instead of manipulating this directly it is often better
8/// to first convert it to either [`Rgba`] or [`crate::Hsva`].
9///
10/// Internally this uses 0-255 gamma space `sRGBA` color with premultiplied alpha.
11/// Alpha channel is in linear space.
12///
13/// The special value of alpha=0 means the color is to be treated as an additive color.
14#[repr(C)]
15#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
16#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
17#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
18pub struct Color32(pub(crate) [u8; 4]);
19
20impl std::ops::Index<usize> for Color32 {
21    type Output = u8;
22
23    #[inline]
24    fn index(&self, index: usize) -> &u8 {
25        &self.0[index]
26    }
27}
28
29impl std::ops::IndexMut<usize> for Color32 {
30    #[inline]
31    fn index_mut(&mut self, index: usize) -> &mut u8 {
32        &mut self.0[index]
33    }
34}
35
36impl Color32 {
37    // Mostly follows CSS names:
38
39    pub const TRANSPARENT: Self = Self::from_rgba_premultiplied(0, 0, 0, 0);
40    pub const BLACK: Self = Self::from_rgb(0, 0, 0);
41    pub const DARK_GRAY: Self = Self::from_rgb(96, 96, 96);
42    pub const GRAY: Self = Self::from_rgb(160, 160, 160);
43    pub const LIGHT_GRAY: Self = Self::from_rgb(220, 220, 220);
44    pub const WHITE: Self = Self::from_rgb(255, 255, 255);
45
46    pub const BROWN: Self = Self::from_rgb(165, 42, 42);
47    pub const DARK_RED: Self = Self::from_rgb(0x8B, 0, 0);
48    pub const RED: Self = Self::from_rgb(255, 0, 0);
49    pub const LIGHT_RED: Self = Self::from_rgb(255, 128, 128);
50
51    pub const YELLOW: Self = Self::from_rgb(255, 255, 0);
52    pub const LIGHT_YELLOW: Self = Self::from_rgb(255, 255, 0xE0);
53    pub const KHAKI: Self = Self::from_rgb(240, 230, 140);
54
55    pub const DARK_GREEN: Self = Self::from_rgb(0, 0x64, 0);
56    pub const GREEN: Self = Self::from_rgb(0, 255, 0);
57    pub const LIGHT_GREEN: Self = Self::from_rgb(0x90, 0xEE, 0x90);
58
59    pub const DARK_BLUE: Self = Self::from_rgb(0, 0, 0x8B);
60    pub const BLUE: Self = Self::from_rgb(0, 0, 255);
61    pub const LIGHT_BLUE: Self = Self::from_rgb(0xAD, 0xD8, 0xE6);
62
63    pub const GOLD: Self = Self::from_rgb(255, 215, 0);
64
65    pub const DEBUG_COLOR: Self = Self::from_rgba_premultiplied(0, 200, 0, 128);
66
67    /// An ugly color that is planned to be replaced before making it to the screen.
68    ///
69    /// This is an invalid color, in that it does not correspond to a valid multiplied color,
70    /// nor to an additive color.
71    ///
72    /// This is used as a special color key,
73    /// i.e. often taken to mean "no color".
74    pub const PLACEHOLDER: Self = Self::from_rgba_premultiplied(64, 254, 0, 128);
75
76    #[deprecated = "Renamed to PLACEHOLDER"]
77    pub const TEMPORARY_COLOR: Self = Self::PLACEHOLDER;
78
79    #[inline]
80    pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
81        Self([r, g, b, 255])
82    }
83
84    #[inline]
85    pub const fn from_rgb_additive(r: u8, g: u8, b: u8) -> Self {
86        Self([r, g, b, 0])
87    }
88
89    /// From `sRGBA` with premultiplied alpha.
90    #[inline]
91    pub const fn from_rgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
92        Self([r, g, b, a])
93    }
94
95    /// From `sRGBA` WITHOUT premultiplied alpha.
96    #[inline]
97    pub fn from_rgba_unmultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
98        if a == 255 {
99            Self::from_rgb(r, g, b) // common-case optimization
100        } else if a == 0 {
101            Self::TRANSPARENT // common-case optimization
102        } else {
103            let r_lin = linear_f32_from_gamma_u8(r);
104            let g_lin = linear_f32_from_gamma_u8(g);
105            let b_lin = linear_f32_from_gamma_u8(b);
106            let a_lin = linear_f32_from_linear_u8(a);
107
108            let r = gamma_u8_from_linear_f32(r_lin * a_lin);
109            let g = gamma_u8_from_linear_f32(g_lin * a_lin);
110            let b = gamma_u8_from_linear_f32(b_lin * a_lin);
111
112            Self::from_rgba_premultiplied(r, g, b, a)
113        }
114    }
115
116    #[inline]
117    pub const fn from_gray(l: u8) -> Self {
118        Self([l, l, l, 255])
119    }
120
121    #[inline]
122    pub const fn from_black_alpha(a: u8) -> Self {
123        Self([0, 0, 0, a])
124    }
125
126    #[inline]
127    pub fn from_white_alpha(a: u8) -> Self {
128        Rgba::from_white_alpha(linear_f32_from_linear_u8(a)).into()
129    }
130
131    #[inline]
132    pub const fn from_additive_luminance(l: u8) -> Self {
133        Self([l, l, l, 0])
134    }
135
136    #[inline]
137    pub const fn is_opaque(&self) -> bool {
138        self.a() == 255
139    }
140
141    #[inline]
142    pub const fn r(&self) -> u8 {
143        self.0[0]
144    }
145
146    #[inline]
147    pub const fn g(&self) -> u8 {
148        self.0[1]
149    }
150
151    #[inline]
152    pub const fn b(&self) -> u8 {
153        self.0[2]
154    }
155
156    #[inline]
157    pub const fn a(&self) -> u8 {
158        self.0[3]
159    }
160
161    /// Returns an opaque version of self
162    #[inline]
163    pub fn to_opaque(self) -> Self {
164        Rgba::from(self).to_opaque().into()
165    }
166
167    /// Returns an additive version of self
168    #[inline]
169    pub const fn additive(self) -> Self {
170        let [r, g, b, _] = self.to_array();
171        Self([r, g, b, 0])
172    }
173
174    /// Is the alpha=0 ?
175    #[inline]
176    pub fn is_additive(self) -> bool {
177        self.a() == 0
178    }
179
180    /// Premultiplied RGBA
181    #[inline]
182    pub const fn to_array(&self) -> [u8; 4] {
183        [self.r(), self.g(), self.b(), self.a()]
184    }
185
186    /// Premultiplied RGBA
187    #[inline]
188    pub const fn to_tuple(&self) -> (u8, u8, u8, u8) {
189        (self.r(), self.g(), self.b(), self.a())
190    }
191
192    #[inline]
193    pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
194        Rgba::from(*self).to_srgba_unmultiplied()
195    }
196
197    /// Multiply with 0.5 to make color half as opaque, perceptually.
198    ///
199    /// Fast multiplication in gamma-space.
200    ///
201    /// This is perceptually even, and faster that [`Self::linear_multiply`].
202    #[inline]
203    pub fn gamma_multiply(self, factor: f32) -> Self {
204        debug_assert!(0.0 <= factor && factor <= 1.0);
205        let Self([r, g, b, a]) = self;
206        Self([
207            (r as f32 * factor + 0.5) as u8,
208            (g as f32 * factor + 0.5) as u8,
209            (b as f32 * factor + 0.5) as u8,
210            (a as f32 * factor + 0.5) as u8,
211        ])
212    }
213
214    /// Multiply with 0.5 to make color half as opaque in linear space.
215    ///
216    /// This is using linear space, which is not perceptually even.
217    /// You likely want to use [`Self::gamma_multiply`] instead.
218    #[inline]
219    pub fn linear_multiply(self, factor: f32) -> Self {
220        debug_assert!(0.0 <= factor && factor <= 1.0);
221        // As an unfortunate side-effect of using premultiplied alpha
222        // we need a somewhat expensive conversion to linear space and back.
223        Rgba::from(self).multiply(factor).into()
224    }
225
226    /// Converts to floating point values in the range 0-1 without any gamma space conversion.
227    ///
228    /// Use this with great care! In almost all cases, you want to convert to [`crate::Rgba`] instead
229    /// in order to obtain linear space color values.
230    #[inline]
231    pub fn to_normalized_gamma_f32(self) -> [f32; 4] {
232        let Self([r, g, b, a]) = self;
233        [
234            r as f32 / 255.0,
235            g as f32 / 255.0,
236            b as f32 / 255.0,
237            a as f32 / 255.0,
238        ]
239    }
240
241    /// Lerp this color towards `other` by `t` in gamma space.
242    pub fn lerp_to_gamma(&self, other: Self, t: f32) -> Self {
243        use emath::lerp;
244
245        Self::from_rgba_premultiplied(
246            fast_round(lerp((self[0] as f32)..=(other[0] as f32), t)),
247            fast_round(lerp((self[1] as f32)..=(other[1] as f32), t)),
248            fast_round(lerp((self[2] as f32)..=(other[2] as f32), t)),
249            fast_round(lerp((self[3] as f32)..=(other[3] as f32), t)),
250        )
251    }
252}