1use crate::{Rgba, fast_round, linear_f32_from_linear_u8};
2
3#[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 std::fmt::Debug for Color32 {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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 std::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 std::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 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 pub const PLACEHOLDER: Self = Self::from_rgba_premultiplied(64, 254, 0, 128);
105
106 #[inline]
108 pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
109 Self([r, g, b, 255])
110 }
111
112 #[inline]
114 pub const fn from_rgb_additive(r: u8, g: u8, b: u8) -> Self {
115 Self([r, g, b, 0])
116 }
117
118 #[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 #[inline]
133 pub fn from_rgba_unmultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
134 use std::sync::OnceLock;
135 match a {
136 0 => Self::TRANSPARENT,
138
139 255 => Self::from_rgb(r, g, b),
141
142 a => {
143 static LOOKUP_TABLE: OnceLock<Box<[u8]>> = OnceLock::new();
144 let lut = LOOKUP_TABLE.get_or_init(|| {
145 (0..=u16::MAX)
146 .map(|i| {
147 let [value, alpha] = i.to_ne_bytes();
148 fast_round(value as f32 * linear_f32_from_linear_u8(alpha))
149 })
150 .collect()
151 });
152
153 let [r, g, b] =
154 [r, g, b].map(|value| lut[usize::from(u16::from_ne_bytes([value, a]))]);
155 Self::from_rgba_premultiplied(r, g, b, a)
156 }
157 }
158 }
159
160 #[inline]
164 pub const fn from_rgba_unmultiplied_const(r: u8, g: u8, b: u8, a: u8) -> Self {
165 match a {
166 0 => Self::TRANSPARENT,
168
169 255 => Self::from_rgb(r, g, b),
171
172 a => {
173 let r = fast_round(r as f32 * linear_f32_from_linear_u8(a));
174 let g = fast_round(g as f32 * linear_f32_from_linear_u8(a));
175 let b = fast_round(b as f32 * linear_f32_from_linear_u8(a));
176 Self::from_rgba_premultiplied(r, g, b, a)
177 }
178 }
179 }
180
181 #[doc(alias = "from_grey")]
183 #[inline]
184 pub const fn from_gray(l: u8) -> Self {
185 Self([l, l, l, 255])
186 }
187
188 #[inline]
190 pub const fn from_black_alpha(a: u8) -> Self {
191 Self([0, 0, 0, a])
192 }
193
194 #[inline]
196 pub fn from_white_alpha(a: u8) -> Self {
197 Self([a, a, a, a])
198 }
199
200 #[inline]
202 pub const fn from_additive_luminance(l: u8) -> Self {
203 Self([l, l, l, 0])
204 }
205
206 #[inline]
207 pub const fn is_opaque(&self) -> bool {
208 self.a() == 255
209 }
210
211 #[inline]
213 pub const fn r(&self) -> u8 {
214 self.0[0]
215 }
216
217 #[inline]
219 pub const fn g(&self) -> u8 {
220 self.0[1]
221 }
222
223 #[inline]
225 pub const fn b(&self) -> u8 {
226 self.0[2]
227 }
228
229 #[inline]
231 pub const fn a(&self) -> u8 {
232 self.0[3]
233 }
234
235 #[inline]
237 pub fn to_opaque(self) -> Self {
238 Rgba::from(self).to_opaque().into()
239 }
240
241 #[inline]
243 pub const fn additive(self) -> Self {
244 let [r, g, b, _] = self.to_array();
245 Self([r, g, b, 0])
246 }
247
248 #[inline]
250 pub fn is_additive(self) -> bool {
251 self.a() == 0
252 }
253
254 #[inline]
256 pub const fn to_array(&self) -> [u8; 4] {
257 [self.r(), self.g(), self.b(), self.a()]
258 }
259
260 #[inline]
262 pub const fn to_tuple(&self) -> (u8, u8, u8, u8) {
263 (self.r(), self.g(), self.b(), self.a())
264 }
265
266 #[inline]
273 pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
274 let [r, g, b, a] = self.to_array();
275 match a {
276 0 | 255 => self.to_array(),
278 a => {
279 let factor = 255.0 / a as f32;
280 let r = fast_round(factor * r as f32);
281 let g = fast_round(factor * g as f32);
282 let b = fast_round(factor * b as f32);
283 [r, g, b, a]
284 }
285 }
286 }
287
288 #[inline]
294 pub fn gamma_multiply(self, factor: f32) -> Self {
295 debug_assert!(
296 0.0 <= factor && factor.is_finite(),
297 "factor should be finite, but was {factor}"
298 );
299 let Self([r, g, b, a]) = self;
300 Self([
301 (r as f32 * factor + 0.5) as u8,
302 (g as f32 * factor + 0.5) as u8,
303 (b as f32 * factor + 0.5) as u8,
304 (a as f32 * factor + 0.5) as u8,
305 ])
306 }
307
308 #[inline]
314 pub fn gamma_multiply_u8(self, factor: u8) -> Self {
315 let Self([r, g, b, a]) = self;
316 let factor = factor as u32;
317 Self([
318 ((r as u32 * factor + 127) / 255) as u8,
319 ((g as u32 * factor + 127) / 255) as u8,
320 ((b as u32 * factor + 127) / 255) as u8,
321 ((a as u32 * factor + 127) / 255) as u8,
322 ])
323 }
324
325 #[inline]
330 pub fn linear_multiply(self, factor: f32) -> Self {
331 debug_assert!(
332 0.0 <= factor && factor.is_finite(),
333 "factor should be finite, but was {factor}"
334 );
335 Rgba::from(self).multiply(factor).into()
338 }
339
340 #[inline]
345 pub fn to_normalized_gamma_f32(self) -> [f32; 4] {
346 let Self([r, g, b, a]) = self;
347 [
348 r as f32 / 255.0,
349 g as f32 / 255.0,
350 b as f32 / 255.0,
351 a as f32 / 255.0,
352 ]
353 }
354
355 pub fn lerp_to_gamma(&self, other: Self, t: f32) -> Self {
357 use emath::lerp;
358
359 Self::from_rgba_premultiplied(
360 fast_round(lerp((self[0] as f32)..=(other[0] as f32), t)),
361 fast_round(lerp((self[1] as f32)..=(other[1] as f32), t)),
362 fast_round(lerp((self[2] as f32)..=(other[2] as f32), t)),
363 fast_round(lerp((self[3] as f32)..=(other[3] as f32), t)),
364 )
365 }
366
367 pub fn blend(self, on_top: Self) -> Self {
369 self.gamma_multiply_u8(255 - on_top.a()) + on_top
370 }
371
372 pub fn intensity(&self) -> f32 {
377 (self.r() as f32 * 0.299 + self.g() as f32 * 0.587 + self.b() as f32 * 0.114) / 255.0
378 }
379}
380
381impl std::ops::Mul for Color32 {
382 type Output = Self;
383
384 #[inline]
386 fn mul(self, other: Self) -> Self {
387 Self([
388 fast_round(self[0] as f32 * other[0] as f32 / 255.0),
389 fast_round(self[1] as f32 * other[1] as f32 / 255.0),
390 fast_round(self[2] as f32 * other[2] as f32 / 255.0),
391 fast_round(self[3] as f32 * other[3] as f32 / 255.0),
392 ])
393 }
394}
395
396impl std::ops::Add for Color32 {
397 type Output = Self;
398
399 #[inline]
400 fn add(self, other: Self) -> Self {
401 Self([
402 self[0].saturating_add(other[0]),
403 self[1].saturating_add(other[1]),
404 self[2].saturating_add(other[2]),
405 self[3].saturating_add(other[3]),
406 ])
407 }
408}
409
410#[cfg(test)]
411mod test {
412 use super::*;
413
414 fn test_rgba() -> impl Iterator<Item = [u8; 4]> {
415 [
416 [0, 0, 0, 0],
417 [0, 0, 0, 255],
418 [10, 0, 30, 0],
419 [10, 0, 30, 40],
420 [10, 100, 200, 0],
421 [10, 100, 200, 100],
422 [10, 100, 200, 200],
423 [10, 100, 200, 255],
424 [10, 100, 200, 40],
425 [10, 20, 0, 0],
426 [10, 20, 0, 255],
427 [10, 20, 30, 255],
428 [10, 20, 30, 40],
429 [255, 255, 255, 0],
430 [255, 255, 255, 255],
431 ]
432 .into_iter()
433 }
434
435 #[test]
436 fn test_color32_additive() {
437 let opaque = Color32::from_rgb(40, 50, 60);
438 let additive = Color32::from_rgb(255, 127, 10).additive();
439 assert_eq!(additive.blend(opaque), opaque, "opaque on top of additive");
440 assert_eq!(
441 opaque.blend(additive),
442 Color32::from_rgb(255, 177, 70),
443 "additive on top of opaque"
444 );
445 }
446
447 #[test]
448 fn test_color32_blend_vs_gamma_blend() {
449 let opaque = Color32::from_rgb(0x60, 0x60, 0x60);
450 let transparent = Color32::from_rgba_unmultiplied(168, 65, 65, 79);
451 assert_eq!(
452 transparent.blend(opaque),
453 opaque,
454 "Opaque on top of transparent"
455 );
456 assert_eq!(
459 opaque.blend(transparent),
460 Color32::from_rgb(
461 blend(0x60, 168, 79),
462 blend(0x60, 65, 79),
463 blend(0x60, 65, 79)
464 ),
465 "Transparent on top of opaque"
466 );
467
468 fn blend(dest: u8, src: u8, alpha: u8) -> u8 {
469 let src = src as f32 / 255.0;
470 let dest = dest as f32 / 255.0;
471 let alpha = alpha as f32 / 255.0;
472 fast_round((src * alpha + dest * (1.0 - alpha)) * 255.0)
473 }
474 }
475
476 #[test]
477 fn color32_unmultiplied_round_trip() {
478 for in_rgba in test_rgba() {
479 let [r, g, b, a] = in_rgba;
480 if a == 0 {
481 continue;
482 }
483
484 let c = Color32::from_rgba_unmultiplied(r, g, b, a);
485 let out_rgba = c.to_srgba_unmultiplied();
486
487 if a == 255 {
488 assert_eq!(in_rgba, out_rgba);
489 } else {
490 for (&a, &b) in in_rgba.iter().zip(out_rgba.iter()) {
493 assert!(a.abs_diff(b) <= 3, "{in_rgba:?} != {out_rgba:?}");
494 }
495 }
496 }
497 }
498
499 #[test]
500 fn from_black_white_alpha() {
501 for a in 0..=255 {
502 assert_eq!(
503 Color32::from_white_alpha(a),
504 Color32::from_rgba_unmultiplied(255, 255, 255, a)
505 );
506 assert_eq!(
507 Color32::from_white_alpha(a),
508 Color32::WHITE.gamma_multiply_u8(a)
509 );
510
511 assert_eq!(
512 Color32::from_black_alpha(a),
513 Color32::from_rgba_unmultiplied(0, 0, 0, a)
514 );
515 assert_eq!(
516 Color32::from_black_alpha(a),
517 Color32::BLACK.gamma_multiply_u8(a)
518 );
519 }
520 }
521
522 #[test]
523 fn to_from_rgba() {
524 for [r, g, b, a] in test_rgba() {
525 let original = Color32::from_rgba_unmultiplied(r, g, b, a);
526 let constfn = Color32::from_rgba_unmultiplied_const(r, g, b, a);
527 let rgba = Rgba::from(original);
528 let back = Color32::from(rgba);
529 assert_eq!(back, original);
530 assert_eq!(constfn, original);
531 }
532
533 assert_eq!(
534 Color32::from(Rgba::from_rgba_unmultiplied(1.0, 0.0, 0.0, 0.5)),
535 Color32::from_rgba_unmultiplied(255, 0, 0, 128)
536 );
537 }
538}