1use crate::hsl::HslColor;
2
3#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
4pub struct RgbColor(pub u8, pub u8, pub u8);
5
6impl RgbColor {
7 pub const SCALE: f32 = 255.0;
8
9 pub fn red(self) -> u8 {
10 self.0
11 }
12
13 pub fn green(self) -> u8 {
14 self.1
15 }
16
17 pub fn blue(self) -> u8 {
18 self.2
19 }
20
21 pub fn into_hsl(self) -> HslColor {
22 self.into()
23 }
24}
25
26impl From<HslColor> for RgbColor {
27 fn from(hsl: HslColor) -> Self {
28 let (r, g, b) = match hsl.1 {
29 s if s == 0.0 => (hsl.2, hsl.2, hsl.2),
30 _ => {
31 let q = if hsl.2 < 0.5 {
32 hsl.2 * (1.0 + hsl.1)
33 } else {
34 hsl.2 + hsl.1 - hsl.2 * hsl.1
35 };
36 let p = 2.0 * hsl.2 - q;
37 (
38 get_rgb_from_hue(p, q, hsl.0 + 1.0 / 3.0),
39 get_rgb_from_hue(p, q, hsl.0),
40 get_rgb_from_hue(p, q, hsl.0 - 1.0 / 3.0)
41 )
42 }
43 };
44
45 Self(
46 (r * RgbColor::SCALE).ceil() as u8,
47 (g * RgbColor::SCALE).ceil() as u8,
48 (b * RgbColor::SCALE).ceil() as u8,
49 )
50 }
51}
52
53fn get_rgb_from_hue(p: f32, q: f32, t: f32) -> f32 {
54 let nt = if t < 0.0 { t + 1.0 } else if t > 1.0 { t - 1.0 } else { t };
55
56 match nt {
57 nt if nt < 1.0 / 6.0 => p + (q - p) * 6.0 * nt,
58 nt if nt < 1.0 / 2.0 => q,
59 nt if nt < 2.0 / 3.0 => p + (q - p) * (2.0 / 3.0 - nt) * 6.0,
60 _ => p,
61 }
62}
63
64
65#[test]
66fn test_hsl_to_rgb() {
67 let cases = vec![
68 (HslColor(0.0, 0.0, 0.0), RgbColor(0, 0, 0)),
70 (HslColor(0.0, 0.0, 0.5), RgbColor(128, 128, 128)),
71 (HslColor(180.0 / 360.0, 1.0, 0.5), RgbColor(0, 255, 255)),
72 (HslColor(30.0 / 360.0, 0.5, 0.5), RgbColor(192, 128, 64)),
73 (HslColor(90.0 / 360.0, 0.2, 0.8), RgbColor(204, 215, 194)),
74 ];
75
76 for (hsl, rgb) in cases.into_iter() {
77 assert_eq!(hsl.into_rgb(), rgb);
78 }
79}