1#[derive(Debug, Clone, Copy, PartialEq, Default)]
3#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
4pub struct Color {
5 pub r: f32,
7 pub g: f32,
9 pub b: f32,
11 pub a: f32,
13}
14
15impl Color {
16 pub const BLACK: Color = Color {
18 r: 0.0,
19 g: 0.0,
20 b: 0.0,
21 a: 1.0,
22 };
23
24 pub const WHITE: Color = Color {
26 r: 1.0,
27 g: 1.0,
28 b: 1.0,
29 a: 1.0,
30 };
31
32 pub const TRANSPARENT: Color = Color {
34 r: 0.0,
35 g: 0.0,
36 b: 0.0,
37 a: 0.0,
38 };
39
40 const fn new(r: f32, g: f32, b: f32, a: f32) -> Color {
45 debug_assert!(
46 r >= 0.0 && r <= 1.0,
47 "Red component must be in [0, 1] range."
48 );
49 debug_assert!(
50 g >= 0.0 && g <= 1.0,
51 "Green component must be in [0, 1] range."
52 );
53 debug_assert!(
54 b >= 0.0 && b <= 1.0,
55 "Blue component must be in [0, 1] range."
56 );
57
58 Color { r, g, b, a }
59 }
60
61 pub const fn from_rgb(r: f32, g: f32, b: f32) -> Color {
63 Color::from_rgba(r, g, b, 1.0f32)
64 }
65
66 pub const fn from_rgba(r: f32, g: f32, b: f32, a: f32) -> Color {
68 Color::new(r, g, b, a)
69 }
70
71 pub const fn from_rgb8(r: u8, g: u8, b: u8) -> Color {
73 Color::from_rgba8(r, g, b, 1.0)
74 }
75
76 pub const fn from_rgba8(r: u8, g: u8, b: u8, a: f32) -> Color {
78 Color::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, a)
79 }
80
81 pub fn from_linear_rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
83 fn gamma_component(u: f32) -> f32 {
86 if u < 0.0031308 {
87 12.92 * u
88 } else {
89 1.055 * u.powf(1.0 / 2.4) - 0.055
90 }
91 }
92
93 Self {
94 r: gamma_component(r),
95 g: gamma_component(g),
96 b: gamma_component(b),
97 a,
98 }
99 }
100
101 pub fn parse(s: &str) -> Option<Color> {
111 let hex = s.strip_prefix('#').unwrap_or(s);
112
113 let parse_channel = |from: usize, to: usize| {
114 let num =
115 usize::from_str_radix(&hex[from..=to], 16).ok()? as f32 / 255.0;
116
117 Some(if from == to { num + num * 16.0 } else { num })
119 };
120
121 Some(match hex.len() {
122 3 => Color::from_rgb(
123 parse_channel(0, 0)?,
124 parse_channel(1, 1)?,
125 parse_channel(2, 2)?,
126 ),
127 4 => Color::from_rgba(
128 parse_channel(0, 0)?,
129 parse_channel(1, 1)?,
130 parse_channel(2, 2)?,
131 parse_channel(3, 3)?,
132 ),
133 6 => Color::from_rgb(
134 parse_channel(0, 1)?,
135 parse_channel(2, 3)?,
136 parse_channel(4, 5)?,
137 ),
138 8 => Color::from_rgba(
139 parse_channel(0, 1)?,
140 parse_channel(2, 3)?,
141 parse_channel(4, 5)?,
142 parse_channel(6, 7)?,
143 ),
144 _ => None?,
145 })
146 }
147
148 #[must_use]
150 pub fn into_rgba8(self) -> [u8; 4] {
151 [
152 (self.r * 255.0).round() as u8,
153 (self.g * 255.0).round() as u8,
154 (self.b * 255.0).round() as u8,
155 (self.a * 255.0).round() as u8,
156 ]
157 }
158
159 pub fn into_linear(self) -> [f32; 4] {
161 fn linear_component(u: f32) -> f32 {
164 if u < 0.04045 {
165 u / 12.92
166 } else {
167 ((u + 0.055) / 1.055).powf(2.4)
168 }
169 }
170
171 [
172 linear_component(self.r),
173 linear_component(self.g),
174 linear_component(self.b),
175 self.a,
176 ]
177 }
178
179 pub fn invert(&mut self) {
181 self.r = 1.0f32 - self.r;
182 self.b = 1.0f32 - self.g;
183 self.g = 1.0f32 - self.b;
184 }
185
186 pub fn inverse(self) -> Color {
188 Color::new(1.0f32 - self.r, 1.0f32 - self.g, 1.0f32 - self.b, self.a)
189 }
190
191 pub fn scale_alpha(self, factor: f32) -> Color {
193 Self {
194 a: self.a * factor,
195 ..self
196 }
197 }
198}
199
200impl From<[f32; 3]> for Color {
201 fn from([r, g, b]: [f32; 3]) -> Self {
202 Color::new(r, g, b, 1.0)
203 }
204}
205
206impl From<[f32; 4]> for Color {
207 fn from([r, g, b, a]: [f32; 4]) -> Self {
208 Color::new(r, g, b, a)
209 }
210}
211
212#[macro_export]
225macro_rules! color {
226 ($r:expr, $g:expr, $b:expr) => {
227 $crate::Color::from_rgb8($r, $g, $b)
228 };
229 ($r:expr, $g:expr, $b:expr, $a:expr) => {{ $crate::Color::from_rgba8($r, $g, $b, $a) }};
230 ($hex:expr) => {{ $crate::color!($hex, 1.0) }};
231 ($hex:expr, $a:expr) => {{
232 let hex = $hex as u32;
233
234 debug_assert!(hex <= 0xffffff, "color! value must not exceed 0xffffff");
235
236 let r = (hex & 0xff0000) >> 16;
237 let g = (hex & 0xff00) >> 8;
238 let b = (hex & 0xff);
239
240 $crate::color!(r as u8, g as u8, b as u8, $a)
241 }};
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn parse() {
250 let tests = [
251 ("#ff0000", [255, 0, 0, 255]),
252 ("00ff0080", [0, 255, 0, 128]),
253 ("#F80", [255, 136, 0, 255]),
254 ("#00f1", [0, 0, 255, 17]),
255 ];
256
257 for (arg, expected) in tests {
258 assert_eq!(
259 Color::parse(arg).expect("color must parse").into_rgba8(),
260 expected
261 );
262 }
263
264 assert!(Color::parse("invalid").is_none());
265 }
266}