1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use super::*;

/// [HSLA color](https://en.wikipedia.org/wiki/HSL_and_HSV).
/// Convert into/from [Rgba] via the [From] and [Into] traits.
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Hsla<T: ColorComponent> {
    /// Hue
    pub h: T,
    /// Saturation
    pub s: T,
    /// Lightness
    pub l: T,
    /// Alpha (opacity)
    pub a: T,
}

impl<T: ColorComponent> Hsla<T> {
    /// Construct a new Hsva value
    pub fn new(h: T, s: T, l: T, a: T) -> Self {
        Self { h, s, l, a }
    }

    /// Map all component values using same function
    pub fn map<F: Fn(T) -> U, U: ColorComponent>(self, f: F) -> Hsla<U> {
        Hsla {
            h: f(self.h),
            s: f(self.s),
            l: f(self.l),
            a: f(self.a),
        }
    }

    /// Convert color component type
    pub fn convert<U: ColorComponent>(self) -> Hsla<U> {
        self.map(|x| x.convert())
    }
}

impl<C1: ColorComponent, C2: ColorComponent> From<Hsla<C1>> for Rgba<C2> {
    fn from(hsl: Hsla<C1>) -> Self {
        let Hsla { h, s, l, a } = hsl.convert::<f32>();
        let alpha = s * l.min(1.0 - l);
        let f = |n: f32| {
            let k = n + h * 12.0;
            let k = k - (k / 12.0).floor() * 12.0;
            l - alpha * (-1.0f32).max((k - 3.0).min(9.0 - k).min(1.0))
        };
        Rgba::new(f(0.0), f(8.0), f(4.0), a).convert()
    }
}

impl<C1: ColorComponent, C2: ColorComponent> From<Rgba<C1>> for Hsla<C2> {
    fn from(rgb: Rgba<C1>) -> Self {
        let Rgba { r, g, b, a } = rgb.convert::<f32>();
        let v = r.max(g).max(b); // max = v
        let min = r.min(g).min(b); // = v - c
        let c = v - min; // = 2 * (v - l)
        let l = v - c / 2.0; // = mid(r, g, b)

        let h = if c == 0.0 {
            0.0
        } else if v == r {
            (g - b) / c / 6.0
        } else if v == g {
            (b - r) / c / 6.0 + 1.0 / 3.0
        } else {
            // if v == b {
            (r - g) / c / 6.0 + 2.0 / 3.0
        };

        // let s = if v == 0.0 { 0.0 } else { c / v };
        let s = if l == 0.0 || l == 1.0 {
            0.0
        } else {
            (v - l) / l.min(1.0 - l)
        };

        Hsla::new(h, s, l, a).convert()
    }
}

impl<T: ColorComponent + Approx> Approx for Hsla<T> {
    fn approx_distance_to(&self, other: &Self) -> f32 {
        (self.h.approx_distance_to(&other.h)
            + self.s.approx_distance_to(&other.s)
            + self.l.approx_distance_to(&other.l)
            + self.a.approx_distance_to(&other.a))
            / 4.0
    }
}

#[test]
fn test_conversion() {
    let tests = [
        ([1.0, 1.0, 1.0], [0.0, 0.0, 1.0]),
        ([0.75, 1.0, 1.0], [0.5, 1.0, 7.0 / 8.0]),
        ([0.438, 0.438, 0.812], [2.0 / 3.0, 0.5, 5.0 / 8.0]),
    ];

    fn eq<T: Approx>(a: impl Into<T>, b: impl Into<T>) -> bool {
        let a = a.into();
        let b = b.into();
        a.approx_eq_eps(&b, 0.1) // TODO: this EPS seems very big
    }

    for ([r, g, b], [h, s, l]) in tests {
        let rgb = Rgba::new(r, g, b, 1.0);
        let hsl = Hsla::new(h, s, l, 1.0);
        assert!(
            eq::<Hsla<f32>>(rgb, hsl),
            "{rgb:?} != {hsl:?} (when converting both to hsl)",
        );
        assert!(
            eq::<Rgba<f32>>(rgb, hsl),
            "{rgb:?} != {hsl:?} (when converting both to rgb)",
        );
    }
}