boja/format/
rgb.rs

1use super::{hsl::Hsl, hsv::Hsv};
2use crate::Color;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
5pub(crate) struct Rgb {
6    red: u8,
7    green: u8,
8    blue: u8,
9}
10
11impl From<Color> for Rgb {
12    fn from(color: Color) -> Self {
13        Self {
14            red: color.red,
15            green: color.green,
16            blue: color.blue,
17        }
18    }
19}
20
21impl From<(u8, u8, u8)> for Rgb {
22    fn from((red, green, blue): (u8, u8, u8)) -> Self {
23        Self { red, green, blue }
24    }
25}
26
27impl From<Hsl> for Rgb {
28    fn from(value: Hsl) -> Self {
29        let (h, s, l) = value.into_tuple();
30
31        debug_assert!((0..360).contains(&h));
32
33        let s = s as f64 / 100f64;
34        let l = l as f64 / 100f64;
35
36        let c: f64 = (1.0 - (2.0 * l - 1.0).abs()) * s;
37        let x = c * (1.0 - ((h as f64 / 60.0) % 2.0 - 1.0).abs());
38        let m = l - c / 2.0;
39
40        let (r1, g1, b1) = match h % 360 {
41            0..=59 => (c, x, 0.0),
42            60..=119 => (x, c, 0.0),
43            120..=179 => (0.0, c, x),
44            180..=239 => (0.0, x, c),
45            240..=299 => (x, 0.0, c),
46            300..=359 => (c, 0.0, x),
47            360.. => unreachable!("hsl hue value must be between 0 and 360"),
48        };
49
50        let red = ((r1 + m) * 255.0).round() as i64;
51        let green = ((g1 + m) * 255.0).round() as i64;
52        let blue = ((b1 + m) * 255.0).round() as i64;
53
54        debug_assert!(u8::try_from(red).is_ok());
55        debug_assert!(u8::try_from(green).is_ok());
56        debug_assert!(u8::try_from(blue).is_ok());
57
58        Self {
59            red: red as u8,
60            green: green as u8,
61            blue: blue as u8,
62        }
63    }
64}
65
66impl From<Hsv> for Rgb {
67    fn from(value: Hsv) -> Self {
68        let (h, s, v) = value.into_tuple();
69
70        debug_assert!((0..=360).contains(&h));
71        debug_assert!((0..=100).contains(&s));
72        debug_assert!((0..=100).contains(&v));
73
74        let s = s as f64 / 100f64;
75        let v = v as f64 / 100f64;
76
77        let c: f64 = v * s;
78        let x = c * (1.0 - ((h as f64 / 60f64) % 2f64 - 1f64).abs());
79        let m = v - c;
80
81        let (r1, g1, b1) = match h % 360 {
82            0..=59 => (c, x, 0.0),
83            60..=119 => (x, c, 0.0),
84            120..=179 => (0.0, c, x),
85            180..=239 => (0.0, x, c),
86            240..=299 => (x, 0.0, c),
87            300..=359 => (c, 0.0, x),
88            360.. => unreachable!("hsl hue value must be between 0 and 360"),
89        };
90
91        let red = ((r1 + m) * 255.0).round() as i64;
92        let green = ((g1 + m) * 255.0).round() as i64;
93        let blue = ((b1 + m) * 255.0).round() as i64;
94
95        debug_assert!(u8::try_from(red).is_ok());
96        debug_assert!(u8::try_from(green).is_ok());
97        debug_assert!(u8::try_from(blue).is_ok());
98
99        Self {
100            red: red as u8,
101            green: green as u8,
102            blue: blue as u8,
103        }
104    }
105}
106
107impl Rgb {
108    pub fn to_rgb(self) -> String {
109        format!("rgb({}, {}, {})", self.red, self.green, self.blue)
110    }
111
112    pub fn to_hex(self) -> String {
113        format!("{:02x}{:02x}{:02x}", self.red, self.green, self.blue)
114    }
115
116    pub fn into_tuple(self) -> (u8, u8, u8) {
117        (self.red, self.green, self.blue)
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use crate::format::hsl::Hsl;
124
125    use super::Rgb;
126
127    #[test]
128    fn black_from_hsl() {
129        let hsl = Hsl::from((0, 0, 0));
130
131        let rgb = Rgb::from(hsl);
132
133        assert_eq!(
134            rgb,
135            Rgb {
136                red: 0,
137                green: 0,
138                blue: 0
139            }
140        )
141    }
142
143    #[test]
144    fn white_from_hsl() {
145        let hsl = Hsl::from((0, 0, 100));
146
147        let rgb = Rgb::from(hsl);
148
149        assert_eq!(
150            rgb,
151            Rgb {
152                red: 255,
153                green: 255,
154                blue: 255
155            }
156        )
157    }
158
159    #[test]
160    fn red_from_hsl() {
161        let hsl = Hsl::from((0, 100, 50));
162
163        let rgb = Rgb::from(hsl);
164
165        assert_eq!(
166            rgb,
167            Rgb {
168                red: 255,
169                green: 0,
170                blue: 0
171            }
172        )
173    }
174
175    #[test]
176    fn olive_from_hsl() {
177        let hsl = Hsl::from((60, 100, 25));
178
179        let rgb = Rgb::from(hsl);
180
181        assert_eq!(
182            rgb,
183            Rgb {
184                red: 128,
185                green: 128,
186                blue: 0
187            }
188        )
189    }
190}