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