1use crate::spaces::{Rgb, Xyz65};
13use crate::traits::ColorSpace;
14
15#[derive(Debug, Clone, Copy, PartialEq)]
18pub struct Hsl {
19 pub h: f64,
21 pub s: f64,
23 pub l: f64,
25 pub alpha: Option<f64>,
27}
28
29#[inline]
30fn normalize_hue(h: f64) -> f64 {
31 let h = h % 360.0;
32 if h < 0.0 {
33 h + 360.0
34 } else {
35 h
36 }
37}
38
39impl ColorSpace for Hsl {
40 const MODE: &'static str = "hsl";
41 const CHANNELS: &'static [&'static str] = &["h", "s", "l"];
42
43 fn alpha(&self) -> Option<f64> {
44 self.alpha
45 }
46
47 fn with_alpha(self, alpha: Option<f64>) -> Self {
48 Self { alpha, ..self }
49 }
50
51 fn to_xyz65(&self) -> Xyz65 {
52 Rgb::from(*self).to_xyz65()
53 }
54
55 fn from_xyz65(xyz: Xyz65) -> Self {
56 Rgb::from_xyz65(xyz).into()
57 }
58}
59
60impl From<Rgb> for Hsl {
61 fn from(c: Rgb) -> Self {
62 let Rgb { r, g, b, alpha } = c;
63 let max = r.max(g).max(b);
64 let min = r.min(g).min(b);
65 let l = 0.5 * (max + min);
66 let s = if max == min {
67 0.0
68 } else {
69 (max - min) / (1.0 - (max + min - 1.0).abs())
70 };
71 let h = if max == min {
72 f64::NAN
73 } else if max == r {
74 let mut h = (g - b) / (max - min);
75 if g < b {
76 h += 6.0;
77 }
78 h * 60.0
79 } else if max == g {
80 ((b - r) / (max - min) + 2.0) * 60.0
81 } else {
82 ((r - g) / (max - min) + 4.0) * 60.0
83 };
84 Self { h, s, l, alpha }
85 }
86}
87
88impl From<Hsl> for Rgb {
89 fn from(c: Hsl) -> Self {
90 let h_in = if c.h.is_nan() { 0.0 } else { c.h };
93 let h = normalize_hue(h_in);
94 let s = c.s;
95 let l = c.l;
96 let m1 = l + s * (if l < 0.5 { l } else { 1.0 - l });
97 let m2 = m1 - (m1 - l) * 2.0 * (((h / 60.0) % 2.0) - 1.0).abs();
98 let (r, g, b) = match (h / 60.0).floor() as i32 {
99 0 => (m1, m2, 2.0 * l - m1),
100 1 => (m2, m1, 2.0 * l - m1),
101 2 => (2.0 * l - m1, m1, m2),
102 3 => (2.0 * l - m1, m2, m1),
103 4 => (m2, 2.0 * l - m1, m1),
104 5 => (m1, 2.0 * l - m1, m2),
105 _ => (2.0 * l - m1, 2.0 * l - m1, 2.0 * l - m1),
106 };
107 Self {
108 r,
109 g,
110 b,
111 alpha: c.alpha,
112 }
113 }
114}