1use std::fmt;
7use std::str::FromStr;
8
9use crate::math;
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct SolidColor {
14 r: f64,
15 g: f64,
16 b: f64,
17 a: f64,
18}
19
20impl SolidColor {
21 pub fn r(&self) -> f64 {
23 self.r
24 }
25 pub fn g(&self) -> f64 {
27 self.g
28 }
29 pub fn b(&self) -> f64 {
31 self.b
32 }
33 pub fn a(&self) -> f64 {
35 self.a
36 }
37
38 pub fn rgba(&self) -> (f64, f64, f64, f64) {
40 (self.r, self.g, self.b, self.a)
41 }
42}
43
44impl Default for SolidColor {
45 fn default() -> Self {
46 Self {
47 r: 0.5,
48 g: 0.5,
49 b: 0.5,
50 a: 1.0,
51 }
52 }
53}
54
55impl SolidColor {
56 pub fn from_rgb(r: u8, g: u8, b: u8) -> Self {
58 Self {
59 r: r as f64 / 255.0,
60 g: g as f64 / 255.0,
61 b: b as f64 / 255.0,
62 a: 1.0,
63 }
64 }
65
66 pub fn to_rgb(&self) -> (u8, u8, u8) {
68 (
69 (self.r * 255.0).round() as u8,
70 (self.g * 255.0).round() as u8,
71 (self.b * 255.0).round() as u8,
72 )
73 }
74
75 pub fn from_hex(hex: &str) -> Option<Self> {
79 let stripped = hex.trim_start_matches('#');
80 if !stripped.chars().all(|c| c.is_ascii_hexdigit()) {
81 return None;
82 }
83 match stripped.len() {
84 3 => {
85 let r = u8::from_str_radix(&stripped[0..1], 16).ok()?;
86 let g = u8::from_str_radix(&stripped[1..2], 16).ok()?;
87 let b = u8::from_str_radix(&stripped[2..3], 16).ok()?;
88 Some(Self {
89 r: (r * 17) as f64 / 255.0,
90 g: (g * 17) as f64 / 255.0,
91 b: (b * 17) as f64 / 255.0,
92 a: 1.0,
93 })
94 }
95 6 => {
96 let r = u8::from_str_radix(&stripped[0..2], 16).ok()?;
97 let g = u8::from_str_radix(&stripped[2..4], 16).ok()?;
98 let b = u8::from_str_radix(&stripped[4..6], 16).ok()?;
99 Some(Self {
100 r: r as f64 / 255.0,
101 g: g as f64 / 255.0,
102 b: b as f64 / 255.0,
103 a: 1.0,
104 })
105 }
106 8 => {
107 let r = u8::from_str_radix(&stripped[0..2], 16).ok()?;
108 let g = u8::from_str_radix(&stripped[2..4], 16).ok()?;
109 let b = u8::from_str_radix(&stripped[4..6], 16).ok()?;
110 let a = u8::from_str_radix(&stripped[6..8], 16).ok()?;
111 Some(Self {
112 r: r as f64 / 255.0,
113 g: g as f64 / 255.0,
114 b: b as f64 / 255.0,
115 a: a as f64 / 255.0,
116 })
117 }
118 _ => None,
119 }
120 }
121
122 pub fn to_hex(&self) -> String {
127 let (r, g, b) = self.to_rgb();
128 let a = (self.a * 255.0).round() as u8;
129 if a == 255 {
130 format!("{:02X}{:02X}{:02X}", r, g, b)
131 } else {
132 format!("{:02X}{:02X}{:02X}{:02X}", r, g, b, a)
133 }
134 }
135
136 pub fn from_hsb(h: f64, s: f64, b: f64, a: f64) -> Self {
138 let (r, g, bl) = math::hsb_to_rgb(h, s, b);
139 Self { r, g, b: bl, a }
140 }
141
142 pub fn to_hsb(&self) -> (f64, f64, f64) {
144 math::rgb_to_hsb(self.r, self.g, self.b)
145 }
146
147 pub fn from_hsl(h: f64, s: f64, l: f64, a: f64) -> Self {
149 let (hb, sb, vb) = math::hsl_to_hsb(h, s, l);
150 let (r, g, bl) = math::hsb_to_rgb(hb, sb, vb);
151 Self { r, g, b: bl, a }
152 }
153
154 pub fn to_hsl(&self) -> (f64, f64, f64) {
156 let (h, s, v) = math::rgb_to_hsb(self.r, self.g, self.b);
157 math::hsb_to_hsl(h, s, v)
158 }
159
160 pub fn from_rgba(r: f64, g: f64, b: f64, a: f64) -> Self {
162 Self {
163 r: r.clamp(0.0, 1.0),
164 g: g.clamp(0.0, 1.0),
165 b: b.clamp(0.0, 1.0),
166 a: a.clamp(0.0, 1.0),
167 }
168 }
169}
170
171impl fmt::Display for SolidColor {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 write!(f, "#{}", self.to_hex())
175 }
176}
177
178impl FromStr for SolidColor {
179 type Err = String;
180
181 fn from_str(s: &str) -> Result<Self, Self::Err> {
183 SolidColor::from_hex(s).ok_or_else(|| format!("invalid hex color: {s}"))
184 }
185}