1use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
13pub struct Color {
14 pub r: u8,
15 pub g: u8,
16 pub b: u8,
17}
18
19impl Color {
20 pub fn new(r: u8, g: u8, b: u8) -> Self {
22 Self { r, g, b }
23 }
24
25 pub fn from_hsv(h: f64, s: f64, v: f64) -> Self {
32 let c = v * s;
33 let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
34 let m = v - c;
35
36 let (r, g, b) = if h < 60.0 {
37 (c, x, 0.0)
38 } else if h < 120.0 {
39 (x, c, 0.0)
40 } else if h < 180.0 {
41 (0.0, c, x)
42 } else if h < 240.0 {
43 (0.0, x, c)
44 } else if h < 300.0 {
45 (x, 0.0, c)
46 } else {
47 (c, 0.0, x)
48 };
49
50 Self {
51 r: ((r + m) * 255.0) as u8,
52 g: ((g + m) * 255.0) as u8,
53 b: ((b + m) * 255.0) as u8,
54 }
55 }
56
57 pub fn black() -> Self {
59 Self::new(0, 0, 0)
60 }
61
62 pub fn white() -> Self {
64 Self::new(255, 255, 255)
65 }
66
67 pub fn from_hex(hex: &str) -> crate::error::Result<Self> {
90 let hex = hex.trim().trim_start_matches('#');
91
92 match hex.len() {
93 3 => {
94 let r = u8::from_str_radix(&hex[0..1].repeat(2), 16)
96 .map_err(|_| crate::error::ColorMapError::InvalidHexColor(hex.to_string()))?;
97 let g = u8::from_str_radix(&hex[1..2].repeat(2), 16)
98 .map_err(|_| crate::error::ColorMapError::InvalidHexColor(hex.to_string()))?;
99 let b = u8::from_str_radix(&hex[2..3].repeat(2), 16)
100 .map_err(|_| crate::error::ColorMapError::InvalidHexColor(hex.to_string()))?;
101 Ok(Self::new(r, g, b))
102 }
103 6 => {
104 let r = u8::from_str_radix(&hex[0..2], 16)
106 .map_err(|_| crate::error::ColorMapError::InvalidHexColor(hex.to_string()))?;
107 let g = u8::from_str_radix(&hex[2..4], 16)
108 .map_err(|_| crate::error::ColorMapError::InvalidHexColor(hex.to_string()))?;
109 let b = u8::from_str_radix(&hex[4..6], 16)
110 .map_err(|_| crate::error::ColorMapError::InvalidHexColor(hex.to_string()))?;
111 Ok(Self::new(r, g, b))
112 }
113 _ => Err(crate::error::ColorMapError::InvalidHexColor(hex.to_string())),
114 }
115 }
116
117 pub fn to_hex(&self) -> String {
127 format!("#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
128 }
129
130 pub fn lerp(&self, other: &Color, t: f64) -> Color {
136 let t = t.clamp(0.0, 1.0);
137 Color {
138 r: (self.r as f64 + (other.r as f64 - self.r as f64) * t) as u8,
139 g: (self.g as f64 + (other.g as f64 - self.g as f64) * t) as u8,
140 b: (self.b as f64 + (other.b as f64 - self.b as f64) * t) as u8,
141 }
142 }
143}
144
145impl std::fmt::Display for Color {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 write!(f, "RGB({},{},{})", self.r, self.g, self.b)
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_color_creation() {
157 let color = Color::new(255, 128, 64);
158 assert_eq!(color.r, 255);
159 assert_eq!(color.g, 128);
160 assert_eq!(color.b, 64);
161 }
162
163 #[test]
164 fn test_color_constants() {
165 let black = Color::black();
166 assert_eq!(black.r, 0);
167 assert_eq!(black.g, 0);
168 assert_eq!(black.b, 0);
169
170 let white = Color::white();
171 assert_eq!(white.r, 255);
172 assert_eq!(white.g, 255);
173 assert_eq!(white.b, 255);
174 }
175
176 #[test]
177 fn test_lerp() {
178 let red = Color::new(255, 0, 0);
179 let blue = Color::new(0, 0, 255);
180
181 let mid = red.lerp(&blue, 0.5);
182 assert_eq!(mid.r, 127);
183 assert_eq!(mid.g, 0);
184 assert_eq!(mid.b, 127);
185
186 let at_red = red.lerp(&blue, 0.0);
187 assert_eq!(at_red.r, 255);
188
189 let at_blue = red.lerp(&blue, 1.0);
190 assert_eq!(at_blue.b, 255);
191 }
192
193 #[test]
194 fn test_hsv_conversion() {
195 let red = Color::from_hsv(0.0, 1.0, 1.0);
197 assert_eq!(red.r, 255);
198 assert_eq!(red.g, 0);
199 assert_eq!(red.b, 0);
200
201 let green = Color::from_hsv(120.0, 1.0, 1.0);
203 assert_eq!(green.r, 0);
204 assert_eq!(green.g, 255);
205 assert_eq!(green.b, 0);
206
207 let blue = Color::from_hsv(240.0, 1.0, 1.0);
209 assert_eq!(blue.r, 0);
210 assert_eq!(blue.g, 0);
211 assert_eq!(blue.b, 255);
212 }
213
214 #[test]
215 fn test_from_hex() {
216 let color1 = Color::from_hex("#FF5733").unwrap();
218 assert_eq!(color1.r, 255);
219 assert_eq!(color1.g, 87);
220 assert_eq!(color1.b, 51);
221
222 let color2 = Color::from_hex("00FF00").unwrap();
224 assert_eq!(color2.r, 0);
225 assert_eq!(color2.g, 255);
226 assert_eq!(color2.b, 0);
227
228 let color3 = Color::from_hex("#F0A").unwrap();
230 assert_eq!(color3.r, 255);
231 assert_eq!(color3.g, 0);
232 assert_eq!(color3.b, 170);
233
234 let color4 = Color::from_hex("C8F").unwrap();
236 assert_eq!(color4.r, 204);
237 assert_eq!(color4.g, 136);
238 assert_eq!(color4.b, 255);
239
240 let color5 = Color::from_hex(" #ABCDEF ").unwrap();
242 assert_eq!(color5.r, 171);
243 assert_eq!(color5.g, 205);
244 assert_eq!(color5.b, 239);
245
246 assert!(Color::from_hex("#GGGGGG").is_err());
248 assert!(Color::from_hex("#12345").is_err());
249 assert!(Color::from_hex("").is_err());
250 }
251
252 #[test]
253 fn test_to_hex() {
254 let color1 = Color::new(255, 87, 51);
255 assert_eq!(color1.to_hex(), "#FF5733");
256
257 let color2 = Color::new(0, 255, 0);
258 assert_eq!(color2.to_hex(), "#00FF00");
259
260 let color3 = Color::new(255, 0, 170);
261 assert_eq!(color3.to_hex(), "#FF00AA");
262 }
263
264 #[test]
265 fn test_hex_roundtrip() {
266 let original = Color::new(123, 45, 67);
267 let hex = original.to_hex();
268 let parsed = Color::from_hex(&hex).unwrap();
269 assert_eq!(original, parsed);
270 }
271}