1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//! This module implements the CIELCHuv color space, a cylindrical transformation of the
//! CIELUV space, akin to the relationship between CIELAB and CIELCH.

use super::cieluvcolor::CIELUVColor;
use color::{Color, XYZColor};
use coord::Coord;
use illuminants::Illuminant;

/// The polar version of CIELUV, analogous to the relationship between CIELCH and CIELAB. Sometimes
/// referred to as CIEHCL, but Scarlet uses CIELCHuv to be explicit and avoid any confusion, as well
/// as keep consistency: in every hue-saturation-value color space or any variation of it, the
/// coordinates are listed in the exact same order.
/// # Example
///
/// ```
/// # use scarlet::prelude::*;
/// # use scarlet::colors::CIELCHuvColor;
/// // hue-shift red to yellow, keeping same brightness: really ends up to be brown
/// let red = RGBColor{r: 0.7, g: 0.1, b: 0.1};
/// let red_lch: CIELCHuvColor = red.convert();
/// let mut yellow = red_lch;
/// yellow.h = yellow.h + 60.;
/// println!("{}", red.to_string());
/// println!("{}", yellow.convert::<RGBColor>().to_string());
/// println!("{:?}", yellow);
/// // prints #B31A1A
/// //        #7B5A00
/// ```
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub struct CIELCHuvColor {
    /// The luminance component. Exactly the same as CIELAB, CIELUV, and CIELCH. Varies between 0 and
    /// 100 by definition.
    pub l: f64,
    /// The chroma component: essentially, how colorful the color is compared to white. (This is
    /// contrasted with saturation, which is how colorful a color is when compared to an equivalently
    /// bright grayscale color: a dark, deep red may have high saturation and low chroma.) This varies
    /// between 0 and about 141 for most visible colors, and is the radius in cylindrical coordinates.
    pub c: f64,
    /// The hue component: essentially, what wavelengths of light have the highest reflectance. This
    /// is the angle from the vertical axis in cylindrical coordinates. 0 degrees corresponds to red,
    /// 90 to yellow, 180 to green, and 270 to blue. (These are called *unique hues.*) It ranges from
    /// 0 to 360, and any value outside that range will be interpreted as its value if one added or
    /// subtracted multiples of 360 to bring the value inside that range.
    pub h: f64,
}

impl Color for CIELCHuvColor {
    /// Converts from XYZ to CIELCHuv through CIELUV.
    fn from_xyz(xyz: XYZColor) -> CIELCHuvColor {
        // get cieluv color
        let luv = CIELUVColor::from_xyz(xyz);

        // compute c and h using f64 methods
        let unbounded_h = luv.v.atan2(luv.u).to_degrees();
        // fix h within 0-360
        let h = if unbounded_h < 0.0 {
            unbounded_h + 360.0
        } else if unbounded_h > 360.0 {
            unbounded_h - 360.0
        } else {
            unbounded_h
        };

        let c = luv.v.hypot(luv.u);
        CIELCHuvColor { l: luv.l, c, h }
    }
    /// Gets the XYZ color that corresponds to this one, through CIELUV.
    fn to_xyz(&self, illuminant: Illuminant) -> XYZColor {
        // go through CIELUV
        let rad_h = self.h.to_radians();
        let u = self.c * rad_h.cos();
        let v = self.c * rad_h.sin();
        CIELUVColor { l: self.l, u, v }.to_xyz(illuminant)
    }
}

impl From<Coord> for CIELCHuvColor {
    fn from(c: Coord) -> CIELCHuvColor {
        CIELCHuvColor {
            l: c.x,
            c: c.y,
            h: c.z,
        }
    }
}

impl From<CIELCHuvColor> for Coord {
    fn from(val: CIELCHuvColor) -> Self {
        Coord {
            x: val.l,
            y: val.c,
            z: val.h,
        }
    }
}

#[cfg(test)]
mod tests {
    #[allow(unused_imports)]
    use super::*;
    use consts::TEST_PRECISION;

    #[test]
    fn test_cielchuv_xyz_conversion_d50() {
        let xyz = XYZColor {
            x: 0.4,
            y: 0.6,
            z: 0.2,
            illuminant: Illuminant::D50,
        };
        let lchuv: CIELCHuvColor = xyz.convert();
        let xyz2: XYZColor = lchuv.convert();
        assert!(xyz.approx_visually_equal(&xyz2));
        assert!(xyz.distance(&xyz2) <= TEST_PRECISION);
    }

    #[test]
    fn test_cielchuv_xyz_conversion_d65() {
        let xyz = XYZColor {
            x: 0.4,
            y: 0.6,
            z: 0.2,
            illuminant: Illuminant::D65,
        };
        let lchuv: CIELCHuvColor = xyz.convert();
        let xyz2: XYZColor = lchuv.convert();
        assert!(xyz.approx_visually_equal(&xyz2));
        assert!(xyz.distance(&xyz2) <= TEST_PRECISION);
    }
}