color_lib/
utils.rs

1//! 
2//! This module contains utility functions for use in color conversion.
3//! Currently it contains functions to convert between any base color
4//! representation used in this library. Other utility functions may be added
5//! later.
6//! 
7
8use crate::{ColorHSIA, ColorHSLA, ColorHSVA, ColorRGBA};
9
10/// A color in the hue, croma, minimum, alpha space, used to convert between HSx and RGB colors
11#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
12struct ColorHCMA {
13    /// The hue component of the color
14    h: f32,
15    /// The croma component of the color
16    c: f32,
17    /// The minimum value of the color
18    m: f32,
19    /// The alpha component of the color
20    a: f32,
21}
22
23impl ColorHCMA {
24    /// Converts from RGB
25    fn from_rgb(color: &ColorRGBA) -> Self {
26        // Get colors
27        let colors = [color.get_red(), color.get_green(), color.get_blue()];
28
29        // Get the location for the smallest and largest component
30        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        // Get (hue' // 2)
45        let hue_major = (i_min + 1) % 3;
46
47        // Get ((hue' % 2) // 1)
48        let hue_minor = ((i_max + 3) - i_min) % 3 - 1;
49
50        // Get the minimum value, croma and x = c * (1 - (hue' % 2 - 1).abs())
51        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        // Stop if it is grayscale
56        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        // Get the hue
66        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    /// Converts from HSV
77    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    /// Converts from HSL
90    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    /// Converts from HSI
103    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    /// Converts to RGB
117    fn to_rgb(&self) -> ColorRGBA {
118        // Calculate temporary parameters for use in the calculations
119        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        // Finds the order of the colors
124        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        // Finds the negative index of the red component
131        let i = hp.div_euclid(2.0) as usize;
132
133        // Calculate the final colors
134        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    /// Converts to HSV
145    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    /// Converts to HSL
153    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    /// Converts to HSI
162    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
171/// Converts a RGB color to HSV representation
172/// 
173/// # Parameters
174/// 
175/// color: The RGB color to convert
176pub fn rgb_to_hsv(color: &ColorRGBA) -> ColorHSVA {
177    return ColorHCMA::from_rgb(color).to_hsv();
178}
179
180/// Converts a RGB color to HSL representation
181/// 
182/// # Parameters
183/// 
184/// color: The RGB color to convert
185pub fn rgb_to_hsl(color: &ColorRGBA) -> ColorHSLA {
186    return ColorHCMA::from_rgb(color).to_hsl();
187}
188
189/// Converts a RGB color to HSI representation
190/// 
191/// # Parameters
192/// 
193/// color: The RGB color to convert
194pub fn rgb_to_hsi(color: &ColorRGBA) -> ColorHSIA {
195    return ColorHCMA::from_rgb(color).to_hsi();
196}
197
198/// Converts a HSV color to HSL representation
199/// 
200/// # Parameters
201/// 
202/// color: The HSV color to convert
203pub fn hsv_to_hsl(color: &ColorHSVA) -> ColorHSLA {
204    return ColorHCMA::from_hsv(color).to_hsl();
205}
206
207/// Converts a HSV color to HSI representation
208/// 
209/// # Parameters
210/// 
211/// color: The HSV color to convert
212pub fn hsv_to_hsi(color: &ColorHSVA) -> ColorHSIA {
213    return ColorHCMA::from_hsv(color).to_hsi();
214}
215
216/// Converts a HSV color to RGB representation
217/// 
218/// # Parameters
219/// 
220/// color: The HSV color to convert
221pub fn hsv_to_rgb(color: &ColorHSVA) -> ColorRGBA {
222    return ColorHCMA::from_hsv(color).to_rgb();
223}
224
225/// Converts a HSL color to HSI representation
226/// 
227/// # Parameters
228/// 
229/// color: The HSL color to convert
230pub fn hsl_to_hsi(color: &ColorHSLA) -> ColorHSIA {
231    return ColorHCMA::from_hsl(color).to_hsi();
232}
233
234/// Converts a HSL color to RGB representation
235/// 
236/// # Parameters
237/// 
238/// color: The HSL color to convert
239pub fn hsl_to_rgb(color: &ColorHSLA) -> ColorRGBA {
240    return ColorHCMA::from_hsl(color).to_rgb();
241}
242
243/// Converts a HSL color to HSV representation
244/// 
245/// # Parameters
246/// 
247/// color: The HSL color to convert
248pub fn hsl_to_hsv(color: &ColorHSLA) -> ColorHSVA {
249    return ColorHCMA::from_hsl(color).to_hsv();
250}
251
252/// Converts a HSI color to RGB representation
253/// 
254/// # Parameters
255/// 
256/// color: The HSI color to convert
257pub fn hsi_to_rgb(color: &ColorHSIA) -> ColorRGBA {
258    return ColorHCMA::from_hsi(color).to_rgb();
259}
260
261/// Converts a HSI color to HSV representation
262/// 
263/// # Parameters
264/// 
265/// color: The HSI color to convert
266pub fn hsi_to_hsv(color: &ColorHSIA) -> ColorHSVA {
267    return ColorHCMA::from_hsi(color).to_hsv();
268}
269
270/// Converts a HSI color to HSL representation
271/// 
272/// # Parameters
273/// 
274/// color: The HSI color to convert
275pub 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    /// Retrieves all test colors
284    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    /// Rounds the HCM color for comparisons
518    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    /// Rounds the RGB color for comparisons
528    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    /// Rounds the HSV color for comparisons
538    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    /// Rounds the HSL color for comparisons
548    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    /// Rounds the HSI color for comparisons
558    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}