1use iced_core::Color;
4
5#[derive(Clone, Copy, Debug, PartialEq)]
7pub struct Hsv {
8 pub(crate) hue: u16,
10 pub(crate) saturation: f32,
12 pub(crate) value: f32,
14}
15
16impl Hsv {
17 #[must_use]
19 pub const fn from_hsv(hue: u16, saturation: f32, value: f32) -> Self {
20 Self {
21 hue,
22 saturation,
23 value,
24 }
25 }
26}
27
28pub trait HexString {
30 fn as_hex_string(&self) -> String;
32}
33
34impl HexString for Color {
35 fn as_hex_string(&self) -> String {
36 format!(
37 "#{:02X?}{:02X?}{:02X?}{:02X?}",
38 (self.r * 255.0) as u8,
39 (self.g * 255.0) as u8,
40 (self.b * 255.0) as u8,
41 (self.a * 255.0) as u8,
42 )
43 }
44}
45
46impl From<Color> for Hsv {
47 fn from(color: Color) -> Self {
49 let max = color.r.max(color.g.max(color.b));
50 let min = color.r.min(color.g.min(color.b));
51
52 let hue = if (max - min).abs() < f32::EPSILON {
53 0.0
54 } else if (max - color.r).abs() < f32::EPSILON {
55 60.0 * (0.0 + (color.g - color.b) / (max - min))
56 } else if (max - color.g).abs() < f32::EPSILON {
57 60.0 * (2.0 + (color.b - color.r) / (max - min))
58 } else {
59 60.0 * (4.0 + (color.r - color.g) / (max - min))
60 };
61
62 let hue = if hue < 0.0 { hue + 360.0 } else { hue } as u16 % 360;
63
64 let saturation = if max == 0.0 { 0.0 } else { (max - min) / max };
65
66 let value = max;
67
68 Self {
69 hue,
70 saturation,
71 value,
72 }
73 }
74}
75
76impl From<Hsv> for Color {
77 fn from(hsv: Hsv) -> Self {
78 let h_i = (f32::from(hsv.hue) / 60.0).floor();
80 let f = (f32::from(hsv.hue) / 60.0) - h_i;
81
82 let p = hsv.value * (1.0 - hsv.saturation);
83 let q = hsv.value * (1.0 - hsv.saturation * f);
84 let t = hsv.value * (1.0 - hsv.saturation * (1.0 - f));
85
86 let h_i = h_i as u8;
87 let (red, green, blue) = match h_i {
88 1 => (q, hsv.value, p),
89 2 => (p, hsv.value, t),
90 3 => (p, q, hsv.value),
91 4 => (t, p, hsv.value),
92 5 => (hsv.value, p, q),
93 _ => (hsv.value, t, p),
94 };
95
96 Self::from_rgb(red, green, blue)
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use iced_core::Color;
103
104 use super::Hsv;
105
106 #[allow(clippy::cognitive_complexity)]
107 #[test]
108 fn rgb_to_hsv() {
109 let red_rgb = Color::from_rgb(1.0, 0.0, 0.0);
111 let red_hsv = Hsv::from_hsv(0, 1.0, 1.0);
112 assert_eq!(red_hsv, red_rgb.into());
113
114 let orange_rgb = Color::from_rgb(1.0, 0.5, 0.0);
115 let orange_hsv = Hsv::from_hsv(30, 1.0, 1.0);
116 assert_eq!(orange_hsv, orange_rgb.into());
117
118 let yellow_rgb = Color::from_rgb(1.0, 1.0, 0.0);
119 let yellow_hsv = Hsv::from_hsv(60, 1.0, 1.0);
120 assert_eq!(yellow_hsv, yellow_rgb.into());
121
122 let dark_green_rgb = Color::from_rgb(0.0, 0.5, 0.0);
123 let dark_green_hsv = Hsv::from_hsv(120, 1.0, 0.5);
124 assert_eq!(dark_green_hsv, dark_green_rgb.into());
125
126 let violett_rgb = Color::from_rgb(0.5, 0.0, 1.0);
127 let violett_hsv = Hsv::from_hsv(270, 1.0, 1.0);
128 assert_eq!(violett_hsv, violett_rgb.into());
129
130 let black_rgb = Color::from_rgb(0.0, 0.0, 0.0);
131 let black_hsv = Hsv::from_hsv(0, 0.0, 0.0);
132 assert_eq!(black_hsv, black_rgb.into());
133
134 let blue_rgb = Color::from_rgb(0.0, 0.0, 1.0);
135 let blue_hsv = Hsv::from_hsv(240, 1.0, 1.0);
136 assert_eq!(blue_hsv, blue_rgb.into());
137
138 let brown_rgb = Color::from_rgb(0.36, 0.18, 0.09);
139 let brown_hsv = Hsv::from_hsv(20, 0.75, 0.36);
140 assert_eq!(brown_hsv, brown_rgb.into());
141
142 let white_rgb = Color::from_rgb(1.0, 1.0, 1.0);
143 let white_hsv = Hsv::from_hsv(0, 0.0, 1.0);
144 assert_eq!(white_hsv, white_rgb.into());
145
146 let green_rgb = Color::from_rgb(0.0, 1.0, 0.0);
147 let green_hsv = Hsv::from_hsv(120, 1.0, 1.0);
148 assert_eq!(green_hsv, green_rgb.into());
149
150 let cyan_rgb = Color::from_rgb(0.0, 1.0, 1.0);
151 let cyan_hsv = Hsv::from_hsv(180, 1.0, 1.0);
152 assert_eq!(cyan_hsv, cyan_rgb.into());
153
154 let magenta_rgb = Color::from_rgb(1.0, 0.0, 1.0);
155 let magenta_hsv = Hsv::from_hsv(300, 1.0, 1.0);
156 assert_eq!(magenta_hsv, magenta_rgb.into());
157
158 let blue_green_rgb = Color::from_rgb(0.0, 1.0, 0.5);
159 let blue_green_hsv = Hsv::from_hsv(150, 1.0, 1.0);
160 assert_eq!(blue_green_hsv, blue_green_rgb.into());
161
162 let green_blue_rgb = Color::from_rgb(0.0, 0.5, 1.0);
163 let green_blue_hsv = Hsv::from_hsv(210, 1.0, 1.0);
164 assert_eq!(green_blue_hsv, green_blue_rgb.into());
165
166 let green_yellow_rgb = Color::from_rgb(0.5, 1.0, 0.0);
167 let green_yellow_hsv = Hsv::from_hsv(90, 1.0, 1.0);
168 assert_eq!(green_yellow_hsv, green_yellow_rgb.into());
169
170 let blue_red_rgb = Color::from_rgb(1.0, 0.0, 0.5);
171 let blue_red_hsv = Hsv::from_hsv(330, 1.0, 1.0);
172 assert_eq!(blue_red_hsv, blue_red_rgb.into());
173
174 let zinnober_rgb = Color::from_rgb(1.0, 0.25, 0.0);
175 let zinnober_hsv = Hsv::from_hsv(15, 1.0, 1.0);
176 assert_eq!(zinnober_hsv, zinnober_rgb.into());
177
178 let indigo_rgb = Color::from_rgb(0.25, 0.0, 1.0);
179 let indigo_hsv = Hsv::from_hsv(255, 1.0, 1.0);
180 assert_eq!(indigo_hsv, indigo_rgb.into());
181
182 let light_blue_green_rgb = Color::from_rgb(0.0, 1.0, 0.25);
183 let light_blue_green_hsv = Hsv::from_hsv(135, 1.0, 1.0);
184 assert_eq!(light_blue_green_hsv, light_blue_green_rgb.into());
185
186 let blue_cyan_rgb = Color::from_rgb(0.0, 0.75, 1.0);
187 let blue_cyan_hsv = Hsv::from_hsv(195, 1.0, 1.0);
188 assert_eq!(blue_cyan_hsv, blue_cyan_rgb.into());
189
190 let light_green_yellow_rgb = Color::from_rgb(0.75, 1.0, 0.0);
191 let light_green_yellow_hsv = Hsv::from_hsv(75, 1.0, 1.0);
192 assert_eq!(light_green_yellow_hsv, light_green_yellow_rgb.into());
193
194 let red_magenta_rgb = Color::from_rgb(1.0, 0.0, 0.75);
195 let red_magenta_hsv = Hsv::from_hsv(315, 1.0, 1.0);
196 assert_eq!(red_magenta_hsv, red_magenta_rgb.into());
197
198 let safran_rgb = Color::from_rgb(1.0, 0.75, 0.0);
199 let safran_hsv = Hsv::from_hsv(45, 1.0, 1.0);
200 assert_eq!(safran_hsv, safran_rgb.into());
201
202 let blue_magenta_rgb = Color::from_rgb(0.75, 0.0, 1.0);
203 let blue_magenta_hsv = Hsv::from_hsv(285, 1.0, 1.0);
204 assert_eq!(blue_magenta_hsv, blue_magenta_rgb.into());
205
206 let green_cyan_rgb = Color::from_rgb(0.0, 1.0, 0.75);
207 let green_cyan_hsv = Hsv::from_hsv(165, 1.0, 1.0);
208 assert_eq!(green_cyan_hsv, green_cyan_rgb.into());
209
210 let light_green_blue_rgb = Color::from_rgb(0.0, 0.25, 1.0);
211 let light_green_blue_hsv = Hsv::from_hsv(225, 1.0, 1.0);
212 assert_eq!(light_green_blue_hsv, light_green_blue_rgb.into());
213
214 let lime_rgb = Color::from_rgb(0.25, 1.0, 0.0);
215 let lime_hsv = Hsv::from_hsv(105, 1.0, 1.0);
216 assert_eq!(lime_hsv, lime_rgb.into());
217
218 let light_blue_red_rgb = Color::from_rgb(1.0, 0.0, 0.25);
219 let light_blue_red_hsv = Hsv::from_hsv(345, 1.0, 1.0);
220 assert_eq!(light_blue_red_hsv, light_blue_red_rgb.into());
221 }
222
223 #[allow(clippy::cognitive_complexity)]
224 #[test]
225 fn hsv_to_rgb() {
226 let red_hsv = Hsv::from_hsv(0, 1.0, 1.0);
228 let red_rgb = Color::from_rgb(1.0, 0.0, 0.0);
229 assert_eq!(red_rgb, red_hsv.into());
230
231 let orange_hsv = Hsv::from_hsv(30, 1.0, 1.0);
232 let orange_rgb = Color::from_rgb(1.0, 0.5, 0.0);
233 assert_eq!(orange_rgb, orange_hsv.into());
234
235 let yellow_hsv = Hsv::from_hsv(60, 1.0, 1.0);
236 let yellow_rgb = Color::from_rgb(1.0, 1.0, 0.0);
237 assert_eq!(yellow_rgb, yellow_hsv.into());
238
239 let dark_green_hsv = Hsv::from_hsv(120, 1.0, 0.5);
240 let dark_green_rgb = Color::from_rgb(0.0, 0.5, 0.0);
241 assert_eq!(dark_green_rgb, dark_green_hsv.into());
242
243 let violett_hsv = Hsv::from_hsv(270, 1.0, 1.0);
244 let violett_rgb = Color::from_rgb(0.5, 0.0, 1.0);
245 assert_eq!(violett_rgb, violett_hsv.into());
246
247 let black_hsv = Hsv::from_hsv(0, 0.0, 0.0);
248 let black_rgb = Color::from_rgb(0.0, 0.0, 0.0);
249 assert_eq!(black_rgb, black_hsv.into());
250
251 let blue_hsv = Hsv::from_hsv(240, 1.0, 1.0);
252 let blue_rgb = Color::from_rgb(0.0, 0.0, 1.0);
253 assert_eq!(blue_rgb, blue_hsv.into());
254
255 let brown_hsv = Hsv::from_hsv(20, 0.75, 0.36);
256 let brown_rgb = Color::from_rgb(0.36, 0.18, 0.09);
257 assert_eq!(brown_rgb, brown_hsv.into());
258
259 let white_hsv = Hsv::from_hsv(0, 0.0, 1.0);
260 let white_rgb = Color::from_rgb(1.0, 1.0, 1.0);
261 assert_eq!(white_rgb, white_hsv.into());
262
263 let green_hsv = Hsv::from_hsv(120, 1.0, 1.0);
264 let green_rgb = Color::from_rgb(0.0, 1.0, 0.0);
265 assert_eq!(green_rgb, green_hsv.into());
266
267 let cyan_hsv = Hsv::from_hsv(180, 1.0, 1.0);
268 let cyan_rgb = Color::from_rgb(0.0, 1.0, 1.0);
269 assert_eq!(cyan_rgb, cyan_hsv.into());
270
271 let magenta_hsv = Hsv::from_hsv(300, 1.0, 1.0);
272 let magenta_rgb = Color::from_rgb(1.0, 0.0, 1.0);
273 assert_eq!(magenta_rgb, magenta_hsv.into());
274
275 let blue_green_hsv = Hsv::from_hsv(150, 1.0, 1.0);
276 let blue_green_rgb = Color::from_rgb(0.0, 1.0, 0.5);
277 assert_eq!(blue_green_rgb, blue_green_hsv.into());
278
279 let green_blue_hsv = Hsv::from_hsv(210, 1.0, 1.0);
280 let green_blue_rgb = Color::from_rgb(0.0, 0.5, 1.0);
281 assert_eq!(green_blue_rgb, green_blue_hsv.into());
282
283 let green_yellow_hsv = Hsv::from_hsv(90, 1.0, 1.0);
284 let green_yellow_rgb = Color::from_rgb(0.5, 1.0, 0.0);
285 assert_eq!(green_yellow_rgb, green_yellow_hsv.into());
286
287 let blue_red_hsv = Hsv::from_hsv(330, 1.0, 1.0);
288 let blue_red_rgb = Color::from_rgb(1.0, 0.0, 0.5);
289 assert_eq!(blue_red_rgb, blue_red_hsv.into());
290
291 let zinnober_hsv = Hsv::from_hsv(15, 1.0, 1.0);
292 let zinnober_rgb = Color::from_rgb(1.0, 0.25, 0.0);
293 assert_eq!(zinnober_rgb, zinnober_hsv.into());
294
295 let indigo_hsv = Hsv::from_hsv(255, 1.0, 1.0);
296 let indigo_rgb = Color::from_rgb(0.25, 0.0, 1.0);
297 assert_eq!(indigo_rgb, indigo_hsv.into());
298
299 let light_blue_green_hsv = Hsv::from_hsv(135, 1.0, 1.0);
300 let light_blue_green_rgb = Color::from_rgb(0.0, 1.0, 0.25);
301 assert_eq!(light_blue_green_rgb, light_blue_green_hsv.into());
302
303 let blue_cyan_hsv = Hsv::from_hsv(195, 1.0, 1.0);
304 let blue_cyan_rgb = Color::from_rgb(0.0, 0.75, 1.0);
305 assert_eq!(blue_cyan_rgb, blue_cyan_hsv.into());
306
307 let light_green_yellow_hsv = Hsv::from_hsv(75, 1.0, 1.0);
308 let light_green_yellow_rgb = Color::from_rgb(0.75, 1.0, 0.0);
309 assert_eq!(light_green_yellow_rgb, light_green_yellow_hsv.into());
310
311 let red_magenta_hsv = Hsv::from_hsv(315, 1.0, 1.0);
312 let red_magenta_rgb = Color::from_rgb(1.0, 0.0, 0.75);
313 assert_eq!(red_magenta_rgb, red_magenta_hsv.into());
314
315 let safran_hsv = Hsv::from_hsv(45, 1.0, 1.0);
316 let safran_rgb = Color::from_rgb(1.0, 0.75, 0.0);
317 assert_eq!(safran_rgb, safran_hsv.into());
318
319 let blue_magenta_hsv = Hsv::from_hsv(285, 1.0, 1.0);
320 let blue_magenta_rgb = Color::from_rgb(0.75, 0.0, 1.0);
321 assert_eq!(blue_magenta_rgb, blue_magenta_hsv.into());
322
323 let green_cyan_hsv = Hsv::from_hsv(165, 1.0, 1.0);
324 let green_cyan_rgb = Color::from_rgb(0.0, 1.0, 0.75);
325 assert_eq!(green_cyan_rgb, green_cyan_hsv.into());
326
327 let light_green_blue_hsv = Hsv::from_hsv(225, 1.0, 1.0);
328 let light_green_blue_rgb = Color::from_rgb(0.0, 0.25, 1.0);
329 assert_eq!(light_green_blue_rgb, light_green_blue_hsv.into());
330
331 let lime_hsv = Hsv::from_hsv(105, 1.0, 1.0);
332 let lime_rgb = Color::from_rgb(0.25, 1.0, 0.0);
333 assert_eq!(lime_rgb, lime_hsv.into());
334
335 let light_blue_red_hsv = Hsv::from_hsv(345, 1.0, 1.0);
336 let light_blue_red_rgb = Color::from_rgb(1.0, 0.0, 0.25);
337 assert_eq!(light_blue_red_rgb, light_blue_red_hsv.into());
338 }
339}