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