1#[derive(Debug, Clone, PartialEq)]
8pub enum Color {
9 Rgb(u8, u8, u8),
11 Rgba(u8, u8, u8, u8),
13 RgbFloat(f32, f32, f32),
15 RgbaFloat(f32, f32, f32, f32),
17 Hex(String),
19}
20
21impl Color {
22 #[inline]
28 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
29 Self::Rgb(r, g, b)
30 }
31
32 #[inline]
34 pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
35 Self::Rgba(r, g, b, a)
36 }
37
38 #[inline]
40 pub const fn rgb_float(r: f32, g: f32, b: f32) -> Self {
41 Self::RgbFloat(r, g, b)
42 }
43
44 #[inline]
46 pub const fn rgba_float(r: f32, g: f32, b: f32, a: f32) -> Self {
47 Self::RgbaFloat(r, g, b, a)
48 }
49
50 #[inline]
52 pub fn hex(s: impl Into<String>) -> Self {
53 Self::Hex(s.into())
54 }
55
56 pub fn from_hex(s: &str) -> Option<Self> {
58 let s = s.strip_prefix('#').unwrap_or(s);
59 match s.len() {
60 3 => {
62 let r = u8::from_str_radix(&s[0..1], 16).ok()?;
63 let g = u8::from_str_radix(&s[1..2], 16).ok()?;
64 let b = u8::from_str_radix(&s[2..3], 16).ok()?;
65 Some(Self::Rgb(r * 17, g * 17, b * 17))
66 }
67 4 => {
69 let r = u8::from_str_radix(&s[0..1], 16).ok()?;
70 let g = u8::from_str_radix(&s[1..2], 16).ok()?;
71 let b = u8::from_str_radix(&s[2..3], 16).ok()?;
72 let a = u8::from_str_radix(&s[3..4], 16).ok()?;
73 Some(Self::Rgba(r * 17, g * 17, b * 17, a * 17))
74 }
75 6 => {
77 let r = u8::from_str_radix(&s[0..2], 16).ok()?;
78 let g = u8::from_str_radix(&s[2..4], 16).ok()?;
79 let b = u8::from_str_radix(&s[4..6], 16).ok()?;
80 Some(Self::Rgb(r, g, b))
81 }
82 8 => {
84 let r = u8::from_str_radix(&s[0..2], 16).ok()?;
85 let g = u8::from_str_radix(&s[2..4], 16).ok()?;
86 let b = u8::from_str_radix(&s[4..6], 16).ok()?;
87 let a = u8::from_str_radix(&s[6..8], 16).ok()?;
88 Some(Self::Rgba(r, g, b, a))
89 }
90 _ => None,
91 }
92 }
93
94 pub fn as_rgb(&self) -> (u8, u8, u8) {
100 match self {
101 Self::Rgb(r, g, b) => (*r, *g, *b),
102 Self::Rgba(r, g, b, _) => (*r, *g, *b),
103 Self::RgbFloat(r, g, b) => (
104 (r.clamp(0.0, 1.0) * 255.0) as u8,
105 (g.clamp(0.0, 1.0) * 255.0) as u8,
106 (b.clamp(0.0, 1.0) * 255.0) as u8,
107 ),
108 Self::RgbaFloat(r, g, b, _) => (
109 (r.clamp(0.0, 1.0) * 255.0) as u8,
110 (g.clamp(0.0, 1.0) * 255.0) as u8,
111 (b.clamp(0.0, 1.0) * 255.0) as u8,
112 ),
113 Self::Hex(s) => Self::from_hex(s).map(|c| c.as_rgb()).unwrap_or((0, 0, 0)),
114 }
115 }
116
117 pub fn as_rgba(&self) -> (u8, u8, u8, u8) {
119 match self {
120 Self::Rgb(r, g, b) => (*r, *g, *b, 255),
121 Self::Rgba(r, g, b, a) => (*r, *g, *b, *a),
122 Self::RgbFloat(r, g, b) => (
123 (r.clamp(0.0, 1.0) * 255.0) as u8,
124 (g.clamp(0.0, 1.0) * 255.0) as u8,
125 (b.clamp(0.0, 1.0) * 255.0) as u8,
126 255,
127 ),
128 Self::RgbaFloat(r, g, b, a) => (
129 (r.clamp(0.0, 1.0) * 255.0) as u8,
130 (g.clamp(0.0, 1.0) * 255.0) as u8,
131 (b.clamp(0.0, 1.0) * 255.0) as u8,
132 (a.clamp(0.0, 1.0) * 255.0) as u8,
133 ),
134 Self::Hex(s) => Self::from_hex(s)
135 .map(|c| c.as_rgba())
136 .unwrap_or((0, 0, 0, 255)),
137 }
138 }
139
140 pub fn as_rgb_float(&self) -> [f32; 3] {
142 match self {
143 Self::Rgb(r, g, b) => [*r as f32 / 255.0, *g as f32 / 255.0, *b as f32 / 255.0],
144 Self::Rgba(r, g, b, _) => [*r as f32 / 255.0, *g as f32 / 255.0, *b as f32 / 255.0],
145 Self::RgbFloat(r, g, b) => [*r, *g, *b],
146 Self::RgbaFloat(r, g, b, _) => [*r, *g, *b],
147 Self::Hex(s) => Self::from_hex(s)
148 .map(|c| c.as_rgb_float())
149 .unwrap_or([0.0, 0.0, 0.0]),
150 }
151 }
152
153 pub fn as_rgba_float(&self) -> [f32; 4] {
155 match self {
156 Self::Rgb(r, g, b) => [*r as f32 / 255.0, *g as f32 / 255.0, *b as f32 / 255.0, 1.0],
157 Self::Rgba(r, g, b, a) => [
158 *r as f32 / 255.0,
159 *g as f32 / 255.0,
160 *b as f32 / 255.0,
161 *a as f32 / 255.0,
162 ],
163 Self::RgbFloat(r, g, b) => [*r, *g, *b, 1.0],
164 Self::RgbaFloat(r, g, b, a) => [*r, *g, *b, *a],
165 Self::Hex(s) => Self::from_hex(s)
166 .map(|c| c.as_rgba_float())
167 .unwrap_or([0.0, 0.0, 0.0, 1.0]),
168 }
169 }
170
171 pub fn as_hex(&self) -> String {
173 match self {
174 Self::Hex(s) => {
175 let s = s.strip_prefix('#').unwrap_or(s);
176 format!("#{s}")
177 }
178 _ => {
179 let (r, g, b, a) = self.as_rgba();
180 if a == 255 {
181 format!("#{r:02x}{g:02x}{b:02x}")
182 } else {
183 format!("#{r:02x}{g:02x}{b:02x}{a:02x}")
184 }
185 }
186 }
187 }
188
189 pub fn alpha(&self) -> f32 {
191 match self {
192 Self::Rgb(_, _, _) | Self::RgbFloat(_, _, _) => 1.0,
193 Self::Rgba(_, _, _, a) => *a as f32 / 255.0,
194 Self::RgbaFloat(_, _, _, a) => *a,
195 Self::Hex(s) => Self::from_hex(s).map(|c| c.alpha()).unwrap_or(1.0),
196 }
197 }
198
199 pub fn with_alpha(&self, alpha: f32) -> Self {
205 let [r, g, b, _] = self.as_rgba_float();
206 Self::RgbaFloat(r, g, b, alpha.clamp(0.0, 1.0))
207 }
208
209 pub fn lighten(&self, amount: f32) -> Self {
211 let [r, g, b, a] = self.as_rgba_float();
212 Self::RgbaFloat(
213 (r + amount).min(1.0),
214 (g + amount).min(1.0),
215 (b + amount).min(1.0),
216 a,
217 )
218 }
219
220 pub fn darken(&self, amount: f32) -> Self {
222 let [r, g, b, a] = self.as_rgba_float();
223 Self::RgbaFloat(
224 (r - amount).max(0.0),
225 (g - amount).max(0.0),
226 (b - amount).max(0.0),
227 a,
228 )
229 }
230
231 pub fn mix(&self, other: &Self, t: f32) -> Self {
233 let [r1, g1, b1, a1] = self.as_rgba_float();
234 let [r2, g2, b2, a2] = other.as_rgba_float();
235 let t = t.clamp(0.0, 1.0);
236 Self::RgbaFloat(
237 r1 + (r2 - r1) * t,
238 g1 + (g2 - g1) * t,
239 b1 + (b2 - b1) * t,
240 a1 + (a2 - a1) * t,
241 )
242 }
243
244 pub fn invert(&self) -> Self {
246 let [r, g, b, a] = self.as_rgba_float();
247 Self::RgbaFloat(1.0 - r, 1.0 - g, 1.0 - b, a)
248 }
249
250 pub fn grayscale(&self) -> Self {
252 let [r, g, b, a] = self.as_rgba_float();
253 let lum = 0.299 * r + 0.587 * g + 0.114 * b;
254 Self::RgbaFloat(lum, lum, lum, a)
255 }
256}
257
258impl From<(u8, u8, u8)> for Color {
263 fn from((r, g, b): (u8, u8, u8)) -> Self {
264 Self::Rgb(r, g, b)
265 }
266}
267
268impl From<(u8, u8, u8, u8)> for Color {
269 fn from((r, g, b, a): (u8, u8, u8, u8)) -> Self {
270 Self::Rgba(r, g, b, a)
271 }
272}
273
274impl From<[u8; 3]> for Color {
275 fn from([r, g, b]: [u8; 3]) -> Self {
276 Self::Rgb(r, g, b)
277 }
278}
279
280impl From<[u8; 4]> for Color {
281 fn from([r, g, b, a]: [u8; 4]) -> Self {
282 Self::Rgba(r, g, b, a)
283 }
284}
285
286impl From<[f32; 3]> for Color {
287 fn from([r, g, b]: [f32; 3]) -> Self {
288 Self::RgbFloat(r, g, b)
289 }
290}
291
292impl From<[f32; 4]> for Color {
293 fn from([r, g, b, a]: [f32; 4]) -> Self {
294 Self::RgbaFloat(r, g, b, a)
295 }
296}
297
298impl From<&str> for Color {
299 fn from(s: &str) -> Self {
300 Self::from_hex(s).unwrap_or(Self::Hex(s.to_string()))
301 }
302}
303
304impl Default for Color {
309 fn default() -> Self {
310 Self::Rgb(0, 0, 0)
311 }
312}
313
314#[cfg(test)]
319mod tests {
320 use super::*;
321
322 #[test]
323 fn test_rgb_constructor() {
324 let c = Color::rgb(255, 128, 0);
325 assert_eq!(c.as_rgb(), (255, 128, 0));
326 }
327
328 #[test]
329 fn test_rgba_constructor() {
330 let c = Color::rgba(255, 128, 0, 128);
331 assert_eq!(c.as_rgba(), (255, 128, 0, 128));
332 }
333
334 #[test]
335 fn test_rgb_to_rgba() {
336 let c = Color::rgb(255, 128, 0);
337 assert_eq!(c.as_rgba(), (255, 128, 0, 255));
338 }
339
340 #[test]
341 fn test_float_to_bytes() {
342 let c = Color::rgba_float(1.0, 0.5, 0.0, 1.0);
343 let (r, g, b, a) = c.as_rgba();
344 assert_eq!(r, 255);
345 assert!((g as i32 - 127).abs() <= 1); assert_eq!(b, 0);
347 assert_eq!(a, 255);
348 }
349
350 #[test]
351 fn test_bytes_to_float() {
352 let c = Color::rgb(255, 0, 128);
353 let [r, g, b] = c.as_rgb_float();
354 assert!((r - 1.0).abs() < 0.01);
355 assert!((g - 0.0).abs() < 0.01);
356 assert!((b - 0.502).abs() < 0.01);
357 }
358
359 #[test]
360 fn test_hex_parsing_6_digit() {
361 let c = Color::from_hex("#ff8000").unwrap();
362 assert_eq!(c.as_rgb(), (255, 128, 0));
363 }
364
365 #[test]
366 fn test_hex_parsing_8_digit() {
367 let c = Color::from_hex("#ff800080").unwrap();
368 assert_eq!(c.as_rgba(), (255, 128, 0, 128));
369 }
370
371 #[test]
372 fn test_hex_parsing_3_digit() {
373 let c = Color::from_hex("#f80").unwrap();
374 assert_eq!(c.as_rgb(), (255, 136, 0));
375 }
376
377 #[test]
378 fn test_hex_parsing_without_hash() {
379 let c = Color::from_hex("ff8000").unwrap();
380 assert_eq!(c.as_rgb(), (255, 128, 0));
381 }
382
383 #[test]
384 fn test_as_hex() {
385 let c = Color::rgb(255, 128, 0);
386 assert_eq!(c.as_hex(), "#ff8000");
387 }
388
389 #[test]
390 fn test_as_hex_with_alpha() {
391 let c = Color::rgba(255, 128, 0, 128);
392 assert_eq!(c.as_hex(), "#ff800080");
393 }
394
395 #[test]
396 fn test_with_alpha() {
397 let c = Color::rgb(255, 128, 0).with_alpha(0.5);
398 assert!((c.alpha() - 0.5).abs() < 0.01);
399 }
400
401 #[test]
402 fn test_lighten() {
403 let c = Color::rgb(128, 128, 128).lighten(0.1);
404 let [r, g, b, _] = c.as_rgba_float();
405 assert!(r > 0.5);
406 assert!(g > 0.5);
407 assert!(b > 0.5);
408 }
409
410 #[test]
411 fn test_darken() {
412 let c = Color::rgb(128, 128, 128).darken(0.1);
413 let [r, g, b, _] = c.as_rgba_float();
414 assert!(r < 0.5);
415 assert!(g < 0.5);
416 assert!(b < 0.5);
417 }
418
419 #[test]
420 fn test_mix() {
421 let white = Color::rgb(255, 255, 255);
422 let black = Color::rgb(0, 0, 0);
423 let gray = white.mix(&black, 0.5);
424 let (r, g, b, _) = gray.as_rgba();
425 assert!((r as i32 - 127).abs() <= 1);
426 assert!((g as i32 - 127).abs() <= 1);
427 assert!((b as i32 - 127).abs() <= 1);
428 }
429
430 #[test]
431 fn test_invert() {
432 let c = Color::rgb(255, 0, 128).invert();
433 let (r, g, b, _) = c.as_rgba();
434 assert_eq!(r, 0);
435 assert_eq!(g, 255);
436 assert!((b as i32 - 127).abs() <= 1);
437 }
438
439 #[test]
440 fn test_grayscale() {
441 let c = Color::rgb(255, 0, 0).grayscale();
442 let [r, g, b, _] = c.as_rgba_float();
443 assert!((r - g).abs() < 0.01);
444 assert!((g - b).abs() < 0.01);
445 }
446
447 #[test]
448 fn test_from_tuple() {
449 let c: Color = (255, 128, 0).into();
450 assert_eq!(c.as_rgb(), (255, 128, 0));
451 }
452
453 #[test]
454 fn test_from_array() {
455 let c: Color = [0.5f32, 0.5, 0.5, 1.0].into();
456 assert!(matches!(c, Color::RgbaFloat(_, _, _, _)));
457 }
458
459 #[test]
460 fn test_from_str() {
461 let c: Color = "#ff8000".into();
462 assert_eq!(c.as_rgb(), (255, 128, 0));
463 }
464
465 #[test]
466 fn test_alpha_rgb() {
467 let c = Color::rgb(255, 128, 0);
468 assert!((c.alpha() - 1.0).abs() < 0.01);
469 }
470
471 #[test]
472 fn test_alpha_rgba() {
473 let c = Color::rgba(255, 128, 0, 128);
474 assert!((c.alpha() - 0.502).abs() < 0.01);
475 }
476
477 #[test]
478 fn test_clamp_float_values() {
479 let c = Color::rgba_float(1.5, -0.5, 0.5, 2.0);
480 let (r, g, b, a) = c.as_rgba();
481 assert_eq!(r, 255);
482 assert_eq!(g, 0);
483 assert_eq!(b, 127);
484 assert_eq!(a, 255);
485 }
486}