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