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