colourado_iter/
color.rs

1use std::format;
2
3use crate::{Hue, Saturation, Value, Hsv};
4
5/// A simple struct containing the three main color components of RGB color space.
6/// Colors are stored as f32 values ranging from 0.0 to 1.0 
7#[derive(Copy, Clone, Debug)]
8pub struct Color {
9    red: f32,
10    green: f32,
11    blue: f32
12}
13
14impl Color {
15    /// Convert to an array of 3 floats
16    pub fn to_array(&self) -> [f32; 3] {
17        [self.red, self.green, self.blue]
18    }
19
20    /// Convert to a tuple of 3 floats
21    pub fn to_tuple(&self) -> (f32, f32, f32) {
22        (self.red, self.green, self.blue)
23    }
24
25    /// Convert to an array for rgba (meaning it will just append 1.0 as the alpha value)
26    pub fn to_rgba_array(&self) -> [f32; 4] {
27        [self.red, self.green, self.blue, 1.0]
28    }
29
30    /// Convert HSV to RGB. Plain and simple
31    pub fn hsv_to_rgb(hue: Hue, saturation: Saturation, value: Value) -> Self {
32        let chroma = value * saturation;
33        let hue2 = hue / 60.0;
34        let tmp = chroma * (1.0 - ((hue2 % 2.0) - 1.0).abs());
35
36        let color2 = match hue2 {
37            h if (0.0..1.0).contains(&h) => (chroma, tmp, 0.0),
38            h if (1.0..2.0).contains(&h) => (tmp, chroma, 0.0),
39            h if (2.0..3.0).contains(&h) => (0.0, chroma, tmp),
40            h if (3.0..4.0).contains(&h) => (0.0, tmp, chroma),
41            h if (4.0..5.0).contains(&h) => (tmp, 0.0, chroma),
42            h if (5.0..6.0).contains(&h) => (chroma, 0.0, tmp),
43            _ => (0.0, 0.0, 0.0)
44        };
45
46        let m = value - chroma;
47        let red = color2.0 + m;
48        let green = color2.1 + m;
49        let blue = color2.2 + m;
50
51        Color {
52            red, 
53            green, 
54            blue
55        }
56    }
57
58    /// Convert RGB to HSV
59    pub fn to_hsv(&self) -> Hsv {
60        let (r, g, b) = self.to_tuple();
61
62        let mut cmax = r;
63        let mut cmin = r;
64        if g > cmax { // f32 does not implement Ord so if tree it is
65            cmax = g;
66        } else if g < cmin {
67            cmin = g;
68        }
69        if b > cmax {
70            cmax = b;
71        } else if b < cmin {
72            cmin = b;
73        }
74        let delta = cmax - cmin;
75
76
77
78        let hue = if cmax == r {
79            60.0 * (((g - b) / delta) % 6.0)
80        } else if cmax == g {
81            60.0 * (((b - r) / delta) + 2.0)
82        } else {
83            60.0 * (((r - g) / delta) + 4.0)
84        };
85
86        let saturation = if cmax == 0.0 {
87            0.0
88        } else {
89            delta / cmax
90        };
91        (hue, saturation, cmax)
92    }
93
94    /// Convert the color to a hex string
95    pub fn to_hex(&self) -> String {
96        format!("#{:02X}{:02X}{:02X}", (self.red * 255.0).round() as u32, (self.green * 255.0).round() as u32, (self.blue * 255.0).round() as u32)
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::Color;
103    use float_cmp::assert_approx_eq;
104
105    #[test]
106    fn test_convert_hsv_rgb() {
107        let colors = [
108            (20.85, 0.51, 0.7051166), 
109            (130.67574, 0.85, 0.51), 
110            (7.302415, 0.85, 0.7659915), 
111            (0.43018022, 0.11269033, 0.85)
112        ];
113
114        for (hue, saturation, value) in colors {
115            let color_obj = Color::hsv_to_rgb(hue, saturation, value);
116            let (hue2, saturation2, value2) = color_obj.to_hsv();
117            assert_approx_eq!(f32, hue, hue2, epsilon = 0.00003);
118            assert_approx_eq!(f32, saturation, saturation2, epsilon = 0.00003);
119            assert_approx_eq!(f32, value, value2, epsilon = 0.00003);
120        }
121    }
122
123    #[test]
124    fn test_convert_hex() {
125        let mapping = [
126            ((0.0, 0.0, 1.0), "#FFFFFF"),
127            ((0.0, 0.0, 0.0), "#000000"),
128            ((0.0, 1.0, 1.0), "#FF0000"),
129            ((0.482 * 360.0, 0.714, 0.878), "#40E0CF"),
130            ((0.051 * 360.0, 0.718, 0.627), "#A0502D"),
131        ];
132
133        for ((hue, saturation, value), hex) in mapping {
134            let color_obj = Color::hsv_to_rgb(hue, saturation, value);
135            let hex2 = color_obj.to_hex();
136            assert_eq!(hex, &hex2);
137        }
138    }
139}