[go: up one dir, main page]

coolor/
color.rs

1use crate::*;
2
3#[cfg(feature = "crossterm")]
4use crossterm::style::Color as CC;
5
6/// Color type, may be Ansi, Hsl or Rgb
7#[derive(Clone, Copy, Debug)]
8pub enum Color {
9    Ansi(AnsiColor),
10    Hsl(Hsl),
11    Rgb(Rgb),
12}
13
14impl Color {
15    pub fn ansi(self) -> AnsiColor {
16        match self {
17            Self::Ansi(ansi) => ansi,
18            Self::Hsl(hsl) => hsl.to_ansi(),
19            Self::Rgb(rgb) => rgb.to_ansi(),
20        }
21    }
22    pub fn hsl(self) -> Hsl {
23        match self {
24            Self::Ansi(ansi) => ansi.to_hsl(),
25            Self::Hsl(hsl) => hsl,
26            Self::Rgb(rgb) => rgb.to_hsl(),
27        }
28    }
29    pub fn rgb(self) -> Rgb {
30        match self {
31            Self::Ansi(ansi) => ansi.to_rgb(),
32            Self::Hsl(hsl) => hsl.to_rgb(),
33            Self::Rgb(rgb) => rgb,
34        }
35    }
36    pub fn luma(self) -> f32 {
37        self.rgb().luma()
38    }
39    /// compute a natural feeling intermediate between two colors
40    pub fn blend<C1: Into<Color>, C2: Into<Color>>(c1: C1, w1: f32, c2: C2, w2: f32) -> Self {
41        let c1: Color = c1.into();
42        let c2: Color = c2.into();
43        debug_assert!(w1 + w2 > 0.0);
44        let hsl1: Hsl = c1.hsl();
45        let hsl2: Hsl = c2.hsl();
46        let mixed_hsl = Hsl::mix(hsl1, w1, hsl2, w2);
47        let rgb1: Rgb = hsl1.to_rgb();
48        let rgb2: Rgb = hsl2.to_rgb();
49        let mixed_rgb = Rgb::mix(rgb1, w1, rgb2, w2);
50        let mixed_rgb_hsl = mixed_rgb.to_hsl();
51        let mixed = Hsl::mix(mixed_hsl, 0.5, mixed_rgb_hsl, 0.5);
52        Hsl {
53            h: mixed_rgb_hsl.h, // hue blending done only on rgb space
54            s: mixed.s,         // saturation is mix between RGB computation and HSL one
55            l: mixed.l,         // luminosity is mix between RGB computation and HSL one
56        }
57        .into()
58    }
59}
60
61impl From<AnsiColor> for Color {
62    fn from(ansi: AnsiColor) -> Self {
63        Self::Ansi(ansi)
64    }
65}
66impl From<Rgb> for Color {
67    fn from(rgb: Rgb) -> Self {
68        Self::Rgb(rgb)
69    }
70}
71impl From<Hsl> for Color {
72    fn from(rgb: Hsl) -> Self {
73        Self::Hsl(rgb)
74    }
75}
76impl From<u8> for Color {
77    fn from(code: u8) -> Self {
78        Self::Ansi(AnsiColor::new(code))
79    }
80}
81
82#[cfg(feature = "crossterm")]
83impl From<CC> for Color {
84    fn from(cc: CC) -> Self {
85        match cc {
86            CC::Reset => 0.into(),
87            CC::Black => 0.into(),
88            CC::DarkGrey => 8.into(),
89            CC::Red => 9.into(),
90            CC::DarkRed => 1.into(),
91            CC::Green => 10.into(),
92            CC::DarkGreen => 2.into(),
93            CC::Yellow => 11.into(),
94            CC::DarkYellow => 3.into(),
95            CC::Blue => 12.into(),
96            CC::DarkBlue => 4.into(),
97            CC::Magenta => 13.into(),
98            CC::DarkMagenta => 5.into(),
99            CC::Cyan => 14.into(),
100            CC::DarkCyan => 6.into(),
101            CC::White => 15.into(),
102            CC::Grey => 7.into(),
103            CC::Rgb { r, g, b } => Color::Rgb(Rgb { r, g, b }),
104            CC::AnsiValue(code) => code.into(),
105        }
106    }
107}
108
109#[cfg(feature = "crossterm")]
110impl Into<CC> for Color {
111    fn into(self) -> CC {
112        match self {
113            Self::Ansi(AnsiColor { code }) => CC::AnsiValue(code),
114            Self::Rgb(Rgb { r, g, b }) => CC::Rgb { r, g, b },
115            Self::Hsl(hsl) => {
116                let Rgb { r, g, b } = hsl.to_rgb();
117                CC::Rgb { r, g, b }
118            }
119        }
120    }
121}
122
123/// check going from ansi to rgb and back makes us fall on the first color
124#[test]
125fn test_ansi_to_rgb_to_ansi() {
126    // we don't check the range 0..16 as it's made of colors
127    // which are also in the 16..255 range
128    for code in 16..=255 {
129        let c1 = AnsiColor { code };
130        let c2 = c1.to_rgb();
131        let c3 = c2.to_ansi();
132        assert_eq!(c1, c3);
133    }
134}
135/// check going from ansi to hsl and back makes us fall on the first color
136#[test]
137fn test_ansi_to_hsl_to_ansi() {
138    // we don't check the range 0..16 as it's made of colors
139    // which are also in the 16..255 range
140    for code in 16..=255 {
141        let c1 = AnsiColor { code };
142        let c2 = c1.to_hsl();
143        let c3 = c2.to_ansi();
144        assert_eq!(c1, c3);
145    }
146}
147#[test]
148fn test_rgb_to_hsl() {
149    assert!(Rgb::new(255, 0, 0).to_hsl().near(Hsl::new(0.0, 1.0, 0.5))); // red
150    assert!(Rgb::new(255, 255, 0)
151        .to_hsl()
152        .near(Hsl::new(60.0, 1.0, 0.5))); // yellow
153    assert!(Rgb::new(255, 255, 255)
154        .to_hsl()
155        .near(Hsl::new(0.0, 0.0, 1.0))); // white
156}
157/// check going from hsl to rgb and back makes us fall on the first color (or not too far)
158#[test]
159fn test_hsl_to_rgb_to_hsl() {
160    let red = Hsl::new(0.0, 1.0, 0.5);
161    let yellow = Hsl::new(60.0, 1.0, 0.5);
162    let white = Hsl::new(0.0, 0.0, 1.0);
163    assert!(red.to_rgb().to_hsl().near(red));
164    assert!(yellow.to_rgb().to_hsl().near(yellow));
165    assert!(white.to_rgb().to_hsl().near(white));
166}