1use crate::{Rgba, fast_round, mul_frac_round};
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 core::fmt::Debug for Color32 {
34 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::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 core::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 core::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 Self::from_rgba_unmultiplied_const(r, g, b, a)
135 }
136
137 #[inline]
139 pub const fn from_rgba_unmultiplied_const(r: u8, g: u8, b: u8, a: u8) -> Self {
140 match a {
141 0 => Self::TRANSPARENT,
143
144 255 => Self::from_rgb(r, g, b),
146
147 a => {
148 let r = mul_frac_round(r, a);
149 let g = mul_frac_round(g, a);
150 let b = mul_frac_round(b, a);
151 Self::from_rgba_premultiplied(r, g, b, a)
152 }
153 }
154 }
155
156 #[doc(alias = "from_grey")]
158 #[inline]
159 pub const fn from_gray(l: u8) -> Self {
160 Self([l, l, l, 255])
161 }
162
163 #[inline]
165 pub const fn from_black_alpha(a: u8) -> Self {
166 Self([0, 0, 0, a])
167 }
168
169 #[inline]
171 pub fn from_white_alpha(a: u8) -> Self {
172 Self([a, a, a, a])
173 }
174
175 #[inline]
177 pub const fn from_additive_luminance(l: u8) -> Self {
178 Self([l, l, l, 0])
179 }
180
181 #[inline]
182 pub const fn is_opaque(&self) -> bool {
183 self.a() == 255
184 }
185
186 #[inline]
188 pub const fn r(&self) -> u8 {
189 self.0[0]
190 }
191
192 #[inline]
194 pub const fn g(&self) -> u8 {
195 self.0[1]
196 }
197
198 #[inline]
200 pub const fn b(&self) -> u8 {
201 self.0[2]
202 }
203
204 #[inline]
206 pub const fn a(&self) -> u8 {
207 self.0[3]
208 }
209
210 #[inline]
212 pub fn to_opaque(self) -> Self {
213 Rgba::from(self).to_opaque().into()
214 }
215
216 #[inline]
218 pub const fn additive(self) -> Self {
219 let [r, g, b, _] = self.to_array();
220 Self([r, g, b, 0])
221 }
222
223 #[inline]
225 pub fn is_additive(self) -> bool {
226 self.a() == 0
227 }
228
229 #[inline]
231 pub const fn to_array(&self) -> [u8; 4] {
232 [self.r(), self.g(), self.b(), self.a()]
233 }
234
235 #[inline]
237 pub const fn to_tuple(&self) -> (u8, u8, u8, u8) {
238 (self.r(), self.g(), self.b(), self.a())
239 }
240
241 #[inline]
248 pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
249 let [r, g, b, a] = self.to_array();
250 match a {
251 0 | 255 => self.to_array(),
253 a => {
254 let factor = 255.0 / a as f32;
255 let r = fast_round(factor * r as f32);
256 let g = fast_round(factor * g as f32);
257 let b = fast_round(factor * b as f32);
258 [r, g, b, a]
259 }
260 }
261 }
262
263 #[inline]
269 pub fn gamma_multiply(self, factor: f32) -> Self {
270 debug_assert!(
271 0.0 <= factor && factor.is_finite(),
272 "factor should be finite, but was {factor}"
273 );
274 let Self([r, g, b, a]) = self;
275 Self([
276 (r as f32 * factor + 0.5) as u8,
277 (g as f32 * factor + 0.5) as u8,
278 (b as f32 * factor + 0.5) as u8,
279 (a as f32 * factor + 0.5) as u8,
280 ])
281 }
282
283 #[inline]
289 pub fn gamma_multiply_u8(self, factor: u8) -> Self {
290 let Self([r, g, b, a]) = self;
291 let factor = factor as u32;
292 Self([
293 ((r as u32 * factor + 127) / 255) as u8,
294 ((g as u32 * factor + 127) / 255) as u8,
295 ((b as u32 * factor + 127) / 255) as u8,
296 ((a as u32 * factor + 127) / 255) as u8,
297 ])
298 }
299
300 #[inline]
305 pub fn linear_multiply(self, factor: f32) -> Self {
306 debug_assert!(
307 0.0 <= factor && factor.is_finite(),
308 "factor should be finite, but was {factor}"
309 );
310 Rgba::from(self).multiply(factor).into()
313 }
314
315 #[inline]
320 pub fn to_normalized_gamma_f32(self) -> [f32; 4] {
321 let Self([r, g, b, a]) = self;
322 [
323 r as f32 / 255.0,
324 g as f32 / 255.0,
325 b as f32 / 255.0,
326 a as f32 / 255.0,
327 ]
328 }
329
330 pub fn lerp_to_gamma(&self, other: Self, t: f32) -> Self {
332 use emath::lerp;
333
334 Self::from_rgba_premultiplied(
335 fast_round(lerp((self[0] as f32)..=(other[0] as f32), t)),
336 fast_round(lerp((self[1] as f32)..=(other[1] as f32), t)),
337 fast_round(lerp((self[2] as f32)..=(other[2] as f32), t)),
338 fast_round(lerp((self[3] as f32)..=(other[3] as f32), t)),
339 )
340 }
341
342 pub fn blend(self, on_top: Self) -> Self {
344 self.gamma_multiply_u8(255 - on_top.a()) + on_top
345 }
346
347 pub fn intensity(&self) -> f32 {
352 (self.r() as f32 * 0.299 + self.g() as f32 * 0.587 + self.b() as f32 * 0.114) / 255.0
353 }
354}
355
356impl core::ops::Mul for Color32 {
357 type Output = Self;
358
359 #[inline]
361 fn mul(self, other: Self) -> Self {
362 Self([
363 fast_round(self[0] as f32 * other[0] as f32 / 255.0),
364 fast_round(self[1] as f32 * other[1] as f32 / 255.0),
365 fast_round(self[2] as f32 * other[2] as f32 / 255.0),
366 fast_round(self[3] as f32 * other[3] as f32 / 255.0),
367 ])
368 }
369}
370
371impl core::ops::Add for Color32 {
372 type Output = Self;
373
374 #[inline]
375 fn add(self, other: Self) -> Self {
376 Self([
377 self[0].saturating_add(other[0]),
378 self[1].saturating_add(other[1]),
379 self[2].saturating_add(other[2]),
380 self[3].saturating_add(other[3]),
381 ])
382 }
383}
384
385#[cfg(test)]
386mod test {
387 use super::*;
388
389 fn test_rgba() -> impl Iterator<Item = [u8; 4]> {
390 [
391 [0, 0, 0, 0],
392 [0, 0, 0, 255],
393 [10, 0, 30, 0],
394 [10, 0, 30, 40],
395 [10, 100, 200, 0],
396 [10, 100, 200, 100],
397 [10, 100, 200, 200],
398 [10, 100, 200, 255],
399 [10, 100, 200, 40],
400 [10, 20, 0, 0],
401 [10, 20, 0, 255],
402 [10, 20, 30, 255],
403 [10, 20, 30, 40],
404 [255, 255, 255, 0],
405 [255, 255, 255, 255],
406 ]
407 .into_iter()
408 }
409
410 #[test]
411 fn test_color32_additive() {
412 let opaque = Color32::from_rgb(40, 50, 60);
413 let additive = Color32::from_rgb(255, 127, 10).additive();
414 assert_eq!(additive.blend(opaque), opaque, "opaque on top of additive");
415 assert_eq!(
416 opaque.blend(additive),
417 Color32::from_rgb(255, 177, 70),
418 "additive on top of opaque"
419 );
420 }
421
422 #[test]
423 fn test_color32_blend_vs_gamma_blend() {
424 let opaque = Color32::from_rgb(0x60, 0x60, 0x60);
425 let transparent = Color32::from_rgba_unmultiplied(168, 65, 65, 79);
426 assert_eq!(
427 transparent.blend(opaque),
428 opaque,
429 "Opaque on top of transparent"
430 );
431 assert_eq!(
434 opaque.blend(transparent),
435 Color32::from_rgb(
436 blend(0x60, 168, 79),
437 blend(0x60, 65, 79),
438 blend(0x60, 65, 79)
439 ),
440 "Transparent on top of opaque"
441 );
442
443 fn blend(dest: u8, src: u8, alpha: u8) -> u8 {
444 let src = src as f32 / 255.0;
445 let dest = dest as f32 / 255.0;
446 let alpha = alpha as f32 / 255.0;
447 fast_round((src * alpha + dest * (1.0 - alpha)) * 255.0)
448 }
449 }
450
451 #[test]
452 fn color32_unmultiplied_round_trip() {
453 for in_rgba in test_rgba() {
454 let [r, g, b, a] = in_rgba;
455 if a == 0 {
456 continue;
457 }
458
459 let c = Color32::from_rgba_unmultiplied(r, g, b, a);
460 let out_rgba = c.to_srgba_unmultiplied();
461
462 if a == 255 {
463 assert_eq!(in_rgba, out_rgba);
464 } else {
465 for (&a, &b) in core::iter::zip(&in_rgba, &out_rgba) {
468 assert!(a.abs_diff(b) <= 3, "{in_rgba:?} != {out_rgba:?}");
469 }
470 }
471 }
472 }
473
474 #[test]
475 fn from_black_white_alpha() {
476 for a in 0..=255 {
477 assert_eq!(
478 Color32::from_white_alpha(a),
479 Color32::from_rgba_unmultiplied(255, 255, 255, a)
480 );
481 assert_eq!(
482 Color32::from_white_alpha(a),
483 Color32::WHITE.gamma_multiply_u8(a)
484 );
485
486 assert_eq!(
487 Color32::from_black_alpha(a),
488 Color32::from_rgba_unmultiplied(0, 0, 0, a)
489 );
490 assert_eq!(
491 Color32::from_black_alpha(a),
492 Color32::BLACK.gamma_multiply_u8(a)
493 );
494 }
495 }
496
497 #[test]
498 fn to_from_rgba() {
499 for [r, g, b, a] in test_rgba() {
500 let original = Color32::from_rgba_unmultiplied(r, g, b, a);
501 let constfn = Color32::from_rgba_unmultiplied_const(r, g, b, a);
502 let rgba = Rgba::from(original);
503 let back = Color32::from(rgba);
504 assert_eq!(back, original);
505 assert_eq!(constfn, original);
506 }
507
508 assert_eq!(
509 Color32::from(Rgba::from_rgba_unmultiplied(1.0, 0.0, 0.0, 0.5)),
510 Color32::from_rgba_unmultiplied(255, 0, 0, 128)
511 );
512 }
513
514 #[test]
515 fn mul_frac_round_vs_old() {
516 for x in (0..=255u8).step_by(4) {
517 for a in (1..=255u8).step_by(4) {
518 let old = fast_round(x as f32 * crate::linear_f32_from_linear_u8(a));
519 assert_eq!(old, mul_frac_round(x, a));
520 }
521 }
522 }
523}