orbtk_utils/
color.rs

1include!(concat!(env!("OUT_DIR"), "/colors.rs"));
2
3#[cfg(not(feature = "no_std"))]
4use std::fmt;
5
6/// A r g b a color.
7#[derive(Copy, Clone, PartialOrd, Default)]
8#[repr(packed)]
9pub struct Color {
10    pub data: u32,
11}
12
13impl Color {
14    /// Create a new color from RGB
15    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
16        Color {
17            data: 0xFF00_0000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32),
18        }
19    }
20
21    /// Create a new color from HSV(0.0-360.0, 0.0-1.0, 0.0-1.0)
22    pub fn hsv(h: f64, s: f64, v: f64) -> Self {
23        Self::hsva(h, s, v, 1.0)
24    }
25
26    /// Create a new color from HSL(0.0-360.0, 0.0-1.0, 0.0-1.0)
27    pub fn hsl(h: f64, s: f64, l: f64) -> Self {
28        Self::hsla(h, s, l, 1.0)
29    }
30
31    /// Create a new color from RGB and alpha values
32    pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
33        Color {
34            data: ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32),
35        }
36    }
37
38    /// Create a new color from HSV(0.0-360.0, 0.0-1.0, 0.0-1.0) and alpha values(0.0-1.0)
39    pub fn hsva(mut hue: f64, mut saturation: f64, mut value: f64, alpha: f64) -> Self {
40        hue %= 360.0;
41        saturation = saturation.max(0.0).min(1.0);
42        value = value.max(0.0).min(1.0);
43        let hh = hue / 60.0;
44        let idx = hh.floor() as i32;
45        let ff = hh.fract();
46        let chroma = value * (1.0 - saturation);
47        let second_component = value * (1.0 - (saturation * ff));
48        let t = value * (1.0 - (saturation * (1.0 - ff)));
49        let (r, g, b) = match idx {
50            0 => (value, t, chroma),
51            1 => (second_component, value, chroma),
52            2 => (chroma, value, t),
53            3 => (chroma, second_component, value),
54            4 => (t, chroma, value),
55            5 => (value, chroma, second_component),
56            _ => unreachable!(),
57        };
58        Self::rgba(
59            (r * 255.0) as u8,
60            (g * 255.0) as u8,
61            (b * 255.0) as u8,
62            (alpha * 255.0) as u8,
63        )
64    }
65
66    /// Create a new color from HSL(0.0-360.0, 0.0-1.0, 0.0-1.0) and alpha values(0.0-1.0)
67    pub fn hsla(mut hue: f64, mut saturation: f64, mut lightness: f64, alpha: f64) -> Self {
68        hue %= 360.0;
69        saturation = saturation.max(0.0).min(1.0);
70        lightness = lightness.max(0.0).min(1.0);
71        let hh = hue / 60.0;
72        let idx = hh.floor() as i32;
73        let chroma = (1.0 - ((2.0 * lightness) - 1.0).abs()) * saturation;
74        let second_component = chroma * (1.0 - (((idx % 2) as f64) - 1.0).abs());
75        let (mut r, mut g, mut b) = match idx {
76            0 => (chroma, second_component, 0.0),
77            1 => (second_component, chroma, 0.0),
78            2 => (0.0, chroma, second_component),
79            3 => (0.0, second_component, chroma),
80            4 => (second_component, 0.0, chroma),
81            5 => (chroma, 0.0, second_component),
82            _ => unreachable!(),
83        };
84        let adjustment = lightness - chroma / 2.0;
85        r += adjustment;
86        g += adjustment;
87        b += adjustment;
88        Self::rgba(
89            (r.min(1.0) * 255.0) as u8,
90            (g.min(1.0) * 255.0) as u8,
91            (b.min(1.0) * 255.0) as u8,
92            (alpha * 255.0) as u8,
93        )
94    }
95
96    /// Get the r value
97    pub fn r(self) -> u8 {
98        ((self.data & 0x00FF_0000) >> 16) as u8
99    }
100
101    /// Get the g value
102    pub fn g(self) -> u8 {
103        ((self.data & 0x0000_FF00) >> 8) as u8
104    }
105
106    /// Get the b value
107    pub fn b(self) -> u8 {
108        (self.data & 0x0000_00FF) as u8
109    }
110
111    /// Get the alpha value
112    pub fn a(self) -> u8 {
113        ((self.data & 0xFF00_0000) >> 24) as u8
114    }
115
116    /// Attempts to get a color from its name, all the CSS colors are avaible and some other ones also.
117    pub fn from_name(name: &str) -> Option<Color> {
118        COLORS.get(name).cloned()
119    }
120
121    /// Interpolate between two colors
122    pub fn interpolate(start_color: Color, end_color: Color, scale: f64) -> Color {
123        let r = Color::interp(start_color.r(), end_color.r(), scale);
124        let g = Color::interp(start_color.g(), end_color.g(), scale);
125        let b = Color::interp(start_color.b(), end_color.b(), scale);
126        let a = Color::interp(start_color.a(), end_color.a(), scale);
127        Color::rgba(r, g, b, a)
128    }
129
130    fn interp(start_color: u8, end_color: u8, scale: f64) -> u8 {
131        (end_color as f64 - start_color as f64).mul_add(scale, start_color as f64) as u8
132    }
133}
134
135impl ToString for Color {
136    fn to_string(&self) -> String {
137        if self.a() == 0 {
138            return String::from("transparent");
139        }
140
141        let data = self.data;
142
143        let mut color = format!("#{:x}", data);
144        color.remove(1);
145        color.remove(1);
146        color
147    }
148}
149
150impl From<&str> for Color {
151    fn from(s: &str) -> Color {
152        if !s.starts_with('#') {
153            if let Some(color) = Color::from_name(s) {
154                return color;
155            }
156        }
157        let clean_hex = s.trim_start_matches('#');
158        match clean_hex.len() {
159            3 | 4 => {
160                let num = u32::from_str_radix(clean_hex, 16).unwrap_or(0);
161
162                let mut blue = (num & 0xF) << 4;
163                let mut green = ((num >> 4) & 0xF) << 4;
164                let mut red = ((num >> 8) & 0xF) << 4;
165                let mut alpha = match clean_hex.len() == 4 {
166                    true => ((num >> 12) & 0xF) << 4,
167                    false => 0xF,
168                };
169                red |= red >> 4;
170                green |= green >> 4;
171                blue |= blue >> 4;
172                alpha |= alpha >> 4;
173
174                Color::rgba(red as u8, green as u8, blue as u8, alpha as u8)
175            }
176            6 | 8 => {
177                let mut x = u32::from_str_radix(clean_hex, 16).unwrap_or(0);
178
179                if clean_hex.len() == 6 {
180                    x |= 0xFF00_0000;
181                }
182
183                Color { data: x }
184            }
185            _ => Color { data: 0 },
186        }
187    }
188}
189
190impl From<String> for Color {
191    fn from(s: String) -> Color {
192        Color::from(s.as_str())
193    }
194}
195
196/// Compares two colors (the alpha value is ignored)
197impl PartialEq for Color {
198    fn eq(&self, other: &Color) -> bool {
199        self.r() == other.r() && self.g() == other.g() && self.b() == other.b()
200    }
201}
202
203#[cfg(not(feature = "no_std"))]
204impl fmt::Debug for Color {
205    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
206        write!(f, "{:#010X}", { self.data })
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213    #[test]
214    fn partial_eq() {
215        let color_1 = Color::rgb(1, 2, 3);
216        let color_2 = Color::rgba(1, 2, 3, 200);
217        let color_3 = Color::rgba(11, 2, 3, 200);
218
219        assert!(
220            color_1.eq(&color_2),
221            "color_1={:?}(, color_2={:?}",
222            &color_1,
223            &color_2
224        );
225        assert!(
226            color_1.eq(&color_2),
227            "color_1={:?}(, color_2={:?}",
228            &color_1,
229            &color_2
230        );
231        assert!(
232            color_2.ne(&color_3),
233            "color_1={:?}(, color_2={:?}",
234            &color_2,
235            &color_3
236        );
237    }
238}