1include!(concat!(env!("OUT_DIR"), "/colors.rs"));
2
3#[cfg(not(feature = "no_std"))]
4use std::fmt;
5
6#[derive(Copy, Clone, PartialOrd, Default)]
8#[repr(packed)]
9pub struct Color {
10 pub data: u32,
11}
12
13impl Color {
14 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
16 Color {
17 data: 0xFF00_0000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32),
18 }
19 }
20
21 pub fn hsv(h: f64, s: f64, v: f64) -> Self {
23 Self::hsva(h, s, v, 1.0)
24 }
25
26 pub fn hsl(h: f64, s: f64, l: f64) -> Self {
28 Self::hsla(h, s, l, 1.0)
29 }
30
31 pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
33 Color {
34 data: ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32),
35 }
36 }
37
38 pub fn hsva(mut hue: f64, mut saturation: f64, mut value: f64, alpha: f64) -> Self {
40 hue %= 360.0;
41 saturation = saturation.max(0.0).min(1.0);
42 value = value.max(0.0).min(1.0);
43 let hh = hue / 60.0;
44 let idx = hh.floor() as i32;
45 let ff = hh.fract();
46 let chroma = value * (1.0 - saturation);
47 let second_component = value * (1.0 - (saturation * ff));
48 let t = value * (1.0 - (saturation * (1.0 - ff)));
49 let (r, g, b) = match idx {
50 0 => (value, t, chroma),
51 1 => (second_component, value, chroma),
52 2 => (chroma, value, t),
53 3 => (chroma, second_component, value),
54 4 => (t, chroma, value),
55 5 => (value, chroma, second_component),
56 _ => unreachable!(),
57 };
58 Self::rgba(
59 (r * 255.0) as u8,
60 (g * 255.0) as u8,
61 (b * 255.0) as u8,
62 (alpha * 255.0) as u8,
63 )
64 }
65
66 pub fn hsla(mut hue: f64, mut saturation: f64, mut lightness: f64, alpha: f64) -> Self {
68 hue %= 360.0;
69 saturation = saturation.max(0.0).min(1.0);
70 lightness = lightness.max(0.0).min(1.0);
71 let hh = hue / 60.0;
72 let idx = hh.floor() as i32;
73 let chroma = (1.0 - ((2.0 * lightness) - 1.0).abs()) * saturation;
74 let second_component = chroma * (1.0 - (((idx % 2) as f64) - 1.0).abs());
75 let (mut r, mut g, mut b) = match idx {
76 0 => (chroma, second_component, 0.0),
77 1 => (second_component, chroma, 0.0),
78 2 => (0.0, chroma, second_component),
79 3 => (0.0, second_component, chroma),
80 4 => (second_component, 0.0, chroma),
81 5 => (chroma, 0.0, second_component),
82 _ => unreachable!(),
83 };
84 let adjustment = lightness - chroma / 2.0;
85 r += adjustment;
86 g += adjustment;
87 b += adjustment;
88 Self::rgba(
89 (r.min(1.0) * 255.0) as u8,
90 (g.min(1.0) * 255.0) as u8,
91 (b.min(1.0) * 255.0) as u8,
92 (alpha * 255.0) as u8,
93 )
94 }
95
96 pub fn r(self) -> u8 {
98 ((self.data & 0x00FF_0000) >> 16) as u8
99 }
100
101 pub fn g(self) -> u8 {
103 ((self.data & 0x0000_FF00) >> 8) as u8
104 }
105
106 pub fn b(self) -> u8 {
108 (self.data & 0x0000_00FF) as u8
109 }
110
111 pub fn a(self) -> u8 {
113 ((self.data & 0xFF00_0000) >> 24) as u8
114 }
115
116 pub fn from_name(name: &str) -> Option<Color> {
118 COLORS.get(name).cloned()
119 }
120
121 pub fn interpolate(start_color: Color, end_color: Color, scale: f64) -> Color {
123 let r = Color::interp(start_color.r(), end_color.r(), scale);
124 let g = Color::interp(start_color.g(), end_color.g(), scale);
125 let b = Color::interp(start_color.b(), end_color.b(), scale);
126 let a = Color::interp(start_color.a(), end_color.a(), scale);
127 Color::rgba(r, g, b, a)
128 }
129
130 fn interp(start_color: u8, end_color: u8, scale: f64) -> u8 {
131 (end_color as f64 - start_color as f64).mul_add(scale, start_color as f64) as u8
132 }
133}
134
135impl ToString for Color {
136 fn to_string(&self) -> String {
137 if self.a() == 0 {
138 return String::from("transparent");
139 }
140
141 let data = self.data;
142
143 let mut color = format!("#{:x}", data);
144 color.remove(1);
145 color.remove(1);
146 color
147 }
148}
149
150impl From<&str> for Color {
151 fn from(s: &str) -> Color {
152 if !s.starts_with('#') {
153 if let Some(color) = Color::from_name(s) {
154 return color;
155 }
156 }
157 let clean_hex = s.trim_start_matches('#');
158 match clean_hex.len() {
159 3 | 4 => {
160 let num = u32::from_str_radix(clean_hex, 16).unwrap_or(0);
161
162 let mut blue = (num & 0xF) << 4;
163 let mut green = ((num >> 4) & 0xF) << 4;
164 let mut red = ((num >> 8) & 0xF) << 4;
165 let mut alpha = match clean_hex.len() == 4 {
166 true => ((num >> 12) & 0xF) << 4,
167 false => 0xF,
168 };
169 red |= red >> 4;
170 green |= green >> 4;
171 blue |= blue >> 4;
172 alpha |= alpha >> 4;
173
174 Color::rgba(red as u8, green as u8, blue as u8, alpha as u8)
175 }
176 6 | 8 => {
177 let mut x = u32::from_str_radix(clean_hex, 16).unwrap_or(0);
178
179 if clean_hex.len() == 6 {
180 x |= 0xFF00_0000;
181 }
182
183 Color { data: x }
184 }
185 _ => Color { data: 0 },
186 }
187 }
188}
189
190impl From<String> for Color {
191 fn from(s: String) -> Color {
192 Color::from(s.as_str())
193 }
194}
195
196impl PartialEq for Color {
198 fn eq(&self, other: &Color) -> bool {
199 self.r() == other.r() && self.g() == other.g() && self.b() == other.b()
200 }
201}
202
203#[cfg(not(feature = "no_std"))]
204impl fmt::Debug for Color {
205 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
206 write!(f, "{:#010X}", { self.data })
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 #[test]
214 fn partial_eq() {
215 let color_1 = Color::rgb(1, 2, 3);
216 let color_2 = Color::rgba(1, 2, 3, 200);
217 let color_3 = Color::rgba(11, 2, 3, 200);
218
219 assert!(
220 color_1.eq(&color_2),
221 "color_1={:?}(, color_2={:?}",
222 &color_1,
223 &color_2
224 );
225 assert!(
226 color_1.eq(&color_2),
227 "color_1={:?}(, color_2={:?}",
228 &color_1,
229 &color_2
230 );
231 assert!(
232 color_2.ne(&color_3),
233 "color_1={:?}(, color_2={:?}",
234 &color_2,
235 &color_3
236 );
237 }
238}