1use crate::{LinearRgb, Srgb, ToAlpha, round_dp};
2use core::fmt;
3
4#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct Hsl {
12 pub hue: f32,
13 pub saturation: f32,
14 pub lightness: f32,
15 pub alpha: f32,
16}
17
18impl Hsl {
19 pub fn new(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Self {
20 Self { hue: hue.rem_euclid(360.0), saturation, lightness, alpha: alpha.clamp(0.0, 100.0) }
21 }
22}
23
24impl ToAlpha for Hsl {
25 fn to_alpha(&self) -> f32 {
26 self.alpha
27 }
28}
29
30impl fmt::Display for Hsl {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 let Self { hue, saturation, lightness, alpha } = self;
33 write!(
34 f,
35 "hsl({} {}% {}%",
36 round_dp(*hue as f64, 2),
37 round_dp(*saturation as f64, 2),
38 round_dp(*lightness as f64, 2)
39 )?;
40 if *alpha < 100.0 {
41 write!(f, " / {}", round_dp(*alpha as f64, 2))?;
42 }
43 write!(f, ")")
44 }
45}
46
47fn gamma(u: f64) -> f64 {
49 let abs = u.abs();
50 if abs <= 0.0031308 { u * 12.92 } else { u.signum() * (1.055 * abs.powf(1.0 / 2.4) - 0.055) }
51}
52
53fn linear(c: f64) -> f64 {
55 let abs = c.abs();
56 if abs > 0.04045 { c.signum() * ((abs + 0.055) / 1.055).powf(2.4) } else { c / 12.92 }
57}
58
59fn srgb_float_to_hsl(r: f64, g: f64, b: f64) -> (f64, f64, f64) {
62 let max = r.max(g).max(b);
63 let min = r.min(g).min(b);
64 let delta = max - min;
65 let lightness = (max + min) / 2.0;
66 let saturation = if delta == 0.0 { 0.0 } else { delta / (1.0 - (2.0 * lightness - 1.0).abs()) };
67 let hue = if delta == 0.0 {
68 0.0
69 } else if max == r {
70 60.0 * (((g - b) / delta) % 6.0)
71 } else if max == g {
72 60.0 * ((b - r) / delta + 2.0)
73 } else {
74 60.0 * ((r - g) / delta + 4.0)
75 };
76 let hue = if hue < 0.0 { hue + 360.0 } else { hue };
77 (hue, saturation * 100.0, lightness * 100.0)
78}
79
80fn hsl_to_srgb_float(hue: f64, saturation: f64, lightness: f64) -> (f64, f64, f64) {
82 let h = hue / 60.0;
83 let s = saturation / 100.0;
84 let l = lightness / 100.0;
85 let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
86 let x = c * (1.0 - (h % 2.0 - 1.0).abs());
87 let m = l - c / 2.0;
88 let (r_prime, g_prime, b_prime) = if h < 1.0 {
89 (c, x, 0.0)
90 } else if h < 2.0 {
91 (x, c, 0.0)
92 } else if h < 3.0 {
93 (0.0, c, x)
94 } else if h < 4.0 {
95 (0.0, x, c)
96 } else if h < 5.0 {
97 (x, 0.0, c)
98 } else {
99 (c, 0.0, x)
100 };
101 (r_prime + m, g_prime + m, b_prime + m)
102}
103
104impl From<Srgb> for Hsl {
105 fn from(value: Srgb) -> Self {
106 let r = value.red as f64 / 255.0;
107 let g = value.green as f64 / 255.0;
108 let b = value.blue as f64 / 255.0;
109 let (hue, saturation, lightness) = srgb_float_to_hsl(r, g, b);
110 Hsl::new(hue as f32, saturation as f32, lightness as f32, value.alpha)
111 }
112}
113
114impl From<Hsl> for Srgb {
115 fn from(value: Hsl) -> Self {
116 let (r, g, b) = hsl_to_srgb_float(value.hue as f64, value.saturation as f64, value.lightness as f64);
117 Srgb::new(
118 (r * 255.0).clamp(0.0, 255.0).round() as u8,
119 (g * 255.0).clamp(0.0, 255.0).round() as u8,
120 (b * 255.0).clamp(0.0, 255.0).round() as u8,
121 value.alpha,
122 )
123 }
124}
125
126impl From<LinearRgb> for Hsl {
127 fn from(value: LinearRgb) -> Self {
128 let r = gamma(value.red);
129 let g = gamma(value.green);
130 let b = gamma(value.blue);
131 let (hue, saturation, lightness) = srgb_float_to_hsl(r, g, b);
132 Hsl::new(hue as f32, saturation as f32, lightness as f32, value.alpha)
133 }
134}
135
136impl From<Hsl> for LinearRgb {
137 fn from(value: Hsl) -> Self {
138 let (r, g, b) = hsl_to_srgb_float(value.hue as f64, value.saturation as f64, value.lightness as f64);
139 LinearRgb::new(linear(r), linear(g), linear(b), value.alpha)
140 }
141}