color_operators/hsl/mod.rs
1#!/usr/bin/env rust
2
3
4use std::fmt;
5use std::fmt::{Display, Formatter};
6
7
8use crate::rgb::RGB;
9
10
11/// Adds color components for `HSL` data structures
12mod add;
13
14/// Subtracts color components for `HSL` data structures
15mod subtract;
16
17/// Equal and not-equal checks for `HSL` data structures
18mod equality;
19
20/// Converts from/to array for `HSL` data structures
21mod convert_array;
22
23/// Converts from/to `Color` enum
24mod convert_color;
25
26/// Converts from/to `JsonValue` for `RGB` data structures
27mod convert_json_value;
28
29/// Converts `HSV` to `HSL` data structure
30mod convert_hsv;
31
32/// Converts `RGB` to `HSL` data structure
33mod convert_rgb;
34
35/// Converts from/to tuple for `HSL` data structures
36mod convert_tuple;
37
38/// Converts from/to vector for `HSL` data structures
39mod convert_vector;
40
41
42/// Data structure for Hue, Saturation, Lightness encoded colors
43#[derive(Clone, Debug, Default)]
44pub struct HSL {
45 hue: f64,
46 saturation: f64,
47 lightness: f64,
48}
49
50
51impl HSL {
52 /// Returns new instance of `HSL` data structure
53 ///
54 /// # Example
55 ///
56 /// ```rust
57 /// use color_operators::hsl::HSL;
58 ///
59 /// let color = HSL::new(60.0, 1.0, 0.5);
60 ///
61 /// assert_eq!(color.get("hue"), Ok(60.0));
62 /// assert_eq!(color.get("saturation"), Ok(1.0));
63 /// assert_eq!(color.get("lightness"), Ok(0.5));
64 /// ```
65 pub fn new<T>(hue: T, saturation: T, lightness: T) -> Self
66 where
67 T: Into<f64>
68 {
69 let hue: f64 = hue.into().min(360.0).max(0.0);
70 let saturation: f64 = saturation.into().min(1.0).max(0.0);
71 let lightness: f64 = lightness.into().min(1.0).max(0.0);
72 Self { hue, saturation, lightness }
73 }
74
75 /// Returns named component value or error
76 ///
77 /// ```rust
78 /// use color_operators::hsl::HSL;
79 ///
80 /// let color = HSL::new(60.0, 1.0, 0.5);
81 ///
82 /// assert_eq!(color.get("nothing"), Err("No color component named -> nothing".to_string()));
83 /// ```
84 pub fn get<S>(&self, component: S) -> Result<f64, String>
85 where
86 S: Into<String>
87 {
88 let component: String = component.into();
89 match component.as_str() {
90 "hue" => Ok(self.hue),
91 "saturation" => Ok(self.saturation),
92 "lightness" => Ok(self.lightness),
93 _ => Err(format!("No color component named -> {}", component)),
94 }
95 }
96
97 /// Returns parsed JSON string for color key/value pares, or defaults values
98 ///
99 /// # Example
100 ///
101 /// ```rust
102 /// use color_operators::hsl::HSL;
103 ///
104 /// let hsl = HSL::from_json_string(r#"{ "hue": 60.0, "saturation": 1.0, "lightness": 0.5 }"#);
105 ///
106 /// assert_eq!(hsl.get("hue"), Ok(60.0));
107 /// assert_eq!(hsl.get("saturation"), Ok(1.0));
108 /// assert_eq!(hsl.get("lightness"), Ok(0.5));
109 /// ```
110 pub fn from_json_string<S>(string: S) -> Self
111 where
112 S: Into<String>
113 {
114 match json::parse(&string.into()) {
115 Ok(data) => Self::from(data),
116 Err(e) => {
117 println!("Warning: ignoring error -> {:?}", e);
118 Self { hue: 0.0, saturation: 0.0, lightness: 0.0 }
119 }
120 }
121 }
122
123 /// Serializes data structure as JSON string
124 ///
125 /// # Example
126 ///
127 /// ```rust
128 /// use color_operators::hsl::HSL;
129 ///
130 /// let hsl = HSL::new(60.0, 1.0, 0.5);
131 /// let data = hsl.to_json_string();
132 ///
133 /// let object = json::object!{
134 /// "hue" => 60.0,
135 /// "saturation" => 1.0,
136 /// "lightness" => 0.5
137 /// };
138 ///
139 /// let expected = json::stringify(object);
140 ///
141 /// assert_eq!(data, expected);
142 /// ```
143 pub fn to_json_string(&self) -> String {
144 json::stringify(self.clone())
145 }
146
147 /// Converts hexadecimal string into `HSL`
148 ///
149 /// **Warning** this method uses `RGB::from_hex_string` and may panic
150 pub fn from_hex_string<S>(input: S) -> Self
151 where
152 S: Into<String>
153 {
154 Self::from(RGB::from_hex_string(input))
155 }
156
157 /// Returns hexadecimal string representation of `HSL` values
158 ///
159 /// **Note** this method uses `RGB::to_hex_string` internally
160 pub fn to_hex_string(&self) -> String {
161 RGB::from(self.clone()).to_hex_string()
162 }
163
164 /// Attempts to rotate hue by some amount of degrees
165 ///
166 /// # Examples
167 ///
168 /// Positive values rotates hue clockwise
169 ///
170 /// ```rust
171 /// use color_operators::hsl::HSL;
172 ///
173 /// let color = HSL::new(120.0, 0.5, 1.0);
174 /// let clockwise = color.rotate_hue(90.0);
175 ///
176 /// let expected = HSL::new(210.0, 0.5, 1.0);
177 ///
178 /// assert_eq!(clockwise, expected);
179 /// ```
180 ///
181 /// Negative values rotates hue anticlockwise
182 ///
183 /// ```rust
184 /// use color_operators::hsl::HSL;
185 ///
186 /// let color = HSL::new(120.0, 0.5, 1.0);
187 /// let anticlockwise = color.rotate_hue(-180.0);
188 ///
189 /// let expected = HSL::new(300.0, 0.5, 1.0);
190 ///
191 /// assert_eq!(anticlockwise, expected);
192 /// ```
193 pub fn rotate_hue<T>(&self, amount: T) -> Self
194 where
195 T: Into<f64>
196 {
197 let mut amount: f64 = amount.into();
198 let ( mut hue, saturation, lightness ) = self.clone().into();
199
200 let max = 360.0;
201 if amount < 0.0 {
202 amount += max;
203 }
204
205 let sum = hue + amount;
206 if sum > max {
207 hue = (sum - (max * (sum / max).round())).abs();
208 } else {
209 hue = sum;
210 }
211
212 Self { hue, saturation, lightness }
213 }
214
215 /// Attempts to rotate this color with another `HSL` value
216 ///
217 /// **Note** this method uses `RGB::rotate_rgb` internally
218 pub fn rotate_rgb(&self, other: Self) -> Self {
219 let rgb_left: RGB = self.clone().into();
220 let rgb_right: RGB = other.into();
221 let sum = rgb_left.rotate_rgb(rgb_right);
222 Self::from(sum)
223 }
224}
225
226
227impl Display for HSL {
228 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
229 write!(f, "hue: {}, saturation: {}, lightness: {}", self.hue, self.saturation, self.lightness)
230 }
231}
232