The OpenD Programming Language

1 // Written in the D programming language.
2 
3 /**
4 This module implements the $(LINK2 https://en.wikipedia.org/wiki/Lab_color_space, CIE Lab) and
5 $(LINK2 https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:__CIELCh_or_CIEHLC, LCh) _color types.
6 
7 Lab is full-spectrum, absolute, vector _color space, with the specific goal of human perceptual uniformity.
8 
9 Authors:    Manu Evans
10 Copyright:  Copyright (c) 2015, Manu Evans.
11 License:    $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0)
12 Source:     $(PHOBOSSRC std/experimental/color/_lab.d)
13 */
14 module std.experimental.color.lab;
15 
16 import std.experimental.color;
17 import std.experimental.color.xyz : XYZ, isXYZ;
18 import std.experimental.color.colorspace : WhitePoint;
19 
20 import std.traits: isFloatingPoint, Unqual;
21 import std.typetuple: TypeTuple;
22 import std.math : sin, cos, sqrt, atan2, PI, M_1_PI;
23 
24 
25 @safe: pure: nothrow: @nogc:
26 
27 /**
28 Detect whether $(D_INLINECODE T) is a L*a*b* color.
29 */
30 enum isLab(T) = isInstanceOf!(Lab, T);
31 
32 /**
33 Detect whether $(D_INLINECODE T) is an L*C*h° color.
34 */
35 enum isLCh(T) = isInstanceOf!(LCh, T);
36 
37 
38 /**
39 A CIE L*a*b* color, parameterised for component type and white point.
40 
41 Lab is a color space that describes all colors visible to the human eye and was created to serve as a device-independent model to be used as a reference.
42 L* represents the lightness of the color; L* = 0 yields black and L* = 100 indicates diffuse white, specular white may be higher.
43 a* represents the position between red/magenta and green; negative values indicate green while positive values indicate magenta.
44 b* represents the position between yellow and blue; negative values indicate blue and positive values indicate yellow.
45 
46 Lab is often found using default white point D50, but it is also common to use D65 when interacting with sRGB images.
47 */
48 struct Lab(F = float, alias whitePoint_ = (WhitePoint!F.D50)) if (isFloatingPoint!F)
49 {
50 @safe: pure: nothrow: @nogc:
51 
52     /** Type of the color components. */
53     alias ComponentType = F;
54 
55     /** The color components that were specified. */
56     enum whitePoint = whitePoint_;
57 
58     /** L* (lightness) component. */
59     F L = 0;
60     /** a* component. Negative values indicate green, positive values indicate magenta. */
61     F a = 0;
62     /** b* component. Negative values indicate blue, positive values indicate yellow. */
63     F b = 0;
64 
65     /** Construct a color from L*a*b* values. */
66     this(F L, F a, F b)
67     {
68         this.L = L;
69         this.a = a;
70         this.b = b;
71     }
72 
73     /** Returns the perceptual distance between the specifies colors. */
74     F perceptualDistance(G)(Lab!G c) const
75     {
76         alias WT = WorkingType!(F, G);
77         return sqrt((WT(c.L) - WT(L))^^2 + (WT(c.a) - WT(a))^^2 + (WT(c.b) - WT(b))^^2);
78     }
79 
80     /**
81     Cast to other color types.
82 
83     This cast is a convenience which simply forwards the call to convertColor.
84     */
85     Color opCast(Color)() const if (isColor!Color)
86     {
87         return convertColor!Color(this);
88     }
89 
90     /** Unary operators. */
91     typeof(this) opUnary(string op)() const if (op == "+" || op == "-" || (op == "~" && is(ComponentType == NormalizedInt!U, U)))
92     {
93         Unqual!(typeof(this)) res = this;
94         foreach (c; AllComponents)
95             mixin(ComponentExpression!("res._ = #_;", c, op));
96         return res;
97     }
98     /** Binary operators. */
99     typeof(this) opBinary(string op)(typeof(this) rh) const if (op == "+" || op == "-" || op == "*")
100     {
101         Unqual!(typeof(this)) res = this;
102         foreach (c; AllComponents)
103             mixin(ComponentExpression!("res._ #= rh._;", c, op));
104         return res;
105     }
106     /** Binary operators. */
107     typeof(this) opBinary(string op, S)(S rh) const if (isColorScalarType!S && (op == "*" || op == "/" || op == "%" || op == "^^"))
108     {
109         Unqual!(typeof(this)) res = this;
110         foreach (c; AllComponents)
111             mixin(ComponentExpression!("res._ #= rh;", c, op));
112         return res;
113     }
114     /** Binary assignment operators. */
115     ref typeof(this) opOpAssign(string op)(typeof(this) rh) if (op == "+" || op == "-" || op == "*")
116     {
117         foreach (c; AllComponents)
118             mixin(ComponentExpression!("_ #= rh._;", c, op));
119         return this;
120     }
121     /** Binary assignment operators. */
122     ref typeof(this) opOpAssign(string op, S)(S rh) if (isColorScalarType!S && (op == "*" || op == "/" || op == "%" || op == "^^"))
123     {
124         foreach (c; AllComponents)
125             mixin(ComponentExpression!("_ #= rh;", c, op));
126         return this;
127     }
128 
129 package:
130 
131     alias ParentColor = XYZ!ComponentType;
132 
133     static To convertColorImpl(To, From)(From color) if (isLab!From && isLab!To)
134     {
135         static if (From.whitePoint == To.whitePoint)
136         {
137             // same whitepoint, just a format conversion
138             return To(To.ComponentType(L), To.ComponentType(a), To.ComponentType(b));
139         }
140         else
141         {
142             // we'll need to pipe through XYZ to adjust the whitepoint
143             auto xyz = cast(XYZ!(To.ComponentType))this;
144             return cast(To)xyz;
145         }
146     }
147 
148     static To convertColorImpl(To, From)(From color) if (isLab!From && isXYZ!To)
149     {
150         alias WT = WorkingType!(From, To);
151 
152         enum w = cast(XYZ!WT)whitePoint;
153 
154         static WT f(WT v)
155         {
156             if (v > WT(0.206893))
157                 return v^^WT(3);
158             else
159                 return (v - WT(16.0/116))*WT(1/7.787);
160         }
161 
162         WT Y = (color.L + 16)*WT(1.0/116);
163         WT X =  color.a*WT(1.0/500) + Y;
164         WT Z = -color.b*WT(1.0/200) + Y;
165 
166         X = w.X * f(X);
167         Y = w.Y * f(Y);
168         Z = w.Z * f(Z);
169 
170         return To(X, Y, Z);
171     }
172 
173     static To convertColorImpl(To, From)(From color) if (isXYZ!From && isLab!To)
174     {
175         alias WT = WorkingType!(From, To);
176 
177         enum w = cast(XYZ!WT)whitePoint;
178 
179         static WT f(WT v)
180         {
181             if (v > WT(0.008856))
182                 return v^^WT(1.0/3);
183             else
184                 return WT(7.787)*v + WT(16.0/116);
185         }
186 
187         WT X = f(color.X / w.X);
188         WT Y = f(color.Y / w.Y);
189         WT Z = f(color.Z / w.Z);
190 
191         return To(116*Y - 16, 500*(X - Y), 200*(Y - Z));
192     }
193 
194 private:
195     alias AllComponents = TypeTuple!("L", "a", "b");
196 }
197 
198 
199 /**
200 A CIE L*C*h° color, parameterised for component type and white point.
201 The LCh color space is a Lab cube color space, where instead of cartesian coordinates a*, b*, the cylindrical coordinates C* (chroma) and h° (hue angle) are specified. The CIELab lightness L* remains unchanged.
202 */
203 struct LCh(F = float, alias whitePoint_ = (WhitePoint!F.D50)) if (isFloatingPoint!F)
204 {
205 @safe: pure: nothrow: @nogc:
206 
207     /** Type of the color components. */
208     alias ComponentType = F;
209 
210     /** The color components that were specified. */
211     enum whitePoint = whitePoint_;
212 
213     /** L* (lightness) component. */
214     F L = 0;
215     /** C* (chroma) component. */
216     F C = 0;
217     /** h° (hue) component, in degrees. */
218     F h = 0;
219 
220     /** Get hue angle in radians. */
221     @property F radians() const
222     {
223         return h * F((1.0/180)*PI);
224     }
225     /** Set hue angle in radians. */
226     @property void radians(F angle)
227     {
228         h = angle * F(M_1_PI*180);
229     }
230 
231     /**
232       Cast to other color types.
233 
234       This cast is a convenience which simply forwards the call to convertColor.
235     */
236     Color opCast(Color)() const if (isColor!Color)
237     {
238         return convertColor!Color(this);
239     }
240 
241 
242 package:
243 
244     alias ParentColor = Lab!(F, whitePoint_);
245 
246     static To convertColorImpl(To, From)(From color) if (isLCh!From && isLCh!To)
247     {
248         static if (From.whitePoint == To.whitePoint)
249         {
250             // same whitepoint, just a format conversion
251             return To(To.ComponentType(L), To.ComponentType(C), To.ComponentType(h));
252         }
253         else
254         {
255             // we'll need to pipe through XYZ to adjust the whitepoint
256             auto xyz = cast(XYZ!(To.ComponentType))this;
257             return cast(To)xyz;
258         }
259     }
260 
261     static To convertColorImpl(To, From)(From color) if (isLCh!From && isLab!To)
262     {
263         alias WT = WorkingType!(From, To);
264 
265         WT a = cos(color.h*WT(1.0/180*PI)) * color.C;
266         WT b = sin(color.h*WT(1.0/180*PI)) * color.C;
267 
268         return To(color.L, a, b);
269     }
270 
271     static To convertColorImpl(To, From)(From color) if (isLab!From && isLCh!To)
272     {
273         alias WT = WorkingType!(From, To);
274 
275         WT C = sqrt(color.a^^2 + color.b^^2);
276         WT h = atan2(color.b, color.a);
277         if (h >= 0)
278             h = h*WT(M_1_PI*180);
279         else
280             h = 360 + h*WT(M_1_PI*180);
281 
282         return To(color.L, C, h);
283     }
284 }