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 }