1use anyhow::bail;
2use serde::de::{self, Deserialize, Deserializer, Visitor};
3use std::fmt;
4
5pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
6 let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
7 let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
8 let b = (hex & 0xFF) as f32 / 255.0;
9 Rgba { r, g, b, a: 1.0 }.into()
10}
11
12pub fn rgba(hex: u32) -> Rgba {
13 let r = ((hex >> 24) & 0xFF) as f32 / 255.0;
14 let g = ((hex >> 16) & 0xFF) as f32 / 255.0;
15 let b = ((hex >> 8) & 0xFF) as f32 / 255.0;
16 let a = (hex & 0xFF) as f32 / 255.0;
17 Rgba { r, g, b, a }
18}
19
20#[derive(PartialEq, Clone, Copy, Default)]
21pub struct Rgba {
22 pub r: f32,
23 pub g: f32,
24 pub b: f32,
25 pub a: f32,
26}
27
28impl fmt::Debug for Rgba {
29 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
30 write!(f, "rgba({:#010x})", u32::from(*self))
31 }
32}
33
34impl Rgba {
35 pub fn blend(&self, other: Rgba) -> Self {
36 if other.a >= 1.0 {
37 other
38 } else if other.a <= 0.0 {
39 return *self;
40 } else {
41 return Rgba {
42 r: (self.r * (1.0 - other.a)) + (other.r * other.a),
43 g: (self.g * (1.0 - other.a)) + (other.g * other.a),
44 b: (self.b * (1.0 - other.a)) + (other.b * other.a),
45 a: self.a,
46 };
47 }
48 }
49}
50
51impl From<Rgba> for u32 {
52 fn from(rgba: Rgba) -> Self {
53 let r = (rgba.r * 255.0) as u32;
54 let g = (rgba.g * 255.0) as u32;
55 let b = (rgba.b * 255.0) as u32;
56 let a = (rgba.a * 255.0) as u32;
57 (r << 24) | (g << 16) | (b << 8) | a
58 }
59}
60
61struct RgbaVisitor;
62
63impl<'de> Visitor<'de> for RgbaVisitor {
64 type Value = Rgba;
65
66 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
67 formatter.write_str("a string in the format #rrggbb or #rrggbbaa")
68 }
69
70 fn visit_str<E: de::Error>(self, value: &str) -> Result<Rgba, E> {
71 Rgba::try_from(value).map_err(E::custom)
72 }
73}
74
75impl<'de> Deserialize<'de> for Rgba {
76 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
77 deserializer.deserialize_str(RgbaVisitor)
78 }
79}
80
81impl From<Hsla> for Rgba {
82 fn from(color: Hsla) -> Self {
83 let h = color.h;
84 let s = color.s;
85 let l = color.l;
86
87 let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
88 let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
89 let m = l - c / 2.0;
90 let cm = c + m;
91 let xm = x + m;
92
93 let (r, g, b) = match (h * 6.0).floor() as i32 {
94 0 | 6 => (cm, xm, m),
95 1 => (xm, cm, m),
96 2 => (m, cm, xm),
97 3 => (m, xm, cm),
98 4 => (xm, m, cm),
99 _ => (cm, m, xm),
100 };
101
102 Rgba {
103 r,
104 g,
105 b,
106 a: color.a,
107 }
108 }
109}
110
111impl TryFrom<&'_ str> for Rgba {
112 type Error = anyhow::Error;
113
114 fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
115 const RGB: usize = "rgb".len();
116 const RGBA: usize = "rgba".len();
117 const RRGGBB: usize = "rrggbb".len();
118 const RRGGBBAA: usize = "rrggbbaa".len();
119
120 const EXPECTED_FORMATS: &str = "Expected #rgb, #rgba, #rrggbb, or #rrggbbaa";
121
122 let Some(("", hex)) = value.trim().split_once('#') else {
123 bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}");
124 };
125
126 let (r, g, b, a) = match hex.len() {
127 RGB | RGBA => {
128 let r = u8::from_str_radix(&hex[0..1], 16)?;
129 let g = u8::from_str_radix(&hex[1..2], 16)?;
130 let b = u8::from_str_radix(&hex[2..3], 16)?;
131 let a = if hex.len() == RGBA {
132 u8::from_str_radix(&hex[3..4], 16)?
133 } else {
134 0xf
135 };
136
137 /// Duplicates a given hex digit.
138 /// E.g., `0xf` -> `0xff`.
139 const fn duplicate(value: u8) -> u8 {
140 value << 4 | value
141 }
142
143 (duplicate(r), duplicate(g), duplicate(b), duplicate(a))
144 }
145 RRGGBB | RRGGBBAA => {
146 let r = u8::from_str_radix(&hex[0..2], 16)?;
147 let g = u8::from_str_radix(&hex[2..4], 16)?;
148 let b = u8::from_str_radix(&hex[4..6], 16)?;
149 let a = if hex.len() == RRGGBBAA {
150 u8::from_str_radix(&hex[6..8], 16)?
151 } else {
152 0xff
153 };
154 (r, g, b, a)
155 }
156 _ => bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}"),
157 };
158
159 Ok(Rgba {
160 r: r as f32 / 255.,
161 g: g as f32 / 255.,
162 b: b as f32 / 255.,
163 a: a as f32 / 255.,
164 })
165 }
166}
167
168#[derive(Default, Copy, Clone, Debug)]
169#[repr(C)]
170pub struct Hsla {
171 pub h: f32,
172 pub s: f32,
173 pub l: f32,
174 pub a: f32,
175}
176
177impl PartialEq for Hsla {
178 fn eq(&self, other: &Self) -> bool {
179 self.h
180 .total_cmp(&other.h)
181 .then(self.s.total_cmp(&other.s))
182 .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a)))
183 .is_eq()
184 }
185}
186
187impl PartialOrd for Hsla {
188 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
189 // SAFETY: The total ordering relies on this always being Some()
190 Some(
191 self.h
192 .total_cmp(&other.h)
193 .then(self.s.total_cmp(&other.s))
194 .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a))),
195 )
196 }
197}
198
199impl Ord for Hsla {
200 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
201 // SAFETY: The partial comparison is a total comparison
202 unsafe { self.partial_cmp(other).unwrap_unchecked() }
203 }
204}
205
206impl Hsla {
207 pub fn to_rgb(self) -> Rgba {
208 self.into()
209 }
210
211 pub fn red() -> Self {
212 red()
213 }
214
215 pub fn green() -> Self {
216 green()
217 }
218
219 pub fn blue() -> Self {
220 blue()
221 }
222
223 pub fn black() -> Self {
224 black()
225 }
226
227 pub fn white() -> Self {
228 white()
229 }
230
231 pub fn transparent_black() -> Self {
232 transparent_black()
233 }
234}
235
236impl Eq for Hsla {}
237
238pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
239 Hsla {
240 h: h.clamp(0., 1.),
241 s: s.clamp(0., 1.),
242 l: l.clamp(0., 1.),
243 a: a.clamp(0., 1.),
244 }
245}
246
247pub fn black() -> Hsla {
248 Hsla {
249 h: 0.,
250 s: 0.,
251 l: 0.,
252 a: 1.,
253 }
254}
255
256pub fn transparent_black() -> Hsla {
257 Hsla {
258 h: 0.,
259 s: 0.,
260 l: 0.,
261 a: 0.,
262 }
263}
264
265pub fn white() -> Hsla {
266 Hsla {
267 h: 0.,
268 s: 0.,
269 l: 1.,
270 a: 1.,
271 }
272}
273
274pub fn red() -> Hsla {
275 Hsla {
276 h: 0.,
277 s: 1.,
278 l: 0.5,
279 a: 1.,
280 }
281}
282
283pub fn blue() -> Hsla {
284 Hsla {
285 h: 0.6,
286 s: 1.,
287 l: 0.5,
288 a: 1.,
289 }
290}
291
292pub fn green() -> Hsla {
293 Hsla {
294 h: 0.33,
295 s: 1.,
296 l: 0.5,
297 a: 1.,
298 }
299}
300
301pub fn yellow() -> Hsla {
302 Hsla {
303 h: 0.16,
304 s: 1.,
305 l: 0.5,
306 a: 1.,
307 }
308}
309
310impl Hsla {
311 /// Returns true if the HSLA color is fully transparent, false otherwise.
312 pub fn is_transparent(&self) -> bool {
313 self.a == 0.0
314 }
315
316 /// Blends `other` on top of `self` based on `other`'s alpha value. The resulting color is a combination of `self`'s and `other`'s colors.
317 ///
318 /// If `other`'s alpha value is 1.0 or greater, `other` color is fully opaque, thus `other` is returned as the output color.
319 /// If `other`'s alpha value is 0.0 or less, `other` color is fully transparent, thus `self` is returned as the output color.
320 /// Else, the output color is calculated as a blend of `self` and `other` based on their weighted alpha values.
321 ///
322 /// Assumptions:
323 /// - Alpha values are contained in the range [0, 1], with 1 as fully opaque and 0 as fully transparent.
324 /// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing it's own alpha value.
325 /// - RGB color components are contained in the range [0, 1].
326 /// - If `self` and `other` colors are out of the valid range, the blend operation's output and behavior is undefined.
327 pub fn blend(self, other: Hsla) -> Hsla {
328 let alpha = other.a;
329
330 if alpha >= 1.0 {
331 other
332 } else if alpha <= 0.0 {
333 return self;
334 } else {
335 let converted_self = Rgba::from(self);
336 let converted_other = Rgba::from(other);
337 let blended_rgb = converted_self.blend(converted_other);
338 return Hsla::from(blended_rgb);
339 }
340 }
341
342 /// Fade out the color by a given factor. This factor should be between 0.0 and 1.0.
343 /// Where 0.0 will leave the color unchanged, and 1.0 will completely fade out the color.
344 pub fn fade_out(&mut self, factor: f32) {
345 self.a *= 1.0 - factor.clamp(0., 1.);
346 }
347}
348
349// impl From<Hsla> for Rgba {
350// fn from(value: Hsla) -> Self {
351// let h = value.h;
352// let s = value.s;
353// let l = value.l;
354
355// let c = (1 - |2L - 1|) X s
356// }
357// }
358
359impl From<Rgba> for Hsla {
360 fn from(color: Rgba) -> Self {
361 let r = color.r;
362 let g = color.g;
363 let b = color.b;
364
365 let max = r.max(g.max(b));
366 let min = r.min(g.min(b));
367 let delta = max - min;
368
369 let l = (max + min) / 2.0;
370 let s = if l == 0.0 || l == 1.0 {
371 0.0
372 } else if l < 0.5 {
373 delta / (2.0 * l)
374 } else {
375 delta / (2.0 - 2.0 * l)
376 };
377
378 let h = if delta == 0.0 {
379 0.0
380 } else if max == r {
381 ((g - b) / delta).rem_euclid(6.0) / 6.0
382 } else if max == g {
383 ((b - r) / delta + 2.0) / 6.0
384 } else {
385 ((r - g) / delta + 4.0) / 6.0
386 };
387
388 Hsla {
389 h,
390 s,
391 l,
392 a: color.a,
393 }
394 }
395}
396
397impl<'de> Deserialize<'de> for Hsla {
398 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
399 where
400 D: Deserializer<'de>,
401 {
402 // First, deserialize it into Rgba
403 let rgba = Rgba::deserialize(deserializer)?;
404
405 // Then, use the From<Rgba> for Hsla implementation to convert it
406 Ok(Hsla::from(rgba))
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use serde_json::json;
413
414 use super::*;
415
416 #[test]
417 fn test_deserialize_three_value_hex_to_rgba() {
418 let actual: Rgba = serde_json::from_value(json!("#f09")).unwrap();
419
420 assert_eq!(actual, rgba(0xff0099ff))
421 }
422
423 #[test]
424 fn test_deserialize_four_value_hex_to_rgba() {
425 let actual: Rgba = serde_json::from_value(json!("#f09f")).unwrap();
426
427 assert_eq!(actual, rgba(0xff0099ff))
428 }
429
430 #[test]
431 fn test_deserialize_six_value_hex_to_rgba() {
432 let actual: Rgba = serde_json::from_value(json!("#ff0099")).unwrap();
433
434 assert_eq!(actual, rgba(0xff0099ff))
435 }
436
437 #[test]
438 fn test_deserialize_eight_value_hex_to_rgba() {
439 let actual: Rgba = serde_json::from_value(json!("#ff0099ff")).unwrap();
440
441 assert_eq!(actual, rgba(0xff0099ff))
442 }
443
444 #[test]
445 fn test_deserialize_eight_value_hex_with_padding_to_rgba() {
446 let actual: Rgba = serde_json::from_value(json!(" #f5f5f5ff ")).unwrap();
447
448 assert_eq!(actual, rgba(0xf5f5f5ff))
449 }
450
451 #[test]
452 fn test_deserialize_eight_value_hex_with_mixed_case_to_rgba() {
453 let actual: Rgba = serde_json::from_value(json!("#DeAdbEeF")).unwrap();
454
455 assert_eq!(actual, rgba(0xdeadbeef))
456 }
457}