1#![allow(clippy::many_single_char_names)]
2#![warn(missing_docs)]
3
4use std::fmt;
60use std::fmt::Write;
61use std::marker::PhantomData;
62
63#[derive(Copy, Clone, PartialEq, Default, Hash)]
78pub struct Color<T: Storage>(u32, PhantomData<T>);
79
80#[doc(hidden)]
81pub trait Storage: PartialEq + Copy + Clone + private::Sealed {
82 fn init(color: u32) -> u32 {
83 color
84 }
85 fn decode(color: u32) -> (u8, u8, u8, u8);
86 fn encode(r: u8, g: u8, b: u8, a: u8) -> u32;
87 fn write_hex(w: &mut dyn Write, color: u32) -> fmt::Result {
88 write!(w, "{:#010x}", color)
89 }
90}
91
92mod private {
93 pub trait Sealed {}
95
96 impl Sealed for super::ZRGB {}
97 impl Sealed for super::RGBA {}
98}
99
100fn decode(color: u32) -> (u8, u8, u8, u8) {
101 let b1 = (color >> 24) & 0xff;
102 let b2 = (color >> 16) & 0xff;
103 let b3 = (color >> 8) & 0xff;
104 let b4 = color & 0xff;
105 (b1 as u8, b2 as u8, b3 as u8, b4 as u8)
106}
107
108fn encode(b1: u8, b2: u8, b3: u8, b4: u8) -> u32 {
109 let b1 = b1 as u32;
110 let b2 = b2 as u32;
111 let b3 = b3 as u32;
112 let b4 = b4 as u32;
113 (b1 << 24) | (b2 << 16) | (b3 << 8) | b4
114}
115
116#[derive(PartialEq, Copy, Clone)]
126#[allow(clippy::upper_case_acronyms)]
127pub struct ZRGB;
128
129impl Storage for ZRGB {
130 fn init(color: u32) -> u32 {
131 color & 0xffffff
132 }
133
134 fn decode(color: u32) -> (u8, u8, u8, u8) {
135 let (_, r, g, b) = decode(color);
136 (r, g, b, 0)
137 }
138
139 fn encode(r: u8, g: u8, b: u8, _a: u8) -> u32 {
140 encode(0, r, g, b)
141 }
142
143 fn write_hex(w: &mut dyn Write, color: u32) -> fmt::Result {
144 write!(w, "{:#08x}", color & 0xffffff)
146 }
147}
148
149#[derive(PartialEq, Copy, Clone)]
159#[allow(clippy::upper_case_acronyms)]
160pub struct RGBA;
161
162impl Storage for RGBA {
163 fn decode(color: u32) -> (u8, u8, u8, u8) {
164 decode(color)
165 }
166
167 fn encode(r: u8, g: u8, b: u8, a: u8) -> u32 {
168 encode(r, g, b, a)
169 }
170}
171
172impl<T: Storage> Color<T> {
173 pub fn new(color: u32) -> Self {
192 Self(T::init(color), PhantomData)
193 }
194
195 #[must_use]
201 pub fn lighten(self, percent: f64) -> Self {
202 assert_percent(percent);
203 self.map_hsla(|h, s, mut l, a| {
204 l = (l + percent).min(1.0);
206 (h, s, l, a)
207 })
208 }
209
210 #[must_use]
216 pub fn darken(self, percent: f64) -> Self {
217 assert_percent(percent);
218 self.map_hsla(|h, s, mut l, a| {
219 l = (l - percent).max(0.0);
221 (h, s, l, a)
222 })
223 }
224
225 #[must_use]
232 pub fn saturate(self, percent: f64) -> Self {
233 assert_percent(percent);
234 self.map_hsla(|h, mut s, l, a| {
235 s = (s + percent).min(1.0);
237 (h, s, l, a)
238 })
239 }
240
241 #[must_use]
248 pub fn desaturate(self, percent: f64) -> Self {
249 assert_percent(percent);
250 self.map_hsla(|h, mut s, l, a| {
251 s = (s - percent).max(0.0);
253 (h, s, l, a)
254 })
255 }
256
257 #[must_use]
260 pub fn rotate_hue(self, amount: f64) -> Self {
261 self.map_hsla(|mut h, s, l, a| {
262 h = ((h + amount) % 360.0 + 360.0) % 360.0;
264 (h, s, l, a)
265 })
266 }
267
268 pub fn to_u32(self) -> u32 {
270 self.0
271 }
272
273 pub fn brightness(self) -> f64 {
276 let (r, g, b, _a) = self.to_rgba();
277 let r = r as f64 / 255.0;
278 let g = g as f64 / 255.0;
279 let b = b as f64 / 255.0;
280 (299.0 * r + 587.0 * g + 114.0 * b) / 1_000.0
281 }
282
283 pub fn is_light(self) -> bool {
286 self.brightness() > 0.5
287 }
288
289 pub fn map<F>(&self, f: F) -> Self
315 where
316 F: Fn(u8, u8, u8, u8) -> (u8, u8, u8, u8),
317 {
318 let (r, g, b, a) = self.to_rgba();
319 let (r, g, b, a) = f(r, g, b, a);
320 Self::from_rgba(r, g, b, a)
321 }
322
323 fn map_hsla<F>(&self, f: F) -> Self
324 where
325 F: Fn(f64, f64, f64, f64) -> (f64, f64, f64, f64),
326 {
327 let (h, s, l, a) = self.to_hsla();
328 let (h, s, l, a) = f(h, s, l, a);
329 Color::from_hsla(h, s, l, a)
330 }
331
332 fn to_rgba(self) -> (u8, u8, u8, u8) {
333 let (r, g, b, a) = T::decode(self.0);
334 (r, g, b, a)
335 }
336
337 fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
338 Self::new(T::encode(r, g, b, a))
339 }
340
341 fn to_hsla(self) -> (f64, f64, f64, f64) {
346 let (r, g, b, a) = self.to_rgba();
347 let (h, s, l, a) = rgba_to_hsla(r, g, b, a);
348 (h, s, l, a)
349 }
350
351 fn from_hsla(h: f64, s: f64, l: f64, a: f64) -> Self {
352 let (r, g, b, a) = hsla_to_rgba(h, s, l, a);
353 Self::from_rgba(r as u8, g as u8, b as u8, a as u8)
354 }
355}
356
357#[cfg(not(any(test, feature = "color_debug")))]
358impl<T: Storage> fmt::Debug for Color<T> {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 let storage = format!("{}", std::any::type_name::<T>())
361 .split("::")
362 .last()
363 .expect("no type name")
364 .to_owned();
365 write!(f, "Color<{}>(", storage)?;
366 T::write_hex(f, self.0)?;
367 write!(f, ")")
368 }
369}
370
371#[cfg(any(test, feature = "color_debug"))]
372impl<T: Storage> fmt::Debug for Color<T> {
373 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
374 use crossterm::style;
375
376 let fg = style::SetForegroundColor(if self.is_light() {
377 style::Color::Black
378 } else {
379 style::Color::White
380 });
381 let (r, g, b, _a) = self.to_rgba();
382 let bg = style::SetBackgroundColor(style::Color::Rgb { r, g, b });
383
384 let storage = format!("{}", std::any::type_name::<T>())
385 .split("::")
386 .last()
387 .expect("no type name")
388 .to_owned();
389
390 write!(f, "Color<{}>({}{}", storage, fg, bg)?;
391 T::write_hex(f, self.0)?;
392 write!(f, "{})", style::ResetColor)
393 }
394}
395
396impl<T: Storage> From<u32> for Color<T> {
397 fn from(color: u32) -> Self {
398 Self::new(color)
399 }
400}
401
402impl<T: Storage> From<(u8, u8, u8)> for Color<T> {
403 fn from(rgb: (u8, u8, u8)) -> Self {
404 let (r, g, b) = rgb;
405 Self::from_rgba(r, g, b, 0)
406 }
407}
408
409impl<T: Storage> From<(u8, u8, u8, u8)> for Color<T> {
410 fn from(rgba: (u8, u8, u8, u8)) -> Self {
411 let (r, g, b, a) = rgba;
412 Self::from_rgba(r, g, b, a)
413 }
414}
415
416impl<T: Storage> From<Color<T>> for u32 {
417 fn from(color: Color<T>) -> u32 {
418 color.to_u32()
419 }
420}
421
422impl<T: Storage> From<Color<T>> for (u8, u8, u8) {
423 fn from(color: Color<T>) -> (u8, u8, u8) {
424 let (r, g, b, _a) = color.to_rgba();
425 (r, g, b)
426 }
427}
428
429impl<T: Storage> From<Color<T>> for (u8, u8, u8, u8) {
430 fn from(color: Color<T>) -> (u8, u8, u8, u8) {
431 color.to_rgba()
432 }
433}
434
435fn assert_percent(percent: f64) {
436 assert!(
437 (0.0..=1.0).contains(&percent),
438 "percent ({:?}) must be between 0.0 and 1.0",
439 percent
440 );
441}
442
443fn hsla_to_rgba(h: f64, s: f64, l: f64, mut a: f64) -> (u8, u8, u8, u8) {
445 debug_assert!(
446 (0.0..=360.0).contains(&h),
447 "h must be between 0.0 and 360.0"
448 );
449 debug_assert!((0.0..=1.0).contains(&s), "s must be between 0.0 and 1.0");
450 debug_assert!((0.0..=1.0).contains(&l), "l must be between 0.0 and 1.0");
451
452 let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
454
455 let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
457
458 let m = l - c / 2.0;
460
461 let (mut r, mut g, mut b) = if (0.0..60.0).contains(&h) {
464 (c, x, 0.0)
465 } else if (60.0..120.0).contains(&h) {
466 (x, c, 0.0)
467 } else if (120.0..180.0).contains(&h) {
468 (0.0, c, x)
469 } else if (180.0..240.0).contains(&h) {
470 (0.0, x, c)
471 } else if (240.0..300.0).contains(&h) {
472 (x, 0.0, c)
473 } else if (300.0..360.0).contains(&h) {
474 (c, 0.0, x)
475 } else {
476 unreachable!();
477 };
478
479 r = (r + m) * 255.0;
481 g = (g + m) * 255.0;
482 b = (b + m) * 255.0;
483 a = a * 255.0;
484
485 (r as u8, g as u8, b as u8, a as u8)
486}
487
488fn rgba_to_hsla(r: u8, g: u8, b: u8, a: u8) -> (f64, f64, f64, f64) {
490 let r = r as f64 / 255.0;
493 let g = g as f64 / 255.0;
494 let b = b as f64 / 255.0;
495 let a = a as f64 / 255.0;
496
497 let c_min = r.min(g.min(b));
500 let c_max = r.max(g.max(b));
501 let delta = c_max - c_min;
502
503 let error_margin = std::f64::EPSILON;
504
505 let mut h = if delta == 0.0 {
507 0.0
508 } else if (c_max - r).abs() < error_margin {
509 ((g - b) / delta) % 6.0
511 } else if (c_max - g).abs() < error_margin {
512 (b - r) / delta + 2.0
514 } else {
515 (r - g) / delta + 4.0
517 };
518
519 h *= 60.0;
520
521 if h < 0.0 {
523 h += 360.0;
524 }
525
526 let l = (c_max + c_min) / 2.0;
528
529 let s = if delta == 0.0 {
531 0.0
532 } else {
533 (delta / (1.0 - (2.0 * l - 1.0).abs())).min(1.0)
535 };
536
537 debug_assert!(s <= 1.0);
538 debug_assert!(l <= 1.0);
539
540 (h, s, l, a)
541}
542
543#[cfg(test)]
544mod tests {
545 use super::*;
546
547 #[test]
548 fn lighten() {
549 let color = Color::<ZRGB>::new(0x000000);
550
551 for percent in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0].iter() {
552 eprintln!("{:?}", color.lighten(*percent));
553 }
554
555 assert_eq!(Color::<ZRGB>::new(0x191919), color.lighten(0.1));
556 assert_eq!(Color::<ZRGB>::new(0x7f7f7f), color.lighten(0.5));
557 assert_eq!(Color::<ZRGB>::new(0xffffff), color.lighten(1.0));
558
559 assert_eq!(
560 Color::<ZRGB>::new(0xffffff),
561 Color::<ZRGB>::new(0xffffff).lighten(1.0)
562 );
563 }
564
565 #[test]
566 fn darken() {
567 let color = Color::<ZRGB>::new(0xffffff);
568
569 for percent in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0].iter() {
570 eprintln!("{:?}", color.darken(*percent));
571 }
572
573 assert_eq!(Color::<ZRGB>::new(0xe5e5e5), color.darken(0.1));
574 assert_eq!(Color::<ZRGB>::new(0x7f7f7f), color.darken(0.5));
575 assert_eq!(Color::<ZRGB>::new(0x000000), color.darken(1.0));
576
577 assert_eq!(
578 Color::<ZRGB>::new(0x000000),
579 Color::<ZRGB>::new(0x000000).darken(1.0)
580 );
581 }
582
583 #[test]
584 fn saturate() {
585 let color = Color::<ZRGB>::new(0xe2e2e2);
586
587 for percent in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0].iter() {
588 eprintln!("{:?}", color.saturate(*percent));
589 }
590
591 assert_eq!(Color::<ZRGB>::new(0xe4dfdf), color.saturate(0.1));
592 assert_eq!(Color::<ZRGB>::new(0xf0d3d3), color.saturate(0.5));
593 assert_eq!(Color::<ZRGB>::new(0xffc4c4), color.saturate(1.0));
594 }
595
596 #[test]
597 fn desaturate() {
598 let color = Color::<ZRGB>::new(0xffc4c4);
599
600 for percent in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0].iter() {
601 eprintln!("{:?}", color.desaturate(*percent));
602 }
603
604 assert_eq!(Color::<ZRGB>::new(0xfcc6c6), color.desaturate(0.1),);
605 assert_eq!(Color::<ZRGB>::new(0xf0d2d2), color.desaturate(0.5));
606 assert_eq!(Color::<ZRGB>::new(0xe1e1e1), color.desaturate(1.0));
607 }
608
609 #[test]
610 fn rotate_hue() {
611 let color = Color::<ZRGB>::new(0xffc4c4);
612
613 for n in (0..360).into_iter().step_by(10) {
614 eprintln!("{:?}", color.rotate_hue(n as f64));
615 }
616
617 assert_eq!(color.rotate_hue(100.0), Color::<ZRGB>::new(0xd7fec3));
618 assert_eq!(color.rotate_hue(200.0), Color::<ZRGB>::new(0xc3ebfe));
619 assert_eq!(color.rotate_hue(300.0), Color::<ZRGB>::new(0xfec3fe));
620 }
621
622 #[test]
623 fn to_rgba() {
624 assert_eq!(
625 (0xbb, 0xcc, 0xdd, 0x00),
626 Color::<ZRGB>::new(0xaabbccdd).to_rgba()
627 );
628 }
629
630 #[test]
631 fn from_rgba() {
632 assert_eq!(
633 Color::<ZRGB>::new(0xaabbccdd),
634 Color::<ZRGB>::from_rgba(0xbb, 0xcc, 0xdd, 0x00)
635 );
636 }
637
638 #[test]
639 fn to_hsla() {
640 let color = Color::<RGBA>::new(0x96643233);
641 let (h, s, l, a) = color.to_hsla();
642
643 let assert_float = |a: f64, b: f64| {
644 let error_margin = std::f64::EPSILON;
645 assert!((a - b).abs() < error_margin, "{:?} == {:?}", a, b)
646 };
647
648 assert_float(29.999999999999996, h);
649 assert_float(0.50000000000000014, s);
650 assert_float(0.39215686274509810, l);
651 assert_float(0.2, a);
652
653 Color::<ZRGB>::new(0xff2009).to_hsla();
655 }
656
657 #[test]
658 fn brightness() {
659 assert_eq!(0.0, Color::<ZRGB>::new(0x000000).brightness());
660 assert_eq!(1.0, Color::<ZRGB>::new(0xffffff).brightness());
661 }
662
663 #[test]
664 fn is_light() {
665 let light = Color::<ZRGB>::new(0xffffff);
666 let dark = Color::<ZRGB>::new(0x000000);
667
668 assert!(light.is_light());
669 assert!(!dark.is_light());
670 }
671
672 #[test]
673 fn map() {
674 let color = Color::<ZRGB>::new(0x00000000);
675 assert_eq!(
676 color
677 .map(|r, g, b, a| (r + 1, g + 2, b + 3, a + 4))
678 .to_u32(),
679 0x00010203
680 );
681
682 let color = Color::<RGBA>::new(0x00000000);
683 assert_eq!(
684 color
685 .map(|r, g, b, a| (r + 1, g + 2, b + 3, a + 4))
686 .to_u32(),
687 0x01020304
688 );
689
690 const RED: fn(u8, u8, u8, u8) -> (u8, u8, u8, u8) = |_r, g, b, a| (255, g, b, a);
691 assert_eq!(color.map(RED).to_u32(), 0xff000000);
692 }
693
694 #[test]
695 fn storage() {
696 assert_eq!(0x00bbccdd, ZRGB::init(0xaabbccdd));
697 assert_eq!((0xbb, 0xcc, 0xdd, 0x00), ZRGB::decode(0xaabbccdd));
698 assert_eq!(0x00bbccdd, ZRGB::encode(0xbb, 0xcc, 0xdd, 0xaa));
699
700 assert_eq!(0xaabbccdd, RGBA::init(0xaabbccdd));
701 assert_eq!((0xaa, 0xbb, 0xcc, 0xdd), RGBA::decode(0xaabbccdd));
702 assert_eq!(0xaabbccdd, RGBA::encode(0xaa, 0xbb, 0xcc, 0xdd));
703
704 eprintln!("{:?}", Color::<ZRGB>::new(0xff_facade));
706 eprintln!("{:?}", Color::<RGBA>::new(0xfacade_ff));
707 }
708
709 #[test]
710 fn test_rgba_to_hsla() {
711 let (h, s, l, a) = rgba_to_hsla(1, 2, 3, 4);
712 let (r, g, b, a) = hsla_to_rgba(h, s, l, a);
713
714 assert_eq!(r, 1);
715 assert_eq!(g, 2);
716 assert_eq!(b, 3);
717 assert_eq!(a, 4);
718 }
719}