1use crate::{Error, Result};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct Color {
6 pub r: u8,
7 pub g: u8,
8 pub b: u8,
9 pub a: u8,
10}
11
12impl Color {
13 pub const BLACK: Self = Self { r: 0, g: 0, b: 0, a: 255 };
14 pub const WHITE: Self = Self { r: 255, g: 255, b: 255, a: 255 };
15 pub const TRANSPARENT: Self = Self { r: 0, g: 0, b: 0, a: 0 };
16
17 pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self { Self { r, g, b, a } }
18
19 pub fn from_hex(s: &str) -> Result<Self> {
21 let s = s.strip_prefix('#').unwrap_or(s);
22 match s.len() {
23 3 => {
24 let r = hex_nibble(s.as_bytes()[0])?;
25 let g = hex_nibble(s.as_bytes()[1])?;
26 let b = hex_nibble(s.as_bytes()[2])?;
27 Ok(Self::new(r << 4 | r, g << 4 | g, b << 4 | b, 255))
28 }
29 6 => {
30 let r = hex_byte(&s[0..2])?;
31 let g = hex_byte(&s[2..4])?;
32 let b = hex_byte(&s[4..6])?;
33 Ok(Self::new(r, g, b, 255))
34 }
35 8 => {
36 let r = hex_byte(&s[0..2])?;
37 let g = hex_byte(&s[2..4])?;
38 let b = hex_byte(&s[4..6])?;
39 let a = hex_byte(&s[6..8])?;
40 Ok(Self::new(r, g, b, a))
41 }
42 _ => Err(Error::InvalidColor(format!(
43 "expected #RGB, #RRGGBB, or #RRGGBBAA, got: #{s}"
44 ))),
45 }
46 }
47
48 pub fn is_transparent(self) -> bool { self.a < 3 }
50}
51
52impl std::fmt::Display for Color {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 if self.a == 255 {
55 write!(f, "#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
56 } else {
57 write!(
58 f,
59 "#{:02X}{:02X}{:02X}{:02X}",
60 self.r, self.g, self.b, self.a
61 )
62 }
63 }
64}
65
66fn hex_nibble(b: u8) -> Result<u8> {
67 match b {
68 b'0'..=b'9' => Ok(b - b'0'),
69 b'a'..=b'f' => Ok(b - b'a' + 10),
70 b'A'..=b'F' => Ok(b - b'A' + 10),
71 _ => Err(Error::InvalidColor(format!("invalid hex digit: {b}"))),
72 }
73}
74
75fn hex_byte(s: &str) -> Result<u8> {
76 let hi = hex_nibble(s.as_bytes()[0])?;
77 let lo = hex_nibble(s.as_bytes()[1])?;
78 Ok(hi << 4 | lo)
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn parse_hex_6() {
87 let c = Color::from_hex("#FF8000").unwrap();
88 assert_eq!(c, Color::new(255, 128, 0, 255));
89 }
90
91 #[test]
92 fn parse_hex_8() {
93 let c = Color::from_hex("#FF800080").unwrap();
94 assert_eq!(c, Color::new(255, 128, 0, 128));
95 }
96
97 #[test]
98 fn parse_hex_3() {
99 let c = Color::from_hex("#F80").unwrap();
100 assert_eq!(c, Color::new(0xFF, 0x88, 0x00, 255));
101 }
102
103 #[test]
104 fn display_rgb() {
105 assert_eq!(Color::BLACK.to_string(), "#000000");
106 }
107
108 #[test]
109 fn display_rgba() {
110 assert_eq!(Color::new(255, 128, 0, 128).to_string(), "#FF800080");
111 }
112}