1 // Written in the D programming language.
2 
3 /**
4 This module implements $(LINK2 https://en.wikipedia.org/wiki/CIE_1931_color_space, CIE XYZ) and
5 $(LINK2 https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space, xyY)
6 _color types.
7 
8 These _color spaces represent the simplest expression of the full-spectrum of human visible _color.
9 No attempts are made to support perceptual uniformity, or meaningful _color blending within these _color spaces.
10 They are most useful as an absolute representation of human visible colors, and a centre point for _color space
11 conversions.
12 
13 Authors:    Manu Evans
14 Copyright:  Copyright (c) 2015, Manu Evans.
15 License:    $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0)
16 Source:     $(PHOBOSSRC std/experimental/color/_xyz.d)
17 */
18 module std.experimental.color.xyz;
19 
20 import std.experimental.color;
21 version(unittest)
22     import std.experimental.color.colorspace : WhitePoint;
23 
24 import std.traits : isInstanceOf, isFloatingPoint, Unqual;
25 import std.typetuple : TypeTuple;
26 import std.typecons : tuple;
27 
28 @safe pure nothrow @nogc:
29 
30 
31 /**
32 Detect whether $(D_INLINECODE T) is an XYZ color.
33 */
34 enum isXYZ(T) = isInstanceOf!(XYZ, T);
35 
36 ///
37 unittest
38 {
39     static assert(isXYZ!(XYZ!float) == true);
40     static assert(isXYZ!(xyY!double) == false);
41     static assert(isXYZ!int == false);
42 }
43 
44 
45 /**
46 Detect whether $(D_INLINECODE T) is an xyY color.
47 */
48 enum isxyY(T) = isInstanceOf!(xyY, T);
49 
50 ///
51 unittest
52 {
53     static assert(isxyY!(xyY!float) == true);
54     static assert(isxyY!(XYZ!double) == false);
55     static assert(isxyY!int == false);
56 }
57 
58 
59 /**
60 A CIE 1931 XYZ color, parameterised for component type.
61 */
62 struct XYZ(F = float) if (isFloatingPoint!F)
63 {
64 @safe pure nothrow @nogc:
65 
66     /** Type of the color components. */
67     alias ComponentType = F;
68 
69     /** X value. */
70     F X = 0;
71     /** Y value. */
72     F Y = 0;
73     /** Z value. */
74     F Z = 0;
75 
76     /** Return the XYZ tristimulus values as a tuple. */
77     @property auto tristimulus() const
78     {
79         return tuple(X, Y, Z);
80     }
81 
82     /** Construct a color from XYZ values. */
83     this(ComponentType X, ComponentType Y, ComponentType Z)
84     {
85         this.X = X;
86         this.Y = Y;
87         this.Z = Z;
88     }
89 
90     /**
91     Cast to other color types.
92 
93     This cast is a convenience which simply forwards the call to convertColor.
94     */
95     Color opCast(Color)() const if (isColor!Color)
96     {
97         return convertColor!Color(this);
98     }
99 
100     /** Unary operators. */
101     typeof(this) opUnary(string op)() const if (op == "+" || op == "-" || (op == "~" && is(ComponentType == NormalizedInt!U, U)))
102     {
103         Unqual!(typeof(this)) res = this;
104         foreach (c; AllComponents)
105             mixin(ComponentExpression!("res._ = #_;", c, op));
106         return res;
107     }
108     /** Binary operators. */
109     typeof(this) opBinary(string op)(typeof(this) rh) const if (op == "+" || op == "-" || op == "*")
110     {
111         Unqual!(typeof(this)) res = this;
112         foreach (c; AllComponents)
113             mixin(ComponentExpression!("res._ #= rh._;", c, op));
114         return res;
115     }
116     /** Binary operators. */
117     typeof(this) opBinary(string op, S)(S rh) const if (isColorScalarType!S && (op == "*" || op == "/" || op == "%" || op == "^^"))
118     {
119         Unqual!(typeof(this)) res = this;
120         foreach (c; AllComponents)
121             mixin(ComponentExpression!("res._ #= rh;", c, op));
122         return res;
123     }
124     /** Binary assignment operators. */
125     ref typeof(this) opOpAssign(string op)(typeof(this) rh) if (op == "+" || op == "-" || op == "*")
126     {
127         foreach (c; AllComponents)
128             mixin(ComponentExpression!("_ #= rh._;", c, op));
129         return this;
130     }
131     /** Binary assignment operators. */
132     ref typeof(this) opOpAssign(string op, S)(S rh) if (isColorScalarType!S && (op == "*" || op == "/" || op == "%" || op == "^^"))
133     {
134         foreach (c; AllComponents)
135             mixin(ComponentExpression!("_ #= rh;", c, op));
136         return this;
137     }
138 
139 
140 package:
141 
142     static To convertColorImpl(To, From)(From color) if (isXYZ!From && isXYZ!To)
143     {
144         alias F = To.ComponentType;
145         return To(F(color.X), F(color.Y), F(color.Z));
146     }
147     unittest
148     {
149         static assert(convertColorImpl!(XYZ!float)(XYZ!double(1, 2, 3)) == XYZ!float(1, 2, 3));
150         static assert(convertColorImpl!(XYZ!double)(XYZ!float(1, 2, 3)) == XYZ!double(1, 2, 3));
151     }
152 
153 private:
154     alias AllComponents = TypeTuple!("X", "Y", "Z");
155 }
156 
157 ///
158 unittest
159 {
160     // CIE XYZ 1931 color with float components
161     alias XYZf = XYZ!float;
162 
163     XYZf c = XYZf(0.8, 1, 1.2);
164 
165     // tristimulus() returns a tuple of the components
166     assert(c.tristimulus == tuple(c.X, c.Y, c.Z));
167 
168     // test XYZ operators and functions
169     static assert(XYZf(0, 0.5, 0) + XYZf(0.5, 0.5, 1) == XYZf(0.5, 1, 1));
170     static assert(XYZf(0.5, 0.5, 1) * 100.0 == XYZf(50, 50, 100));
171 }
172 
173 
174 /**
175 A CIE 1931 xyY color, parameterised for component type.
176 */
177 struct xyY(F = float) if (isFloatingPoint!F)
178 {
179 @safe pure nothrow @nogc:
180 
181     /** Type of the color components. */
182     alias ComponentType = F;
183 
184     /** x coordinate. */
185     F x = 0;
186     /** y coordinate. */
187     F y = 0;
188     /** Y value (luminance). */
189     F Y = 0;
190 
191     /** Construct a color from xyY values. */
192     this(ComponentType x, ComponentType y, ComponentType Y)
193     {
194         this.x = x;
195         this.y = y;
196         this.Y = Y;
197     }
198 
199     /**
200     Cast to other color types.
201 
202     This cast is a convenience which simply forwards the call to convertColor.
203     */
204     Color opCast(Color)() const if (isColor!Color)
205     {
206         return convertColor!Color(this);
207     }
208 
209     /** Unary operators. */
210     typeof(this) opUnary(string op)() const if (op == "+" || op == "-" || (op == "~" && is(ComponentType == NormalizedInt!U, U)))
211     {
212         Unqual!(typeof(this)) res = this;
213         foreach (c; AllComponents)
214             mixin(ComponentExpression!("res._ = #_;", c, op));
215         return res;
216     }
217 
218     /** Binary operators. */
219     typeof(this) opBinary(string op)(typeof(this) rh) const if (op == "+" || op == "-" || op == "*")
220     {
221         Unqual!(typeof(this)) res = this;
222         foreach (c; AllComponents)
223             mixin(ComponentExpression!("res._ #= rh._;", c, op));
224         return res;
225     }
226 
227     /** Binary operators. */
228     typeof(this) opBinary(string op, S)(S rh) const if (isColorScalarType!S && (op == "*" || op == "/" || op == "%" || op == "^^"))
229     {
230         Unqual!(typeof(this)) res = this;
231         foreach (c; AllComponents)
232             mixin(ComponentExpression!("res._ #= rh;", c, op));
233         return res;
234     }
235 
236     /** Binary assignment operators. */
237     ref typeof(this) opOpAssign(string op)(typeof(this) rh) if (op == "+" || op == "-" || op == "*")
238     {
239         foreach (c; AllComponents)
240             mixin(ComponentExpression!("_ #= rh._;", c, op));
241         return this;
242     }
243 
244     /** Binary assignment operators. */
245     ref typeof(this) opOpAssign(string op, S)(S rh) if (isColorScalarType!S && (op == "*" || op == "/" || op == "%" || op == "^^"))
246     {
247         foreach (c; AllComponents)
248             mixin(ComponentExpression!("_ #= rh;", c, op));
249         return this;
250     }
251 
252 
253 package:
254 
255     alias ParentColor = XYZ!ComponentType;
256 
257     static To convertColorImpl(To, From)(From color) if (isxyY!From && isxyY!To)
258     {
259         alias F = To.ComponentType;
260         return To(F(color.x), F(color.y), F(color.Y));
261     }
262     unittest
263     {
264         static assert(convertColorImpl!(xyY!float)(xyY!double(1, 2, 3)) == xyY!float(1, 2, 3));
265         static assert(convertColorImpl!(xyY!double)(xyY!float(1, 2, 3)) == xyY!double(1, 2, 3));
266     }
267 
268     static To convertColorImpl(To, From)(From color) if (isxyY!From && isXYZ!To)
269     {
270         alias F = To.ComponentType;
271         if (color.y == 0)
272             return To(F(0), F(0), F(0));
273         else
274             return To(F((color.Y/color.y)*color.x), F(color.Y), F((color.Y/color.y)*(1-color.x-color.y)));
275     }
276     unittest
277     {
278         static assert(convertColorImpl!(XYZ!float)(xyY!float(0.5, 0.5, 1)) == XYZ!float(1, 1, 0));
279 
280         // degenerate case
281         static assert(convertColorImpl!(XYZ!float)(xyY!float(0.5, 0, 1)) == XYZ!float(0, 0, 0));
282     }
283 
284     static To convertColorImpl(To, From)(From color) if (isXYZ!From && isxyY!To)
285     {
286         alias F = To.ComponentType;
287         auto sum = color.X + color.Y + color.Z;
288         if (sum == 0)
289             return To(WhitePoint!F.D65.x, WhitePoint!F.D65.y, F(0));
290         else
291             return To(F(color.X/sum), F(color.Y/sum), F(color.Y));
292     }
293     unittest
294     {
295         static assert(convertColorImpl!(xyY!float)(XYZ!float(0.5, 1, 0.5)) == xyY!float(0.25, 0.5, 1));
296 
297         // degenerate case
298         static assert(convertColorImpl!(xyY!float)(XYZ!float(0, 0, 0)) == xyY!float(WhitePoint!float.D65.x, WhitePoint!float.D65.y, 0));
299     }
300 
301 private:
302     alias AllComponents = TypeTuple!("x", "y", "Y");
303 }
304 
305 ///
306 unittest
307 {
308     // CIE xyY 1931 color with double components
309     alias xyYd = xyY!double;
310 
311     xyYd c = xyYd(0.4, 0.5, 1);
312 
313     // test xyY operators and functions
314     static assert(xyYd(0, 0.5, 0) + xyYd(0.5, 0.5, 1) == xyYd(0.5, 1, 1));
315     static assert(xyYd(0.5, 0.5, 1) * 100.0 == xyYd(50, 50, 100));
316 }