primitives/foundation/colorspace/
hsl.rs1use super::{utils, Color, ColorError, Float};
2use std::fmt;
3
4#[derive(Clone, Copy, PartialEq, Debug)]
6pub struct HslColor {
7 pub hue: Float,
9 pub saturation: Float,
11 pub lightness: Float,
13}
14
15impl HslColor {
16 pub fn new(h: Float, s: Float, l: Float) -> Self {
18 Self {
19 hue: h % 360.0,
20 saturation: if s > 100.0 { 100.0 } else { s },
21 lightness: if s > 100.0 { 100.0 } else { l },
22 }
23 }
24}
25
26impl fmt::Display for HslColor {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 write!(
29 f,
30 "hsl({}°, {}%, {}%)",
31 self.hue, self.saturation, self.lightness
32 )
33 }
34}
35
36impl From<HslColor> for Color {
38 fn from(hsl: HslColor) -> Self {
39 let HslColor {
40 hue,
41 saturation,
42 lightness,
43 } = hsl;
44 let c = (1. - ((2. * (lightness as Float / 100.)) - 1.).abs()) * (saturation as Float / 100.);
45 let x = c * (1. - ((((hue as Float) / 60.) % 2.) - 1.).abs());
46 let m = (lightness as Float / 100.) - (c / 2.);
47
48 let (r_prime, g_prime, b_prime) = {
49 if (0.0..60.0).contains(&hue) {
50 (c, x, 0.)
51 } else if (60.0..120.0).contains(&hue) {
52 (x, c, 0.)
53 } else if (120.0..180.0).contains(&hue) {
54 (0., c, x)
55 } else if (180.0..240.0).contains(&hue) {
56 (0., x, c)
57 } else if (240.0..300.0).contains(&hue) {
58 (x, 0., c)
59 } else if (300.0..360.0).contains(&hue) {
60 (c, 0., x)
61 } else {
62 unreachable!("{}", ColorError::DegreeOverflow)
63 }
64 };
65 Color {
66 red: r_prime + m,
67 green: g_prime + m,
68 blue: b_prime + m,
69 alpha: 1.,
70 }
71 }
72}
73
74impl From<Color> for HslColor {
76 fn from(rgb: Color) -> Self {
77 let Color {
78 red,
79 green,
80 blue,
81 alpha: _,
82 } = rgb;
83
84 let (c_min, c_max) = utils::min_max_tuple([red, green, blue].iter());
85 let delta = c_max - c_min;
86
87 let hue = if (delta - 0.).abs() < Float::EPSILON {
88 0.
89 } else {
90 match c_max {
91 x if (x - red).abs() < Float::MIN_POSITIVE => 60. * (((green - blue) / delta) % 6.),
92 x if (x - green).abs() < Float::MIN_POSITIVE => 60. * (((blue - red) / delta) + 2.),
93 x if (x - blue).abs() < Float::MIN_POSITIVE => 60. * (((red - green) / delta) + 4.),
94 _ => unreachable!("Invalid hue calculation!"),
95 }
96 };
97
98 let lightness = (c_max + c_min) / 2.;
99
100 let saturation = if (delta - 0.).abs() < Float::EPSILON {
101 0.
102 } else {
103 delta / (1. - ((2. * lightness) - 1.).abs()) * 100.
104 };
105
106 HslColor {
107 hue,
108 saturation,
109 lightness: lightness * 100.,
110 }
111 }
112}
113
114#[cfg(test)]
115mod test {
116 use super::super::test_utils;
117 use super::super::*;
118
119 #[test]
120 fn to_rgb() {
121 test_utils::test_to_rgb_conversion(test_utils::RGB_HSL.iter())
122 }
123
124 #[test]
125 fn rgb_to_hsv() {
126 test_utils::test_conversion(test_utils::RGB_HSL.iter(), |actual_color, expected_hsl| {
127 let actual_rgb: RgbColor = (*actual_color).into();
128 let actual_hsl: HslColor = actual_rgb.into();
129 let HslColor {
130 hue: actual_h,
131 saturation: actual_s,
132 lightness: actual_l,
133 } = actual_hsl;
134 let (actual_h, actual_s, actual_l) =
135 (actual_h.round(), actual_s.round(), actual_l.round());
136 let HslColor {
137 hue: expected_h,
138 saturation: expected_s,
139 lightness: expected_l,
140 } = *expected_hsl;
141 assert!(
142 test_utils::diff_less_than_f64(actual_h, expected_h, 1.),
143 "wrong hue: {} -> {} != {}",
144 actual_rgb,
145 actual_hsl,
146 expected_hsl
147 );
148 assert!(
149 test_utils::diff_less_than_f64(actual_s, expected_s, 1.),
150 "wrong saturation: {} -> {} != {}",
151 actual_rgb,
152 actual_hsl,
153 expected_hsl
154 );
155 assert!(
156 test_utils::diff_less_than_f64(actual_l, expected_l, 1.),
157 "wrong lightness: {} -> {} != {}",
158 actual_rgb,
159 actual_hsl,
160 expected_hsl
161 );
162 })
163 }
164}