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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
//! The CIE XYZ device-independent color space

use crate::channel::{
    ChannelCast, ChannelFormatCast, ColorChannel, FreeChannelScalar, PosFreeChannel,
};
use crate::color::{Bounded, Broadcast, Color, Flatten, FromTuple, HomogeneousColor, Lerp};
use crate::tags::XyzTag;
#[cfg(feature = "approx")]
use approx;
use std::fmt;
use std::mem;
use std::slice;

/// The CIE XYZ device-independent color space
///
/// The XYZ color space was defined by the *International Commission on Illumination* (CIE) in 1931
/// to be able to describe color human vision.
/// It was developed from a series of experiments to map the human perceptual response to light. A
/// set of "color matching" functions were developed, and from these the XYZ space is defined.
/// XYZ forms an authoritative definition of perceived colors, and is thus used widely in many fields
/// where accurate color representation and conversion are needed.
///
/// XYZ is the "parent" device independent color space from which all other color spaces are defined.
/// All device-dependent color spaces are defined by a set of (generally three) primaries defined
/// in XYZ plus a white point defining the viewing conditions. The transformation from RGB to XYZ
/// can be represented in a 3x3 matrix of values, which when multiplied against a vector of `(R, G, B)`
/// produces a vector of `(X, Y, Z)`.
///
/// While XYZ is authoritative, it is not generally the most convenient space to do manipulations in.
/// The Y of XYZ is luminance whereas X and Z are both linearly independent responses to color, but
/// do not map neatly to an observable color. XYZ is also not perceptually uniform.
///
/// For perceptual uniformity, CIE defined two further spaces that are transformations of XYZ:
/// LAV and LUV. These are non-linearly derived from XYZ, and both are approximately perceptually uniform,
/// although with different properties. See [`Lab`](struct.Lab.html) and [`Luv`](struct.Luv.html)
/// for more details on those color spaces.
///
/// XYZ coordinates are not technically bounded in any range, and the visible region of the space is not
/// a simple shape. Many combinations of XYZ will correspond to no representable color and are therefore
/// "imaginary" to humans.
///
/// ## Standard Observer
///
/// XYZ is actually a family of spaces, each constructed from a set of color matching functions. Currently
/// there are two different "standard observers" defined by the CIE, which are the color matching functions
/// obtained from experiments that represent the average human eye response at a given field of view.
/// The $`2^{\circ}`$ standard observer is by far the most widely used, and is defined using only the
/// center-most 2 degrees of vision. SRgb and the majority of other used color spaces are defined
/// against this standard observer. A later $`10^{\circ}`$ standard observer was created representing a
/// larger field of view. These two standard observers differ in their color matching functions by
/// a modest but not insignificant amount, and XYZ using one is not compatible with XYZ using the other.
/// While $`10^{\circ}`$ standard observer is recommended for use in many applications using more
/// than about $`4^{\circ}`$ of
/// vision, the $`2^{\circ}`$ standard observer is still much more widely used in practice.
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct Xyz<T> {
    x: PosFreeChannel<T>,
    y: PosFreeChannel<T>,
    z: PosFreeChannel<T>,
}

impl<T> Xyz<T>
where
    T: FreeChannelScalar,
{
    /// Construct a new `Xyz` instance from `x`, `y` and `z`
    pub fn new(x: T, y: T, z: T) -> Self {
        Xyz {
            x: PosFreeChannel::new(x),
            y: PosFreeChannel::new(y),
            z: PosFreeChannel::new(z),
        }
    }

    impl_color_color_cast_square!(Xyz { x, y, z }, chan_traits = { FreeChannelScalar });

    /// Returns the `X` value
    pub fn x(&self) -> T {
        self.x.0.clone()
    }
    /// Returns the `Y` value
    pub fn y(&self) -> T {
        self.y.0.clone()
    }
    /// Returns the `Z` value
    pub fn z(&self) -> T {
        self.z.0.clone()
    }
    /// Returns a mutable reference to the `X` value
    pub fn x_mut(&mut self) -> &mut T {
        &mut self.x.0
    }
    /// Returns a mutable reference to the `Y` value
    pub fn y_mut(&mut self) -> &mut T {
        &mut self.y.0
    }
    /// Returns a mutable reference to the `Z` value
    pub fn z_mut(&mut self) -> &mut T {
        &mut self.z.0
    }
    /// Set the `X` value
    pub fn set_x(&mut self, val: T) {
        self.x.0 = val;
    }
    /// Set the `Y` value
    pub fn set_y(&mut self, val: T) {
        self.y.0 = val;
    }
    /// Set the `Z` value
    pub fn set_z(&mut self, val: T) {
        self.z.0 = val;
    }
}

impl<T> Color for Xyz<T>
where
    T: FreeChannelScalar,
{
    type Tag = XyzTag;
    type ChannelsTuple = (T, T, T);

    #[inline]
    fn num_channels() -> u32 {
        3
    }
    fn to_tuple(self) -> Self::ChannelsTuple {
        (self.x.0, self.y.0, self.z.0)
    }
}

impl<T> FromTuple for Xyz<T>
where
    T: FreeChannelScalar,
{
    fn from_tuple(values: (T, T, T)) -> Self {
        Xyz::new(values.0, values.1, values.2)
    }
}

impl<T> HomogeneousColor for Xyz<T>
where
    T: FreeChannelScalar,
{
    type ChannelFormat = T;

    impl_color_homogeneous_color_square!(Xyz<T> {x, y, z});
}

impl<T> Broadcast for Xyz<T>
where
    T: FreeChannelScalar,
{
    impl_color_broadcast!(Xyz<T> {x, y, z}, chan=PosFreeChannel);
}

impl<T> Bounded for Xyz<T>
where
    T: FreeChannelScalar,
{
    impl_color_bounded!(Xyz { x, y, z });
}

impl<T> Lerp for Xyz<T>
where
    T: FreeChannelScalar,
    PosFreeChannel<T>: Lerp,
{
    type Position = <PosFreeChannel<T> as Lerp>::Position;
    impl_color_lerp_square!(Xyz { x, y, z });
}

impl<T> Flatten for Xyz<T>
where
    T: FreeChannelScalar,
{
    impl_color_as_slice!(T);
    impl_color_from_slice_square!(Xyz<T> {x:PosFreeChannel - 0, y:PosFreeChannel - 1,
        z:PosFreeChannel - 2});
}
#[cfg(feature = "approx")]
impl<T> approx::AbsDiffEq for Xyz<T>
where
    T: FreeChannelScalar + approx::AbsDiffEq,
    T::Epsilon: Clone,
{
    impl_abs_diff_eq!({x, y, z});
}
#[cfg(feature = "approx")]
impl<T> approx::RelativeEq for Xyz<T>
where
    T: FreeChannelScalar + approx::RelativeEq,
    T::Epsilon: Clone,
{
    impl_rel_eq!({x, y, z});
}
#[cfg(feature = "approx")]
impl<T> approx::UlpsEq for Xyz<T>
where
    T: FreeChannelScalar + approx::UlpsEq,
    T::Epsilon: Clone,
{
    impl_ulps_eq!({x, y, z});
}

impl<T> Default for Xyz<T>
where
    T: FreeChannelScalar,
{
    impl_color_default!(Xyz {
        x: PosFreeChannel,
        y: PosFreeChannel,
        z: PosFreeChannel
    });
}

impl<T> fmt::Display for Xyz<T>
where
    T: FreeChannelScalar + fmt::Display,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "XYZ({}, {}, {})", self.x, self.y, self.z)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use approx::*;

    #[test]
    fn test_construction() {
        let c1 = Xyz::new(0.5, 1.2, 0.9);
        assert_eq!(c1.x(), 0.5);
        assert_eq!(c1.y(), 1.2);
        assert_eq!(c1.z(), 0.9);
        assert_eq!(c1.clone().to_tuple(), (0.5, 1.2, 0.9));
        assert_relative_eq!(Xyz::from_tuple(c1.clone().to_tuple()), c1);

        let c2 = Xyz::new(0.5, -0.4, 0.3);
        assert_eq!(c2.x(), 0.5);
        assert_eq!(c2.y(), -0.4);
        assert_eq!(c2.z(), 0.3);
        assert_eq!(c2.to_tuple(), (0.5, -0.4, 0.3));
        assert_relative_eq!(Xyz::from_tuple(c2.clone().to_tuple()), c2);

        let c3 = Xyz::broadcast(1.1);
        assert_eq!(c3.x(), c3.y());
        assert_eq!(c3.y(), c3.z());
        assert_eq!(c3.to_tuple(), (1.1, 1.1, 1.1));
        assert_relative_eq!(Xyz::from_tuple(c3.clone().to_tuple()), c3);
    }

    #[test]
    fn test_lerp() {
        let c1 = Xyz::new(0.8, 0.2, 1.5);
        let c2 = Xyz::new(0.1, 0.7, 0.3);
        assert_relative_eq!(c1.lerp(&c2, 0.0), c1);
        assert_relative_eq!(c1.lerp(&c2, 1.0), c2);
        assert_relative_eq!(c1.lerp(&c2, 0.5), Xyz::new(0.45, 0.45, 0.9));
        assert_relative_eq!(c1.lerp(&c2, 0.25), Xyz::new(0.625, 0.325, 1.2));
    }

    #[test]
    fn test_normalize() {
        let c1 = Xyz::new(1e6, -2e7, 8e-5);
        assert!(!c1.is_normalized());
        assert_relative_eq!(c1.normalize(), Xyz::new(1e6, 0.0, 8e-5));

        let c2 = Xyz::new(1.0, 0.0, 1.0);
        assert!(c2.is_normalized());
        assert_relative_eq!(c2.normalize(), c2);
    }

    #[test]
    fn test_flatten() {
        let c1 = Xyz::new(0.4, 0.7, 1.0);
        assert_eq!(c1.as_slice(), &[0.4, 0.7, 1.0]);
        assert_relative_eq!(Xyz::from_slice(c1.as_slice()), c1);
    }

    #[test]
    fn test_color_cast() {
        let c1 = Xyz::new(0.5, 1.0, 0.8);
        assert_relative_eq!(c1.color_cast(), c1);
        assert_relative_eq!(c1.color_cast(), Xyz::new(0.5f32, 1.0, 0.8));
    }

}