1use crate::{ColorHSIA, ColorHSLA, ColorHSVA, ColorRGBA};
9
10#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
12struct ColorHCMA {
13 h: f32,
15 c: f32,
17 m: f32,
19 a: f32,
21}
22
23impl ColorHCMA {
24 fn from_rgb(color: &ColorRGBA) -> Self {
26 let colors = [color.get_red(), color.get_green(), color.get_blue()];
28
29 let (i_min, i_max) = if colors[0] < colors[1] && colors[0] < colors[2] {
31 let i_max = if colors[1] < colors[2] { 2 } else { 1 };
32
33 (0, i_max)
34 } else if colors[1] < colors[2] {
35 let i_max = if colors[0] < colors[2] { 2 } else { 0 };
36
37 (1, i_max)
38 } else {
39 let i_max = if colors[0] < colors[1] { 1 } else { 0 };
40
41 (2, i_max)
42 };
43
44 let hue_major = (i_min + 1) % 3;
46
47 let hue_minor = ((i_max + 3) - i_min) % 3 - 1;
49
50 let m = colors[i_min];
52 let c = colors[i_max] - m;
53 let x = colors[(hue_major + (hue_minor + 1) % 2) % 3] - m;
54
55 if c == 0.0 {
57 return Self {
58 h: 0.0,
59 c: 0.0,
60 m: m,
61 a: color.get_alpha(),
62 };
63 }
64
65 let hp = 2.0 * (hue_major as f32) + if hue_minor == 0 { x / c } else { 2.0 - x / c };
67
68 return Self {
69 h: hp / 6.0,
70 c: c,
71 m: m,
72 a: color.get_alpha(),
73 };
74 }
75
76 fn from_hsv(color: &ColorHSVA) -> Self {
78 let c = color.get_value() * color.get_saturation();
79 let m = color.get_value() - c;
80
81 return Self {
82 h: color.get_hue(),
83 c,
84 m,
85 a: color.get_alpha(),
86 };
87 }
88
89 fn from_hsl(color: &ColorHSLA) -> Self {
91 let c = (1.0 - (2.0 * color.get_lightness() - 1.0).abs()) * color.get_saturation();
92 let m = color.get_lightness() - 0.5 * c;
93
94 return Self {
95 h: color.get_hue(),
96 c,
97 m,
98 a: color.get_alpha(),
99 };
100 }
101
102 fn from_hsi(color: &ColorHSIA) -> Self {
104 let z = 1.0 - ((6.0 * color.get_hue()).rem_euclid(2.0) - 1.0).abs();
105 let c = 3.0 * color.get_intensity() * color.get_saturation() / (1.0 + z);
106 let m = color.get_intensity() * (1.0 - color.get_saturation());
107
108 return Self {
109 h: color.get_hue(),
110 c,
111 m,
112 a: color.get_alpha(),
113 };
114 }
115
116 fn to_rgb(&self) -> ColorRGBA {
118 let hp = self.h * 6.0;
120 let z = 1.0 - (hp.rem_euclid(2.0) - 1.0).abs();
121 let x = self.c * z;
122
123 let colors = if hp.rem_euclid(2.0) < 1.0 {
125 [self.c, x, 0.0]
126 } else {
127 [x, self.c, 0.0]
128 };
129
130 let i = hp.div_euclid(2.0) as usize;
132
133 return unsafe {
135 ColorRGBA::new_unsafe(
136 colors[(3 - i) % 3] + self.m,
137 colors[(4 - i) % 3] + self.m,
138 colors[(5 - i) % 3] + self.m,
139 self.a,
140 )
141 };
142 }
143
144 fn to_hsv(&self) -> ColorHSVA {
146 let v = self.m + self.c;
147 let s = if v == 0.0 { 0.0 } else { self.c / v };
148
149 return unsafe { ColorHSVA::new_unsafe(self.h, s, v, self.a) };
150 }
151
152 fn to_hsl(&self) -> ColorHSLA {
154 let l = self.m + 0.5 * self.c;
155 let z = 1.0 - (2.0 * l - 1.0).abs();
156 let s = if z == 0.0 { 0.0 } else { self.c / z };
157
158 return unsafe { ColorHSLA::new_unsafe(self.h, s, l, self.a) };
159 }
160
161 fn to_hsi(&self) -> ColorHSIA {
163 let z = 1.0 - ((6.0 * self.h).rem_euclid(2.0) - 1.0).abs();
164 let i = self.m + self.c * (1.0 + z) / 3.0;
165 let s = if i == 0.0 { 0.0 } else { 1.0 - self.m / i };
166
167 return unsafe { ColorHSIA::new_unsafe(self.h, s, i, self.a) };
168 }
169}
170
171pub fn rgb_to_hsv(color: &ColorRGBA) -> ColorHSVA {
177 return ColorHCMA::from_rgb(color).to_hsv();
178}
179
180pub fn rgb_to_hsl(color: &ColorRGBA) -> ColorHSLA {
186 return ColorHCMA::from_rgb(color).to_hsl();
187}
188
189pub fn rgb_to_hsi(color: &ColorRGBA) -> ColorHSIA {
195 return ColorHCMA::from_rgb(color).to_hsi();
196}
197
198pub fn hsv_to_hsl(color: &ColorHSVA) -> ColorHSLA {
204 return ColorHCMA::from_hsv(color).to_hsl();
205}
206
207pub fn hsv_to_hsi(color: &ColorHSVA) -> ColorHSIA {
213 return ColorHCMA::from_hsv(color).to_hsi();
214}
215
216pub fn hsv_to_rgb(color: &ColorHSVA) -> ColorRGBA {
222 return ColorHCMA::from_hsv(color).to_rgb();
223}
224
225pub fn hsl_to_hsi(color: &ColorHSLA) -> ColorHSIA {
231 return ColorHCMA::from_hsl(color).to_hsi();
232}
233
234pub fn hsl_to_rgb(color: &ColorHSLA) -> ColorRGBA {
240 return ColorHCMA::from_hsl(color).to_rgb();
241}
242
243pub fn hsl_to_hsv(color: &ColorHSLA) -> ColorHSVA {
249 return ColorHCMA::from_hsl(color).to_hsv();
250}
251
252pub fn hsi_to_rgb(color: &ColorHSIA) -> ColorRGBA {
258 return ColorHCMA::from_hsi(color).to_rgb();
259}
260
261pub fn hsi_to_hsv(color: &ColorHSIA) -> ColorHSVA {
267 return ColorHCMA::from_hsi(color).to_hsv();
268}
269
270pub fn hsi_to_hsl(color: &ColorHSIA) -> ColorHSLA {
276 return ColorHCMA::from_hsi(color).to_hsl();
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 fn get_test_values() -> [(ColorHCMA, ColorRGBA, ColorHSVA, ColorHSLA, ColorHSIA); 19] {
285 return [
286 (
287 ColorHCMA {
288 h: 0.0,
289 c: 0.0,
290 m: 1.0,
291 a: 1.0,
292 },
293 ColorRGBA::new_rgb(1.0, 1.0, 1.0),
294 ColorHSVA::new_hsv(0.0, 0.0, 1.0),
295 ColorHSLA::new_hsl(0.0, 0.0, 1.0),
296 ColorHSIA::new_hsi(0.0, 0.0, 1.0),
297 ),
298 (
299 ColorHCMA {
300 h: 0.0,
301 c: 0.0,
302 m: 0.5,
303 a: 1.0,
304 },
305 ColorRGBA::new_rgb(0.5, 0.5, 0.5),
306 ColorHSVA::new_hsv(0.0, 0.0, 0.5),
307 ColorHSLA::new_hsl(0.0, 0.0, 0.5),
308 ColorHSIA::new_hsi(0.0, 0.0, 0.5),
309 ),
310 (
311 ColorHCMA {
312 h: 0.0,
313 c: 0.0,
314 m: 0.0,
315 a: 1.0,
316 },
317 ColorRGBA::new_rgb(0.0, 0.0, 0.0),
318 ColorHSVA::new_hsv(0.0, 0.0, 0.0),
319 ColorHSLA::new_hsl(0.0, 0.0, 0.0),
320 ColorHSIA::new_hsi(0.0, 0.0, 0.0),
321 ),
322 (
323 ColorHCMA {
324 h: 0.0,
325 c: 1.0,
326 m: 0.0,
327 a: 1.0,
328 },
329 ColorRGBA::new_rgb(1.0, 0.0, 0.0),
330 ColorHSVA::new_hsv(0.0, 1.0, 1.0),
331 ColorHSLA::new_hsl(0.0, 1.0, 0.5),
332 ColorHSIA::new_hsi(0.0, 1.0, 0.3333),
333 ),
334 (
335 ColorHCMA {
336 h: 60.0 / 360.0,
337 c: 0.75,
338 m: 0.0,
339 a: 1.0,
340 },
341 ColorRGBA::new_rgb(0.75, 0.75, 0.0),
342 ColorHSVA::new_hsv(60.0 / 360.0, 1.0, 0.75),
343 ColorHSLA::new_hsl(60.0 / 360.0, 1.0, 0.375),
344 ColorHSIA::new_hsi(60.0 / 360.0, 1.0, 0.5),
345 ),
346 (
347 ColorHCMA {
348 h: 120.0 / 360.0,
349 c: 0.5,
350 m: 0.0,
351 a: 1.0,
352 },
353 ColorRGBA::new_rgb(0.0, 0.5, 0.0),
354 ColorHSVA::new_hsv(120.0 / 360.0, 1.0, 0.5),
355 ColorHSLA::new_hsl(120.0 / 360.0, 1.0, 0.25),
356 ColorHSIA::new_hsi(120.0 / 360.0, 1.0, 0.1667),
357 ),
358 (
359 ColorHCMA {
360 h: 180.0 / 360.0,
361 c: 0.5,
362 m: 0.5,
363 a: 1.0,
364 },
365 ColorRGBA::new_rgb(0.5, 1.0, 1.0),
366 ColorHSVA::new_hsv(180.0 / 360.0, 0.5, 1.0),
367 ColorHSLA::new_hsl(180.0 / 360.0, 1.0, 0.75),
368 ColorHSIA::new_hsi(180.0 / 360.0, 0.4, 0.833),
369 ),
370 (
371 ColorHCMA {
372 h: 240.0 / 360.0,
373 c: 0.5,
374 m: 0.5,
375 a: 1.0,
376 },
377 ColorRGBA::new_rgb(0.5, 0.5, 1.0),
378 ColorHSVA::new_hsv(240.0 / 360.0, 0.5, 1.0),
379 ColorHSLA::new_hsl(240.0 / 360.0, 1.0, 0.75),
380 ColorHSIA::new_hsi(240.0 / 360.0, 0.25, 0.667),
381 ),
382 (
383 ColorHCMA {
384 h: 300.0 / 360.0,
385 c: 0.5,
386 m: 0.25,
387 a: 1.0,
388 },
389 ColorRGBA::new_rgb(0.75, 0.25, 0.75),
390 ColorHSVA::new_hsv(300.0 / 360.0, 0.667, 0.75),
391 ColorHSLA::new_hsl(300.0 / 360.0, 0.5, 0.5),
392 ColorHSIA::new_hsi(300.0 / 360.0, 0.571, 0.5834),
393 ),
394 (
395 ColorHCMA {
396 h: 61.8 / 360.0,
397 c: 0.501,
398 m: 0.142,
399 a: 1.0,
400 },
401 ColorRGBA::new_rgb(0.628, 0.643, 0.142),
402 ColorHSVA::new_hsv(61.8 / 360.0, 0.779, 0.643),
403 ColorHSLA::new_hsl(61.8 / 360.0, 0.638, 0.3924),
404 ColorHSIA::new_hsi(61.8 / 360.0, 0.699, 0.471),
405 ),
406 (
407 ColorHCMA {
408 h: 251.1 / 360.0,
409 c: 0.814,
410 m: 0.104,
411 a: 1.0,
412 },
413 ColorRGBA::new_rgb(0.255, 0.104, 0.918),
414 ColorHSVA::new_hsv(251.1 / 360.0, 0.887, 0.918),
415 ColorHSLA::new_hsl(251.1 / 360.0, 0.832, 0.511),
416 ColorHSIA::new_hsi(251.1 / 360.0, 0.7555, 0.4255),
417 ),
418 (
419 ColorHCMA {
420 h: 134.9 / 360.0,
421 c: 0.559,
422 m: 0.116,
423 a: 1.0,
424 },
425 ColorRGBA::new_rgb(0.116, 0.675, 0.255),
426 ColorHSVA::new_hsv(134.9 / 360.0, 0.828, 0.675),
427 ColorHSLA::new_hsl(134.9 / 360.0, 0.7065, 0.3955),
428 ColorHSIA::new_hsi(134.9 / 360.0, 0.667, 0.349),
429 ),
430 (
431 ColorHCMA {
432 h: 49.5 / 360.0,
433 c: 0.888,
434 m: 0.053,
435 a: 1.0,
436 },
437 ColorRGBA::new_rgb(0.9405, 0.7855, 0.053),
438 ColorHSVA::new_hsv(49.5 / 360.0, 0.944, 0.941),
439 ColorHSLA::new_hsl(49.5 / 360.0, 0.893, 0.497),
440 ColorHSIA::new_hsi(49.5 / 360.0, 0.911, 0.593),
441 ),
442 (
443 ColorHCMA {
444 h: 283.7 / 360.0,
445 c: 0.710,
446 m: 0.187,
447 a: 1.0,
448 },
449 ColorRGBA::new_rgb(0.704, 0.187, 0.897),
450 ColorHSVA::new_hsv(283.7 / 360.0, 0.792, 0.897),
451 ColorHSLA::new_hsl(283.7 / 360.0, 0.775, 0.542),
452 ColorHSIA::new_hsi(283.7 / 360.0, 0.686, 0.596),
453 ),
454 (
455 ColorHCMA {
456 h: 14.3 / 360.0,
457 c: 0.615,
458 m: 0.316,
459 a: 1.0,
460 },
461 ColorRGBA::new_rgb(0.931, 0.463, 0.316),
462 ColorHSVA::new_hsv(14.3 / 360.0, 0.661, 0.931),
463 ColorHSLA::new_hsl(14.3 / 360.0, 0.81749, 0.6239),
464 ColorHSIA::new_hsi(14.3 / 360.0, 0.4454, 0.570),
465 ),
466 (
467 ColorHCMA {
468 h: 56.9 / 360.0,
469 c: 0.466,
470 m: 0.532,
471 a: 1.0,
472 },
473 ColorRGBA::new_rgb(0.998, 0.974, 0.532),
474 ColorHSVA::new_hsv(56.9 / 360.0, 0.467, 0.998),
475 ColorHSLA::new_hsl(56.9 / 360.0, 0.991, 0.765),
476 ColorHSIA::new_hsi(56.9 / 360.0, 0.3625, 0.8345),
477 ),
478 (
479 ColorHCMA {
480 h: 162.4 / 360.0,
481 c: 0.696,
482 m: 0.099,
483 a: 1.0,
484 },
485 ColorRGBA::new_rgb(0.099, 0.795, 0.591),
486 ColorHSVA::new_hsv(162.4 / 360.0, 0.875, 0.795),
487 ColorHSLA::new_hsl(162.4 / 360.0, 0.779, 0.447),
488 ColorHSIA::new_hsi(162.4 / 360.0, 0.800, 0.495),
489 ),
490 (
491 ColorHCMA {
492 h: 248.3 / 360.0,
493 c: 0.448,
494 m: 0.149,
495 a: 1.0,
496 },
497 ColorRGBA::new_rgb(0.211, 0.149, 0.597),
498 ColorHSVA::new_hsv(248.3 / 360.0, 0.750, 0.597),
499 ColorHSLA::new_hsl(248.3 / 360.0, 0.601, 0.373),
500 ColorHSIA::new_hsi(248.3 / 360.0, 0.533, 0.319),
501 ),
502 (
503 ColorHCMA {
504 h: 240.5 / 360.0,
505 c: 0.228,
506 m: 0.493,
507 a: 1.0,
508 },
509 ColorRGBA::new_rgb(0.495, 0.493, 0.721),
510 ColorHSVA::new_hsv(240.5 / 360.0, 0.316, 0.721),
511 ColorHSLA::new_hsl(240.5 / 360.0, 0.290, 0.607),
512 ColorHSIA::new_hsi(240.5 / 360.0, 0.1345, 0.5695),
513 ),
514 ];
515 }
516
517 fn round_hcm(color: &ColorHCMA) -> [i32; 4] {
519 return [
520 (color.h * 1000.0).round() as i32,
521 (color.c * 1000.0).round() as i32,
522 (color.m * 1000.0).round() as i32,
523 (color.a * 1000.0).round() as i32,
524 ];
525 }
526
527 fn round_rgb(color: &ColorRGBA) -> [i32; 4] {
529 return [
530 (color.get_red() * 1000.0).round() as i32,
531 (color.get_green() * 1000.0).round() as i32,
532 (color.get_blue() * 1000.0).round() as i32,
533 (color.get_alpha() * 1000.0).round() as i32,
534 ];
535 }
536
537 fn round_hsv(color: &ColorHSVA) -> [i32; 4] {
539 return [
540 (color.get_hue() * 1000.0).round() as i32,
541 (color.get_saturation() * 1000.0).round() as i32,
542 (color.get_value() * 1000.0).round() as i32,
543 (color.get_alpha() * 1000.0).round() as i32,
544 ];
545 }
546
547 fn round_hsl(color: &ColorHSLA) -> [i32; 4] {
549 return [
550 (color.get_hue() * 1000.0).round() as i32,
551 (color.get_saturation() * 1000.0).round() as i32,
552 (color.get_lightness() * 1000.0).round() as i32,
553 (color.get_alpha() * 1000.0).round() as i32,
554 ];
555 }
556
557 fn round_hsi(color: &ColorHSIA) -> [i32; 4] {
559 return [
560 (color.get_hue() * 1000.0).round() as i32,
561 (color.get_saturation() * 1000.0).round() as i32,
562 (color.get_intensity() * 1000.0).round() as i32,
563 (color.get_alpha() * 1000.0).round() as i32,
564 ];
565 }
566
567 mod conversion {
568 use super::*;
569
570 #[test]
571 fn from_rgb() {
572 let test_values = get_test_values();
573
574 for values in test_values.iter() {
575 let rgb = &values.1;
576 let hcm = ColorHCMA::from_rgb(rgb);
577
578 assert_eq!(round_hcm(&values.0), round_hcm(&hcm));
579 }
580 }
581
582 #[test]
583 fn from_hsv() {
584 let test_values = get_test_values();
585
586 for values in test_values.iter() {
587 let hsv = &values.2;
588 let hcm = ColorHCMA::from_hsv(hsv);
589
590 assert_eq!(round_hcm(&values.0), round_hcm(&hcm));
591 }
592 }
593
594 #[test]
595 fn from_hsl() {
596 let test_values = get_test_values();
597
598 for values in test_values.iter() {
599 let hsl = &values.3;
600 let hcm = ColorHCMA::from_hsl(hsl);
601
602 assert_eq!(round_hcm(&values.0), round_hcm(&hcm));
603 }
604 }
605
606 #[test]
607 fn from_hsi() {
608 let test_values = get_test_values();
609
610 for values in test_values.iter() {
611 let hsi = &values.4;
612 let hcm = ColorHCMA::from_hsi(hsi);
613
614 assert_eq!(round_hcm(&values.0), round_hcm(&hcm));
615 }
616 }
617
618 #[test]
619 fn to_rgb() {
620 let test_values = get_test_values();
621
622 for values in test_values.iter() {
623 let hcm = &values.0;
624 let rgb = hcm.to_rgb();
625
626 assert_eq!(round_rgb(&values.1), round_rgb(&rgb));
627 }
628 }
629
630 #[test]
631 fn to_hsv() {
632 let test_values = get_test_values();
633
634 for values in test_values.iter() {
635 let hcm = &values.0;
636 let hsv = hcm.to_hsv();
637
638 assert_eq!(round_hsv(&values.2), round_hsv(&hsv));
639 }
640 }
641
642 #[test]
643 fn to_hsl() {
644 let test_values = get_test_values();
645
646 for values in test_values.iter() {
647 let hcm = &values.0;
648 let hsl = hcm.to_hsl();
649
650 assert_eq!(round_hsl(&values.3), round_hsl(&hsl));
651 }
652 }
653
654 #[test]
655 fn to_hsi() {
656 let test_values = get_test_values();
657
658 for values in test_values.iter() {
659 let hcm = &values.0;
660 let hsi = hcm.to_hsi();
661
662 assert_eq!(round_hsi(&values.4), round_hsi(&hsi));
663 }
664 }
665 }
666}