1 /** \file
2  * \brief Color Manipulation
3  *
4  * See Copyright Notice in im_lib.h
5  */
6 module im.im_color;
7 
8 version(IM) :
9 
10 //#include "im_math.h" //import im.im_math;
11 
12 //import std.exception : enforce;
13 import std.traits : isNumeric;
14 import core.stdc.math : powf;
15 import im.im_util : IM_CROPMINMAX, IM_FLOATCROP;
16 
17 /** \defgroup color Color Manipulation
18  *
19  * \par
20  * Functions to convert from one color space to another,
21  * and color gamut utilities.
22  * \par
23  * See \ref im_color.h
24  *
25  * \section s1 Some Color Science
26  * \par
27  * Y is luminance, a linear-light quantity.
28  * It is directly proportional to physical intensity
29  * weighted by the spectral sensitivity of human vision.
30  * \par
31  * L* is lightness, a nonlinear luminance
32  * that approximates the perception of brightness.
33  * It is nearly perceptual uniform.
34  * It has a range of 0 to 100.
35  * \par
36  * Y' is luma, a nonlinear luminance that approximates lightness.
37  * \par
38  * Brightness is a visual sensation according to which an area
39  * appears to exhibit more or less light.
40  * It is a subjective quantity and can not be measured.
41  * \par
42  * One unit of Euclidian distance in CIE L*u*v* or CIE L*a*b* corresponds
43  * roughly to a just-noticeable difference (JND) of color.
44  * \par
45 \verbatim
46  ChromaUV = sqrt(u*u + v*v)
47  HueUV = atan2(v, u)
48  SaturationUV = ChromaUV / L      (called psychometric saturation)
49  (the same can be calculated for Lab)
50 \endverbatim
51  * \par
52  * IEC 61966-2.1 Default RGB color space - sRGB
53  * \li ITU-R Recommendation BT.709 (D65 white point).
54  * \li D65 White Point (X,Y,Z) = (0.9505 1.0000 1.0890)
55  * \par
56  * Documentation extracted from  Charles Poynton - Digital Video and HDTV - Morgan Kaufmann - 2003.
57  *
58  * \section Links
59  * \li www.color.org - ICC
60  * \li www.srgb.com - sRGB
61  * \li www.poynton.com - Charles Poynton
62  * \li www.littlecms.com - A free Color Management System (use this if you need precise color conversions)
63  *
64  * \section cci Color Component Intervals
65  * \par
66  * When minimum and maximum values must be pre-defined values,
67  * the following values are used:
68  * \par
69 \verbatim
70  byte   [0,255]              (1 byte)
71  short  [-32768,32767]       (2 bytes)
72  ushort [0,65535]            (2 bytes)
73  int    [-8388608,+8388607]  (3 bytes of 4 possible)
74  float  [0,1]                (4 bytes)
75  double [0,1]                (8 bytes)
76  \endverbatim
77  * Usually this intervals are used when converting from real to integer,
78  * and when demoting an integer data type.
79  * \ingroup util */
80 
81 /** Returns the zero value for YCbCr color conversion. \n
82  * When data type is unsigned Cb and Cr are shifted to 0-max.
83  * So before they can be used in conversion equations
84  * Cb and Cr values must be shifted back to fix the zero position.
85  * \ingroup color */
86 float imColorZeroShift(int data_type) pure nothrow @nogc @safe // we want -boundscheck in release code
87 {
88   float[8] zero= [128.0f,     // [-128,+127]
89                   0,
90                   32768.0f,   // [-32768,+32767]
91                   0,
92                   0,
93                   0,
94                   0,
95                   0 ];
96   return zero[data_type];
97 }
98 
99 /** Returns the maximum value for pre-defined color conversion purposes. \n
100  * See \ref cci.
101  * \ingroup color */
102 int imColorMax(int data_type) pure nothrow @nogc @safe // we want -boundscheck in release code
103 {
104   int[8] max  = [255,
105                  32767,
106                  65535,
107                  8388607,
108                  1,
109                  1,
110                  0,
111                  0 ];
112   return max[data_type];
113 }
114 
115 /** Returns the minimum value for pre-defined color conversion purposes. \n
116  * See \ref cci.
117  * \ingroup color */
118 int imColorMin(int data_type) pure nothrow @nogc @safe // we want -boundscheck in release code
119 {
120   int[8] min = [0,
121                 -32768,
122                 0,
123                 -8388608,
124                 0,
125                 0,
126                 0,
127                 0 ];
128   return min[data_type];
129 }
130 
131 /** Quantize 0-1 values into min-max. \n
132  * Value are usually integers,
133  * but the dummy quantizer uses real values.
134  * See also \ref math.
135  * \ingroup color */
136 T imColorQuantize(T)(ref const(float) value, ref const(T) min, ref const(T) max) pure nothrow @nogc @safe
137 	if (isNumeric!T)
138 {
139   if (max == 1) return cast(T)value; // to allow a dummy quantizer
140   if (value >= 1) return max;
141   if (value <= 0) return min;
142   float range = cast(float)max - cast(float)min + 1.0f;
143   return cast(T)imRound(value*range - 0.5f) + min;
144 }
145 
146 /** Reconstruct min-max values into 0-1. \n
147  * Values are usually integers,
148  * but the dummy reconstructor uses real values.
149  * See also \ref math.
150  * \ingroup color */
151 float imColorReconstruct(T)(ref const(T) value, ref const(T) min, ref const(T) max) pure nothrow @nogc @safe
152 	if (isNumeric!T)
153 {
154   if (max == 1) return cast(float)value;  // to allow a dummy reconstructor
155   if (value <= min) return 0;
156   if (value >= max) return 1;
157   float range = cast(float)max - cast(float)min + 1.0f;
158   return ((cast(float)value - cast(float)min + 0.5f)/range);
159 }
160 
161 /** Converts Y'CbCr to R'G'B' (all nonlinear). \n
162  * ITU-R Recommendation 601-1 with no headroom/footroom.
163 \verbatim
164  0 <= Y <= 1 ; -0.5 <= CbCr <= 0.5 ; 0 <= RGB <= 1
165 
166  R'= Y' + 0.000 *Cb + 1.402 *Cr
167  G'= Y' - 0.344 *Cb - 0.714 *Cr
168  B'= Y' + 1.772 *Cb + 0.000 *Cr
169 \endverbatim
170  * \ingroup color */
171 void imColorYCbCr2RGB(T)(const T Y, const T Cb, const T Cr,
172                              ref T R, ref T G, ref T B,
173                              ref const(T) zero, ref const(T) min, ref const(T) max) pure nothrow @nogc @safe
174 	if (isNumeric!T)
175 {
176   float r = float(Y                        + 1.402f * (Cr - zero));
177   float g = float(Y - 0.344f * (Cb - zero) - 0.714f * (Cr - zero));
178   float b = float(Y + 1.772f * (Cb - zero));
179 
180   // now we should enforce min <= rgb <= max
181   R = cast(T)IM_CROPMINMAX(r, min, max);
182   G = cast(T)IM_CROPMINMAX(g, min, max);
183   B = cast(T)IM_CROPMINMAX(b, min, max);
184 }
185 
186 /** Converts R'G'B' to Y'CbCr (all nonlinear). \n
187  * ITU-R Recommendation 601-1 with no headroom/footroom.
188 \verbatim
189  0 <= Y <= 1 ; -0.5 <= CbCr <= 0.5 ; 0 <= RGB <= 1
190 
191  Y' =  0.299 *R' + 0.587 *G' + 0.114 *B'
192  Cb = -0.169 *R' - 0.331 *G' + 0.500 *B'
193  Cr =  0.500 *R' - 0.419 *G' - 0.081 *B'
194 \endverbatim
195  * \ingroup color */
196 void imColorRGB2YCbCr(T)(const T R, const T G, const T B,
197                              ref T Y, ref T Cb, ref T Cr,
198                              ref const(T) zero) pure nothrow @nogc @safe
199 	if (isNumeric!T)
200 {
201   Y  = cast(T)( 0.299f *R + 0.587f *G + 0.114f *B);
202   Cb = cast(T)(-0.169f *R - 0.331f *G + 0.500f *B + cast(float)zero);
203   Cr = cast(T)( 0.500f *R - 0.419f *G - 0.081f *B + cast(float)zero);
204 }
205 
206 /** Converts C'M'Y'K' to R'G'B' (all nonlinear). \n
207  * This is a poor conversion that works for a simple visualization.
208 \verbatim
209   0 <= CMYK <= 1 ; 0 <= RGB <= 1
210 
211   R = (1 - K) * (1 - C)
212   G = (1 - K) * (1 - M)
213   B = (1 - K) * (1 - Y)
214 \endverbatim
215  * \ingroup color */
216 void imColorCMYK2RGB(T)(const T C, const T M, const T Y, const T K,
217                             ref T R, ref T G, ref T B, ref const(T) max) pure nothrow @nogc @safe
218 	if (isNumeric!T)
219 {
220   T W = max - K;
221   R = cast(T)((W * (max - C)) / max);
222   G = cast(T)((W * (max - M)) / max);
223   B = cast(T)((W * (max - Y)) / max);
224 }
225 
226 /** Converts CIE XYZ to Rec 709 RGB (all linear). \n
227  * ITU-R Recommendation BT.709 (D65 white point). \n
228 \verbatim
229   0 <= XYZ <= 1 ; 0 <= RGB <= 1
230 
231   R =  3.2406 *X - 1.5372 *Y - 0.4986 *Z
232   G = -0.9689 *X + 1.8758 *Y + 0.0415 *Z
233   B =  0.0557 *X - 0.2040 *Y + 1.0570 *Z
234 \endverbatim
235  * \ingroup color */
236 void imColorXYZ2RGB(T)(const T X, const T Y, const T Z,
237                            ref T R, ref T G, ref T B) pure nothrow @nogc @safe
238 	if (isNumeric!T)
239 {
240   float r =  3.2406f *X - 1.5372f *Y - 0.4986f *Z;
241   float g = -0.9689f *X + 1.8758f *Y + 0.0415f *Z;
242   float b =  0.0557f *X - 0.2040f *Y + 1.0570f *Z;
243 
244   // we need to crop because not all XYZ colors are visible
245   R = cast(T)IM_FLOATCROP(r);
246   G = cast(T)IM_FLOATCROP(g);
247   B = cast(T)IM_FLOATCROP(b);
248 }
249 
250 /** Converts Rec 709 RGB to CIE XYZ (all linear). \n
251  * ITU-R Recommendation BT.709 (D65 white point). \n
252 \verbatim
253   0 <= XYZ <= 1 ; 0 <= RGB <= 1
254 
255   X = 0.4124 *R + 0.3576 *G + 0.1805 *B
256   Y = 0.2126 *R + 0.7152 *G + 0.0722 *B
257   Z = 0.0193 *R + 0.1192 *G + 0.9505 *B
258 \endverbatim
259  * \ingroup color */
260 void imColorRGB2XYZ(T)(const T R, const T G, const T B,
261                            ref T X, ref T Y, ref T Z) pure nothrow @nogc @safe
262 	if (isNumeric!T)
263 {
264   X = cast(T)(0.4124f *R + 0.3576f *G + 0.1805f *B);
265   Y = cast(T)(0.2126f *R + 0.7152f *G + 0.0722f *B);
266   Z = cast(T)(0.0193f *R + 0.1192f *G + 0.9505f *B);
267 }
268 
269 float IM_FWLAB(float _w) nothrow @nogc @safe { // powf turns it impure
270                         return (_w > 0.008856f?
271                         powf(_w, 1.0f/3.0f):
272                         7.787f * _w + 0.16f/1.16f); }
273 
274 /** Converts CIE XYZ (linear) to CIE L*a*b* (nonlinear). \n
275  * The white point is D65. \n
276 \verbatim
277   0 <= L <= 1 ; -0.5 <= ab <= +0.5 ; 0 <= XYZ <= 1
278 
279   if (t > 0.008856)
280     f(t) = pow(t, 1/3)
281   else
282     f(t) = 7.787*t + 16/116
283 
284   fX = f(X / Xn)      fY = f(Y / Yn)      fZ = f(Z / Zn)
285 
286   L = 1.16 * fY - 0.16
287   a = 2.5 * (fX - fY)
288   b = (fY - fZ)
289 
290 \endverbatim
291  * \ingroup color */
292 void imColorXYZ2Lab(const float X, const float Y, const float Z,
293                           ref float L, ref float a, ref float b) nothrow @nogc @safe
294 {
295   float fX = X / 0.9505f;  // white point D65
296   float fY = Y / 1.0f;
297   float fZ = Z / 1.0890f;
298 
299   fX = IM_FWLAB(fX);
300   fY = IM_FWLAB(fY);
301   fZ = IM_FWLAB(fZ);
302 
303   L = 1.16f * fY - 0.16f;
304   a = 2.5f * (fX - fY);
305   b = (fY - fZ);
306 }
307 
308 float IM_GWLAB(float _w) nothrow @nogc @safe  // powf turns it impure
309 {                        return (_w > 0.20689f?
310                          powf(_w, 3.0f):
311                          0.1284f * (_w - 0.16f/1.16f)); }
312 
313 /** Converts CIE L*a*b* (nonlinear) to CIE XYZ (linear). \n
314  * The white point is D65. \n
315  * 0 <= L <= 1 ; -0.5 <= ab <= +0.5 ; 0 <= XYZ <= 1
316  * \ingroup color */
317 void imColorLab2XYZ(const float L, const float a, const float b,
318                           ref float X, ref float Y, ref float Z) nothrow @nogc @safe
319 
320 {
321   float fY = (L + 0.16f) / 1.16f;
322   float gY = IM_GWLAB(fY);
323 
324   float fgY = IM_FWLAB(gY);
325   float gX = fgY + a / 2.5f;
326   float gZ = fgY - b;
327   gX = IM_GWLAB(gX);
328   gZ = IM_GWLAB(gZ);
329 
330   X = gX * 0.9505f;     // white point D65
331   Y = gY * 1.0f;
332   Z = gZ * 1.0890f;
333 }
334 
335 /** Converts CIE XYZ (linear) to CIE L*u*v* (nonlinear). \n
336  * The white point is D65. \n
337 \verbatim
338   0 <= L <= 1 ; -1 <= uv <= +1 ; 0 <= XYZ <= 1
339 
340   Y = Y / 1.0      (for D65)
341   if (Y > 0.008856)
342     fY = pow(Y, 1/3)
343   else
344     fY = 7.787 * Y + 0.16/1.16
345   L = 1.16 * fY - 0.16
346 
347   U(x, y, z) = (4 * x)/(x + 15 * y + 3 * z)
348   V(x, y, z) = (9 * x)/(x + 15 * y + 3 * z)
349   un = U(Xn, Yn, Zn) = 0.1978      (for D65)
350   vn = V(Xn, Yn, Zn) = 0.4683      (for D65)
351   fu = U(X, Y, Z)
352   fv = V(X, Y, Z)
353 
354   u = 13 * L * (fu - un)
355   v = 13 * L * (fv - vn)
356 \endverbatim
357  * \ingroup color */
358 void imColorXYZ2Luv(const float X, const float Y, const float Z,
359                           ref  float L, ref float u, ref float v) nothrow @nogc @safe
360 {
361   float XYZ = cast(float)(X + 15 * Y + 3 * Z);
362   float fY = Y / 1.0f;
363 
364   if (XYZ != 0)
365   {
366     L = 1.16f * IM_FWLAB(fY) - 0.16f;
367     u = 6.5f * L * ((4 * X)/XYZ - 0.1978f);
368     v = 6.5f * L * ((9 * Y)/XYZ - 0.4683f);
369   }
370   else
371   {
372     L = u = v = 0;
373   }
374 }
375 
376 /** Converts CIE L*u*v* (nonlinear) to CIE XYZ (linear). \n
377  * The white point is D65.
378  * 0 <= L <= 1 ; -0.5 <= uv <= +0.5 ; 0 <= XYZ <= 1 \n
379  * \ingroup color */
380 void imColorLuv2XYZ(const float L, const float u, const float v,
381                           ref float X, ref float Y, ref float Z) nothrow @nogc @safe
382 
383 {
384   float fY = (L + 0.16f) / 1.16f;
385   Y = IM_GWLAB(fY) * 1.0f;
386 
387   float ul = 0.1978f, vl = 0.4683f;
388   if (L != 0)
389   {
390     ul = u / (6.5f * L) + 0.1978f;
391     vl = v / (6.5f * L) + 0.4683f;
392   }
393 
394   X = ((9 * ul) / (4 * vl)) * Y;
395   Z = ((12 - 3 * ul - 20 * vl) / (4 * vl)) * Y;
396 }
397 
398 /** Converts nonlinear values to linear values. \n
399  * We use the sRGB transfer function. sRGB uses ITU-R 709 primaries and D65 white point. \n
400 \verbatim
401   0 <= l <= 1 ; 0 <= v <= 1
402 
403   if (v < 0.03928)
404     l = v / 12.92
405   else
406     l = pow((v + 0.055) / 1.055, 2.4)
407 \endverbatim
408  * \ingroup color */                           
409 float imColorTransfer2Linear(ref const(float) nonlinear_value) nothrow @nogc @safe
410 {
411   if (nonlinear_value < 0.03928f)
412     return nonlinear_value / 12.92f;
413   else
414     return powf((nonlinear_value + 0.055f) / 1.055f, 2.4f);
415 }
416 
417 /** Converts linear values to nonlinear values. \n
418  * We use the sRGB transfer function. sRGB uses ITU-R 709 primaries and D65 white point. \n
419 \verbatim
420   0 <= l <= 1 ; 0 <= v <= 1
421 
422   if (l < 0.0031308)
423     v = 12.92 * l
424   else
425     v = 1.055 * pow(l, 1/2.4) - 0.055
426 \endverbatim
427  * \ingroup color */                           
428 float imColorTransfer2Nonlinear(ref const(float) value) nothrow @nogc @safe
429 {
430   if (value < 0.0031308f)
431     return 12.92f * value;
432   else
433     return 1.055f * powf(value, 1.0f/2.4f) - 0.055f;
434 }
435 
436 /** Converts RGB (linear) to R'G'B' (nonlinear).
437  * \ingroup color */
438 void imColorRGB2RGBNonlinear(const float RL, const float GL, const float BL,
439                                    ref float R, ref float G, ref float B) nothrow @nogc @safe
440 {
441   R = imColorTransfer2Nonlinear(RL);
442   G = imColorTransfer2Nonlinear(GL);
443   B = imColorTransfer2Nonlinear(BL);
444 }
445 
446 /** Converts R'G'B' to Y' (all nonlinear). \n
447 \verbatim
448  Y'  =  0.299 *R' + 0.587 *G' + 0.114 *B'
449 \endverbatim
450  * \ingroup color */
451 T imColorRGB2Luma(T)(const T R, const T G, const T B) pure nothrow @nogc @safe
452 	if (isNumeric!T)
453 {
454   return cast(T)((299 * R + 587 * G + 114 * B) / 1000);
455 }
456 
457 /** Converts Luminance (CIE Y) to Lightness (CIE L*) (all linear). \n
458  * The white point is D65.
459 \verbatim
460   0 <= Y <= 1 ; 0 <= L* <= 1
461 
462   Y = Y / 1.0      (for D65)
463   if (Y > 0.008856)
464     fY = pow(Y, 1/3)
465   else
466     fY = 7.787 * Y + 0.16/1.16
467   L = 1.16 * fY - 0.16
468 \endverbatim
469  * \ingroup color */
470 float imColorLuminance2Lightness(ref const(float) Y) nothrow @nogc @safe
471 {
472   return 1.16f * IM_FWLAB(Y) - 0.16f;
473 }
474 
475 /** Converts Lightness (CIE L*) to Luminance (CIE Y) (all linear). \n
476  * The white point is D65.
477 \verbatim
478   0 <= Y <= 1 ; 0 <= L* <= 1
479 
480   fY = (L + 0.16)/1.16
481   if (fY > 0.20689)
482     Y = pow(fY, 3)
483   else
484     Y = 0.1284 * (fY - 0.16/1.16)
485   Y = Y * 1.0      (for D65)
486 \endverbatim
487  * \ingroup color */
488 float imColorLightness2Luminance(ref const(float) L) nothrow @nogc @safe
489 {
490   float fY = (L + 0.16f) / 1.16f;
491   return IM_GWLAB(fY);
492 }
493