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