1use crate::{Rgba, fast_round, linear_f32_from_linear_u8};
2
3#[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 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 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 pub const PLACEHOLDER: Self = Self::from_rgba_premultiplied(64, 254, 0, 128);
104
105 #[inline]
107 pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
108 Self([r, g, b, 255])
109 }
110
111 #[inline]
113 pub const fn from_rgb_additive(r: u8, g: u8, b: u8) -> Self {
114 Self([r, g, b, 0])
115 }
116
117 #[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 #[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 0 => Self::TRANSPARENT,
137
138 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 #[doc(alias = "from_grey")]
161 #[inline]
162 pub const fn from_gray(l: u8) -> Self {
163 Self([l, l, l, 255])
164 }
165
166 #[inline]
168 pub const fn from_black_alpha(a: u8) -> Self {
169 Self([0, 0, 0, a])
170 }
171
172 #[inline]
174 pub fn from_white_alpha(a: u8) -> Self {
175 Self([a, a, a, a])
176 }
177
178 #[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 #[inline]
191 pub const fn r(&self) -> u8 {
192 self.0[0]
193 }
194
195 #[inline]
197 pub const fn g(&self) -> u8 {
198 self.0[1]
199 }
200
201 #[inline]
203 pub const fn b(&self) -> u8 {
204 self.0[2]
205 }
206
207 #[inline]
209 pub const fn a(&self) -> u8 {
210 self.0[3]
211 }
212
213 #[inline]
215 pub fn to_opaque(self) -> Self {
216 Rgba::from(self).to_opaque().into()
217 }
218
219 #[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 #[inline]
228 pub fn is_additive(self) -> bool {
229 self.a() == 0
230 }
231
232 #[inline]
234 pub const fn to_array(&self) -> [u8; 4] {
235 [self.r(), self.g(), self.b(), self.a()]
236 }
237
238 #[inline]
240 pub const fn to_tuple(&self) -> (u8, u8, u8, u8) {
241 (self.r(), self.g(), self.b(), self.a())
242 }
243
244 #[inline]
251 pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
252 let [r, g, b, a] = self.to_array();
253 match a {
254 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 #[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 #[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 #[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 Rgba::from(self).multiply(factor).into()
316 }
317
318 #[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 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 pub fn blend(self, on_top: Self) -> Self {
347 self.gamma_multiply_u8(255 - on_top.a()) + on_top
348 }
349
350 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 #[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 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 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}