1use crate::{fast_round, linear_f32_from_linear_u8, Rgba};
2
3#[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 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 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 #[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 #[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 0 => Self::TRANSPARENT,
116 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 #[inline]
187 pub fn to_opaque(self) -> Self {
188 Rgba::from(self).to_opaque().into()
189 }
190
191 #[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 #[inline]
200 pub fn is_additive(self) -> bool {
201 self.a() == 0
202 }
203
204 #[inline]
206 pub const fn to_array(&self) -> [u8; 4] {
207 [self.r(), self.g(), self.b(), self.a()]
208 }
209
210 #[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 #[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 #[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 #[inline]
260 pub fn linear_multiply(self, factor: f32) -> Self {
261 debug_assert!(0.0 <= factor && factor.is_finite());
262 Rgba::from(self).multiply(factor).into()
265 }
266
267 #[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 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 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 #[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}