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}