1use palette::rgb::{Srgb, Srgba};
2
3#[derive(Debug, Clone, Copy, PartialEq, Default)]
5pub struct Color {
6 pub r: f32,
8 pub g: f32,
10 pub b: f32,
12 pub a: f32,
14}
15
16impl Color {
17 pub const BLACK: Color = Color {
19 r: 0.0,
20 g: 0.0,
21 b: 0.0,
22 a: 1.0,
23 };
24
25 pub const WHITE: Color = Color {
27 r: 1.0,
28 g: 1.0,
29 b: 1.0,
30 a: 1.0,
31 };
32
33 pub const TRANSPARENT: Color = Color {
35 r: 0.0,
36 g: 0.0,
37 b: 0.0,
38 a: 0.0,
39 };
40
41 const fn new(r: f32, g: f32, b: f32, a: f32) -> Color {
46 debug_assert!(
47 r >= 0.0 && r <= 1.0,
48 "Red component must be in [0, 1] range."
49 );
50 debug_assert!(
51 g >= 0.0 && g <= 1.0,
52 "Green component must be in [0, 1] range."
53 );
54 debug_assert!(
55 b >= 0.0 && b <= 1.0,
56 "Blue component must be in [0, 1] range."
57 );
58
59 Color { r, g, b, a }
60 }
61
62 pub const fn from_rgb(r: f32, g: f32, b: f32) -> Color {
64 Color::from_rgba(r, g, b, 1.0f32)
65 }
66
67 pub const fn from_rgba(r: f32, g: f32, b: f32, a: f32) -> Color {
69 Color::new(r, g, b, a)
70 }
71
72 pub const fn from_rgb8(r: u8, g: u8, b: u8) -> Color {
74 Color::from_rgba8(r, g, b, 1.0)
75 }
76
77 pub const fn from_rgba8(r: u8, g: u8, b: u8, a: f32) -> Color {
79 Color::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, a)
80 }
81
82 pub fn from_linear_rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
84 fn gamma_component(u: f32) -> f32 {
87 if u < 0.0031308 {
88 12.92 * u
89 } else {
90 1.055 * u.powf(1.0 / 2.4) - 0.055
91 }
92 }
93
94 Self {
95 r: gamma_component(r),
96 g: gamma_component(g),
97 b: gamma_component(b),
98 a,
99 }
100 }
101
102 pub fn parse(s: &str) -> Option<Color> {
112 let hex = s.strip_prefix('#').unwrap_or(s);
113
114 let parse_channel = |from: usize, to: usize| {
115 let num =
116 usize::from_str_radix(&hex[from..=to], 16).ok()? as f32 / 255.0;
117
118 Some(if from == to { num + num * 16.0 } else { num })
120 };
121
122 Some(match hex.len() {
123 3 => Color::from_rgb(
124 parse_channel(0, 0)?,
125 parse_channel(1, 1)?,
126 parse_channel(2, 2)?,
127 ),
128 4 => Color::from_rgba(
129 parse_channel(0, 0)?,
130 parse_channel(1, 1)?,
131 parse_channel(2, 2)?,
132 parse_channel(3, 3)?,
133 ),
134 6 => Color::from_rgb(
135 parse_channel(0, 1)?,
136 parse_channel(2, 3)?,
137 parse_channel(4, 5)?,
138 ),
139 8 => Color::from_rgba(
140 parse_channel(0, 1)?,
141 parse_channel(2, 3)?,
142 parse_channel(4, 5)?,
143 parse_channel(6, 7)?,
144 ),
145 _ => None?,
146 })
147 }
148
149 #[must_use]
151 pub fn into_rgba8(self) -> [u8; 4] {
152 [
153 (self.r * 255.0).round() as u8,
154 (self.g * 255.0).round() as u8,
155 (self.b * 255.0).round() as u8,
156 (self.a * 255.0).round() as u8,
157 ]
158 }
159
160 pub fn into_linear(self) -> [f32; 4] {
162 fn linear_component(u: f32) -> f32 {
165 if u < 0.04045 {
166 u / 12.92
167 } else {
168 ((u + 0.055) / 1.055).powf(2.4)
169 }
170 }
171
172 [
173 linear_component(self.r),
174 linear_component(self.g),
175 linear_component(self.b),
176 self.a,
177 ]
178 }
179
180 pub fn invert(&mut self) {
182 self.r = 1.0f32 - self.r;
183 self.b = 1.0f32 - self.g;
184 self.g = 1.0f32 - self.b;
185 }
186
187 pub fn inverse(self) -> Color {
189 Color::new(1.0f32 - self.r, 1.0f32 - self.g, 1.0f32 - self.b, self.a)
190 }
191
192 pub fn scale_alpha(self, factor: f32) -> Color {
194 Self {
195 a: self.a * factor,
196 ..self
197 }
198 }
199}
200
201impl From<[f32; 3]> for Color {
202 fn from([r, g, b]: [f32; 3]) -> Self {
203 Color::new(r, g, b, 1.0)
204 }
205}
206
207impl From<[f32; 4]> for Color {
208 fn from([r, g, b, a]: [f32; 4]) -> Self {
209 Color::new(r, g, b, a)
210 }
211}
212
213#[macro_export]
226macro_rules! color {
227 ($r:expr, $g:expr, $b:expr) => {
228 $crate::Color::from_rgb8($r, $g, $b)
229 };
230 ($r:expr, $g:expr, $b:expr, $a:expr) => {{ $crate::Color::from_rgba8($r, $g, $b, $a) }};
231 ($hex:expr) => {{ $crate::color!($hex, 1.0) }};
232 ($hex:expr, $a:expr) => {{
233 let hex = $hex as u32;
234
235 debug_assert!(hex <= 0xffffff, "color! value must not exceed 0xffffff");
236
237 let r = (hex & 0xff0000) >> 16;
238 let g = (hex & 0xff00) >> 8;
239 let b = (hex & 0xff);
240
241 $crate::color!(r as u8, g as u8, b as u8, $a)
242 }};
243}
244
245impl From<Srgba> for Color {
247 fn from(rgba: Srgba) -> Self {
248 Color::new(rgba.red, rgba.green, rgba.blue, rgba.alpha)
249 }
250}
251
252impl From<Color> for Srgba {
254 fn from(c: Color) -> Self {
255 Srgba::new(c.r, c.g, c.b, c.a)
256 }
257}
258
259impl From<Srgb> for Color {
261 fn from(rgb: Srgb) -> Self {
262 Color::new(rgb.red, rgb.green, rgb.blue, 1.0)
263 }
264}
265
266impl From<Color> for Srgb {
268 fn from(c: Color) -> Self {
269 Srgb::new(c.r, c.g, c.b)
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use palette::blend::Blend;
277
278 #[test]
279 fn srgba_traits() {
280 let c = Color::from_rgb(0.5, 0.4, 0.3);
281 let s: Srgba = c.into();
283 let r: Color = s.into();
284 assert_eq!(c, r);
285 }
286
287 #[test]
288 fn color_manipulation() {
289 use approx::assert_relative_eq;
290
291 let c1 = Color::from_rgb(0.5, 0.4, 0.3);
292 let c2 = Color::from_rgb(0.2, 0.5, 0.3);
293
294 let l1 = Srgba::from(c1).into_linear();
296 let l2 = Srgba::from(c2).into_linear();
297
298 let lighter = l1.lighten(l2);
300
301 let result: Color = Srgba::from_linear(lighter).into();
303
304 assert_relative_eq!(result.r, 0.5);
305 assert_relative_eq!(result.g, 0.5);
306 assert_relative_eq!(result.b, 0.3);
307 assert_relative_eq!(result.a, 1.0);
308 }
309
310 #[test]
311 fn parse() {
312 let tests = [
313 ("#ff0000", [255, 0, 0, 255]),
314 ("00ff0080", [0, 255, 0, 128]),
315 ("#F80", [255, 136, 0, 255]),
316 ("#00f1", [0, 0, 255, 17]),
317 ];
318
319 for (arg, expected) in tests {
320 assert_eq!(
321 Color::parse(arg).expect("color must parse").into_rgba8(),
322 expected
323 );
324 }
325
326 assert!(Color::parse("invalid").is_none());
327 }
328}