primitives/foundation/colorspace/
adjust.rs

1use super::prelude::*;
2
3use super::*;
4
5/// Defines getter for the color hue component
6pub trait GetHue {
7    /// Retrieve hue component
8    fn get_hue(self) -> Float;
9}
10
11/// Defines setter for the color hue component
12pub trait SetHue {
13    /// Set the hue component
14    fn set_hue(&mut self, hue: Float) -> Self;
15}
16
17/// Defines setter and getters for the color hue component
18pub trait HasHue: Clone + Copy + GetHue + SetHue {}
19
20
21/// Defines the color saturation functionality
22pub trait HasSaturation: Clone + Copy {
23    /// Retrieve saturation
24    fn get_saturation(self) -> Float;
25    /// Set the saturation
26    fn set_saturation(&mut self, saturation: Float) -> Self;
27}
28
29/// Defines getters for the chroma radial saturation functionality
30pub trait GetRadialSaturation:
31    FromColor<HslColor> + IntoColor<HslColor> + FromColor<HsvColor> + IntoColor<HsvColor>
32{
33    /// Retrieve hsl saturation
34    fn get_hsl_saturation(self) -> Float;
35    /// Set hsv saturation
36    fn get_hsv_saturation(self) -> Float;
37}
38
39/// Defines setters for the chroma radial saturation functionality
40pub trait SetRadialSaturation:
41    FromColor<HslColor> + IntoColor<HslColor> + FromColor<HsvColor> + IntoColor<HsvColor>
42{
43    /// Retrieve hsl saturation
44    fn set_hsl_saturation(&mut self, saturation: Float) -> Self;
45    /// Set hsv saturation
46    fn set_hsv_saturation(&mut self, saturation: Float) -> Self;
47}
48
49/// Defines the color lighten/darken functionality
50pub trait Lighten: Sized {
51    /// Lighten the color with delta
52    fn lighten(self, delta: Float) -> Self;
53    /// Darken the color with delta
54    fn darken(self, delta: Float) -> Self {
55        self.lighten(-delta)
56    }
57}
58
59/// Defines the color hue adjustment functionality
60pub trait AdjustHue: Sized {
61    /// Adjust hue component of color with delta
62    fn adjust_hue(self, delta: Float) -> Self;
63    /// Rotate hue component with 180 degrees
64    fn complement(self) -> Self {
65        self.adjust_hue(180.)
66    }
67}
68
69/// Defines the color saturation functionality
70pub trait Saturate: Sized {
71    /// Saturate the color with delta
72    fn saturate(self, delta: Float) -> Self;
73    /// Desaturate the color with delta
74    fn desaturate(self, delta: Float) -> Self {
75        self.saturate(-delta)
76    }
77}
78
79/// Defines the color grayscale adjustment functionality
80pub trait Grayscale: Saturate {
81    /// Desaturate the color to grayscale
82    fn grayscale(self) -> Self {
83        self.saturate(-100.)
84    }
85}
86impl<C: Saturate> Grayscale for C {}
87
88/// Defines the color inversion functionality
89pub trait Invert: Sized {
90    /// Invert color
91    fn invert(self) -> Self;
92}
93
94/// Defines the color adjustment functionality
95pub trait Adjust: Lighten + AdjustHue + Saturate + Grayscale {}
96impl<C: Lighten + AdjustHue + Saturate + Grayscale> Adjust for C {}
97
98impl GetHue for HslColor {
99    fn get_hue(self) -> Float {
100        self.hue
101    }
102}
103impl SetHue for HslColor {
104    fn set_hue(&mut self, hue: Float) -> Self {
105        self.hue = hue_bound(hue);
106        *self
107    }
108}
109impl GetHue for HsvColor {
110    fn get_hue(self) -> Float {
111        self.hue
112    }
113}
114impl SetHue for HsvColor {
115    fn set_hue(&mut self, hue: Float) -> Self {
116        self.hue = hue_bound(hue);
117        *self
118    }
119}
120
121impl HasHue for HslColor {}
122impl HasHue for HsvColor {}
123
124impl HasSaturation for HslColor {
125    fn get_saturation(self) -> Float {
126        self.saturation
127    }
128    fn set_saturation(&mut self, saturation: Float) -> Self {
129        self.saturation = clamp(saturation, 0., 100.);
130        *self
131    }
132}
133impl HasSaturation for HsvColor {
134    fn get_saturation(self) -> Float {
135        self.saturation
136    }
137    fn set_saturation(&mut self, saturation: Float) -> Self {
138        self.saturation = clamp(saturation, 0., 100.);
139        *self
140    }
141}
142
143impl<C: NonRadialSpace> GetHue for C {
144    fn get_hue(self) -> Float {
145        let hsv: HsvColor = self.into_color();
146        hsv.get_hue()
147    }
148}
149impl<C: NonRadialSpace> SetHue for C {
150    fn set_hue(&mut self, hue: Float) -> Self {
151        let mut hsv: HsvColor = (*self).into_color();
152        hsv.set_hue(hue);
153        *self = hsv.into_color();
154        *self
155    }
156}
157
158impl<C> GetRadialSaturation for C
159where
160    C: Clone
161        + Copy
162        + FromColor<HslColor>
163        + IntoColor<HslColor>
164        + FromColor<HsvColor>
165        + IntoColor<HsvColor>,
166{
167    fn get_hsl_saturation(self) -> Float {
168        let hsl: HslColor = self.into_color();
169        hsl.get_saturation()
170    }
171    fn get_hsv_saturation(self) -> Float {
172        let hsv: HsvColor = self.into_color();
173        hsv.get_saturation()
174    }
175}
176impl<C> SetRadialSaturation for C
177where
178    C: Clone
179        + Copy
180        + FromColor<HslColor>
181        + IntoColor<HslColor>
182        + FromColor<HsvColor>
183        + IntoColor<HsvColor>,
184{
185    fn set_hsl_saturation(&mut self, saturation: Float) -> Self {
186        let mut hsl: HslColor = (*self).into_color();
187        *self = hsl.set_saturation(saturation).into_color();
188        *self
189    }
190    fn set_hsv_saturation(&mut self, saturation: Float) -> Self {
191        let mut hsv: HsvColor = (*self).into_color();
192        *self = hsv.set_saturation(saturation).into_color();
193        *self
194    }
195}
196
197impl<C: FromColor<HslColor> + IntoColor<HslColor>> Lighten for C {
198    fn lighten(self, delta: Float) -> Self {
199        let hsl: HslColor = self.into_color();
200        let lightness = hsl.lightness + utils::clamp(delta, -100., 100.);
201        C::from_color(HslColor {
202            hue: hsl.hue,
203            saturation: hsl.saturation,
204            lightness: utils::clamp(lightness, 0., 100.),
205        })
206    }
207}
208
209impl<C: Clone + GetHue + SetHue> AdjustHue for C {
210    fn adjust_hue(self, delta: Float) -> Self {
211        self.clone().set_hue(self.get_hue() + delta)
212    }
213}
214
215impl<C: Clone + GetRadialSaturation + SetRadialSaturation> Saturate for C {
216    fn saturate(self, delta: Float) -> Self {
217        self.clone()
218            .set_hsl_saturation(self.get_hsl_saturation() + clamp(delta, -100., 100.))
219    }
220}
221
222impl<C: ColorTransition> Invert for C {
223    fn invert(self) -> Self {
224        let Color {
225            red,
226            green,
227            blue,
228            alpha,
229        } = self.into();
230        C::from(Color {
231            red: 1. - red,
232            green: 1. - green,
233            blue: 1. - blue,
234            alpha,
235        })
236    }
237}
238
239// Implementations for colors with alpha channel
240impl<C: Lighten + ColorSpace> Lighten for Alpha<C> {
241    fn lighten(self, delta: Float) -> Self {
242        let (color, alpha) = self.split();
243        Alpha::new(color.lighten(delta), alpha)
244    }
245}
246impl<C: AdjustHue + ColorSpace> AdjustHue for Alpha<C> {
247    fn adjust_hue(self, delta: Float) -> Self {
248        let (color, alpha) = self.split();
249        Alpha::new(color.adjust_hue(delta), alpha)
250    }
251}
252impl<C: Saturate + ColorSpace> Saturate for Alpha<C> {
253    fn saturate(self, delta: Float) -> Self {
254        let (color, alpha) = self.split();
255        Alpha::new(color.saturate(delta), alpha)
256    }
257}
258impl<C: Invert + ColorSpace> Invert for Alpha<C> {
259    fn invert(self) -> Self {
260        let (color, alpha) = self.split();
261        Alpha::new(color.invert(), alpha)
262    }
263}
264
265#[cfg(test)]
266mod test {
267    use super::super::prelude::*;
268
269    use super::super::*;
270    // use math::round::half_down;
271
272    // [x] make test for all adjustments
273    // [x] add adjustments impl for alpha::Alpha
274    // [x] replace unicolor::Color enum by Color struct with rgba floats (0.0 - 1.0)
275    // [ ] add tests of adjustments for alpha::Alpha
276    // [ ] add tests of adjustments for Color
277
278    #[test]
279    fn adjust_hue_for_rgb() {
280        let red_rgb = RgbColor::new(255, 0, 0);
281        let green_rgb = red_rgb.adjust_hue(120.);
282        assert_eq!(
283            green_rgb.red, 0,
284            "wrong red cmp of green color: {}",
285            green_rgb
286        );
287        assert_eq!(
288            green_rgb.green, 255,
289            "wrong green cmp of green color: {}",
290            green_rgb
291        );
292        assert_eq!(
293            green_rgb.blue, 0,
294            "wrong blue cmp of green color: {}",
295            green_rgb
296        );
297    }
298
299    #[test]
300    fn adjust_hue_for_hsv() {
301        let red_hsv: HsvColor = RgbColor::new(255, 0, 0).into_color();
302        let green_hsv = red_hsv.adjust_hue(120.);
303        assert_eq!(red_hsv.hue, 0.);
304        assert_eq!(red_hsv.saturation, 100.);
305        assert_eq!(red_hsv.value, 100.);
306        assert_eq!(green_hsv.hue, 120.);
307        assert_eq!(green_hsv.saturation, 100.);
308        assert_eq!(green_hsv.value, 100.);
309        let green_rgb: RgbColor = green_hsv.into();
310        assert_eq!(green_rgb.red, 0, "wrong red: {}", green_rgb);
311        assert_eq!(green_rgb.green, 255, "wrong green: {}", green_rgb);
312        assert_eq!(green_rgb.blue, 0, "wrong blue: {}", green_rgb);
313    }
314
315    // #[test]
316    // fn lighten_for_rgb_color() {
317    //     let base = RgbColor::new(204, 0, 0); // hsl(0, 100, 40)
318    //     let lighten = base.lighten(5.); // rgb(230, 0, 0) <- hsl(0, 100, 45)
319    //     let darken = base.darken(10.); // rgb(153, 0, 0) <- hsl(0, 100, 30)
320    //     assert_eq!(half_down(HslColor::from(lighten).lightness as f64, 0), 45.);
321    //     assert_eq!(half_down(HslColor::from(darken).lightness as f64, 10), 30.);
322    //     assert_eq!(lighten.red, 230);
323    //     assert_eq!(darken.red, 153);
324    // }
325
326    // #[test]
327    // fn lighten_for_lin_rgb() {
328    //     let base: Color = RgbColor::new(204, 0, 0).into(); // hsl(0, 100, 40)
329    //     let lighten = base.lighten(5.); // rgb(230, 0, 0) <- hsl(0, 100, 45)
330    //     let darken = base.darken(10.); // rgb(153, 0, 0) <- hsl(0, 100, 30)
331    //     assert_eq!(half_down(HslColor::from(lighten).lightness as f64, 0), 45.);
332    //     assert_eq!(half_down(HslColor::from(darken).lightness as f64, 10), 30.);
333    //     assert_eq!(RgbColor::from(lighten).red, 230);
334    //     assert_eq!(RgbColor::from(darken).red, 153);
335    // }
336
337    #[test]
338    fn saturate() {
339        let base = RgbColor::new(127, 63, 191); // hsl(270, 50, 50)
340        let saturated = base.saturate(20.); // rgb(127, 38, 216) <- hsl(270, 70, 50)
341        let desaturated = base.desaturate(20.); // rgb(127, 89, 165) <- hsl(270, 30, 50)
342        assert_eq!(base.get_hsl_saturation().round(), 50.);
343        assert_eq!(saturated.get_hsl_saturation().round(), 70.);
344        assert_eq!(desaturated.get_hsl_saturation().round(), 31.);
345    }
346
347    #[test]
348    fn grayscale() {
349        let base = RgbColor::new(102, 61, 142); // hsl(270, 40, 40)
350        let grayscale = base.grayscale();
351        assert_eq!(grayscale.red, 102);
352        assert_eq!(grayscale.green, 102);
353        assert_eq!(grayscale.blue, 102);
354    }
355
356    #[test]
357    fn get_saturation() {
358        let color = RgbColor::new(102, 61, 142); // hsl(270, 40, 40), hsv(270, 57, 55)
359        assert_eq!(color.get_hsl_saturation().round(), 40.);
360        assert_eq!(color.get_hsv_saturation().round(), 57.);
361    }
362
363    #[test]
364    fn complement() {
365        let base: CmykColor = HslColor::new(60., 50., 50.).into();
366        assert_eq!(base.get_hue(), 60.);
367        let base_rgb = RgbColor::from(base);
368        assert_eq!(base_rgb.red, 191);
369        assert_eq!(base_rgb.green, 191);
370        assert_eq!(base_rgb.blue, 63);
371
372        let complemented = base.complement();
373        assert_eq!(complemented.get_hue(), 240.);
374        let complemented_rgb: RgbColor = complemented.into();
375        assert_eq!(complemented_rgb.red, 63);
376        assert_eq!(complemented_rgb.green, 63);
377        assert_eq!(complemented_rgb.blue, 191);
378    }
379
380    #[test]
381    fn invert() {
382        let color = RgbColor::new(100, 60, 255);
383        let inverted = color.invert();
384        assert_eq!(inverted.red, 155);
385        assert_eq!(inverted.green, 195);
386        assert_eq!(inverted.blue, 0);
387    }
388}