1use std::format;
2
3use crate::{Hue, Saturation, Value, Hsv};
4
5#[derive(Copy, Clone, Debug)]
8pub struct Color {
9 red: f32,
10 green: f32,
11 blue: f32
12}
13
14impl Color {
15 pub fn to_array(&self) -> [f32; 3] {
17 [self.red, self.green, self.blue]
18 }
19
20 pub fn to_tuple(&self) -> (f32, f32, f32) {
22 (self.red, self.green, self.blue)
23 }
24
25 pub fn to_rgba_array(&self) -> [f32; 4] {
27 [self.red, self.green, self.blue, 1.0]
28 }
29
30 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 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 { 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 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}