IM: Color Manipulation

IM - An Imaging Tool

Color Manipulation
[Utilities]


Detailed Description

Functions to convert from one color space to another, and color gammut utilities.
See im_color.h

Some Color Science

Y is luminance, a linear-light quantity. It is directly proportional to physical intensity weighted by the spectral sensitivity of human vision.
L* is lightness, a nonlinear luminance that aproximates the perception of brightness. It is nearly perceptual uniform. It has a range of 0 to 100.
Y' is luma, a nonlinear luminance that aproximates lightness.
Brightness is a visual sensation according to which an area apears to exhibit more or less light. It is a subjective quantity and can not be measured.
One unit of euclidian distante in CIE L*u*v* or CIE L*a*b* corresponds roughly to a just-noticeable difference (JND) of color.
 ChromaUV = sqrt(u*u + v*v)       
 HueUV = atan2(v, u)
 SaturationUV = ChromaUV / L      (called psychometric saturation) 
 (the same can be calculated for Lab)
IEC 61966-2.1 Default RGB colour space - sRGB
  • ITU-R Recommendation BT.709 (D65 white point).
  • D65 White Point (X,Y,Z) = (0.9505 1.0000 1.0890)
Documentation extracted from Charles Poynton - Digital Video and HDTV - Morgan Kaufmann - 2003.

Links

  • www.color.org - ICC
  • www.srgb.com - sRGB
  • www.poynton.com - Charles Poynton
  • www.littlecms.com - A free Color Management System (use this if you need precise color conversions)

Color Component Intervals

All the color components are stored in the 0-max interval, even the signed ones.
Here are the pre-defined intervals for each data type. These values are used for standard color conversion. You should normalize data before converting betwwen color spaces.
 byte   [0,255]      or [-128,+127]          (1 byte)
 ushort [0,65535]    or [-32768,+32767]      (2 bytes)
 int    [0,16777215] or [-8388608,+8388607]  (3 bytes)
 float  [0,1]        or [-0.5,+0.5]          (4 bytes)


Modules

 HSI Color Coordinate System Conversions

Functions

float imColorZero (int data_type)
int imColorMax (int data_type)
template<class T>
imColorQuantize (const float &value, const T &max)
template<class T>
float imColorReconstruct (const T &value, const T &max)
template<class T>
void imColorYCbCr2RGB (const T Y, const T Cb, const T Cr, T &R, T &G, T &B, const T &zero, const T &max)
template<class T>
void imColorRGB2YCbCr (const T R, const T G, const T B, T &Y, T &Cb, T &Cr, const T &zero)
template<class T>
void imColorCMYK2RGB (const T C, const T M, const T Y, const T K, T &R, T &G, T &B, const T &max)
template<class T>
void imColorXYZ2RGB (const T X, const T Y, const T Z, T &R, T &G, T &B, const T &max)
template<class T>
void imColorRGB2XYZ (const T R, const T G, const T B, T &X, T &Y, T &Z)
void imColorXYZ2Lab (const float X, const float Y, const float Z, float &L, float &a, float &b)
void imColorLab2XYZ (const float L, const float a, const float b, float &X, float &Y, float &Z)
void imColorXYZ2Luv (const float X, const float Y, const float Z, float &L, float &u, float &v)
void imColorLuv2XYZ (const float L, const float u, const float v, float &X, float &Y, float &Z)
float imColorTransfer2Linear (const float &nonlinear_value)
float imColorTransfer2Nonlinear (const float &value)
void imColorRGB2RGBNonlinear (const float RL, const float GL, const float BL, float &R, float &G, float &B)
template<class T>
imColorRGB2Luma (const T R, const T G, const T B)
float imColorLuminance2Lightness (const float &Y)
float imColorLightness2Luminance (const float &L)

Function Documentation

float imColorZero int  data_type  )  [inline]
 

Returns the zero value for color conversion porpouses.
This is a value to be compensated when the data_type is unsigned and component is signed.

00078 {
00079   float zero[] = {128.0f, 32768.0f, 8388608.0f, 0.5f};
00080   return zero[data_type];
00081 }

int imColorMax int  data_type  )  [inline]
 

Returns the maximum value for color conversion porpouses.

00086 {
00087   int max[] = {255, 65535, 16777215, 1};
00088   return max[data_type];
00089 }

template<class T>
T imColorQuantize const float &  value,
const T &  max
[inline]
 

Quantize 0-1 values into 0-max.
q = r * (max + 1)
Divide by the size of each interval 1/(max+1), then the value is rounded down in the typecast.
But 0 is mapped to 0, and 1 is mapped to max.

00099 {
00100   if (max == 1) return (T)value; // to allow a dummy quantize
00101   if (value >= 1) return max;
00102   if (value <= 0) return 0;
00103   return (T)(value*(max + 1));  
00104 }                               

template<class T>
float imColorReconstruct const T &  value,
const T &  max
[inline]
 

Reconstruct 0-max values into 0-1.
r = (q + 0.5)/(max + 1)
Add 0.5 to set the same origin, then multiply by the size of each interval 1/(max+1).
But 0 is mapped to 0, and max is mapped to 1.

00113 {
00114   if (max == 1) return (float)value;  // to allow a dummy reconstruct
00115   if (value <= 0) return 0;
00116   if (value >= max) return 1;
00117   return (((float)value + 0.5f)/((float)max + 1.0f));
00118 }

template<class T>
void imColorYCbCr2RGB const T  Y,
const T  Cb,
const T  Cr,
T &  R,
T &  G,
T &  B,
const T &  zero,
const T &  max
[inline]
 

Converts Y'CbCr to R'G'B' (all nonlinear).
ITU-R Recommendation 601-1 with no headroom/footroom.

 0 <= Y <= 1 ; -0.5 <= CbCr <= 0.5 ; 0 <= RGB <= 1 

 R'= Y' + 0.000 *Cb + 1.402 *Cr
 G'= Y' - 0.344 *Cb - 0.714 *Cr
 B'= Y' + 1.772 *Cb + 0.000 *Cr
00134 {
00135   float r = float(Y                        + 1.402f * (Cr - zero));
00136   float g = float(Y - 0.344f * (Cb - zero) - 0.714f * (Cr - zero));
00137   float b = float(Y + 1.772f * (Cb - zero));
00138 
00139   // now we should enforce 0<= rgb <= max
00140 
00141   R = (T)IM_CROPMAX(r, max);
00142   G = (T)IM_CROPMAX(g, max);
00143   B = (T)IM_CROPMAX(b, max);
00144 }

template<class T>
void imColorRGB2YCbCr const T  R,
const T  G,
const T  B,
T &  Y,
T &  Cb,
T &  Cr,
const T &  zero
[inline]
 

Converts R'G'B' to Y'CbCr (all nonlinear).
ITU-R Recommendation 601-1 with no headroom/footroom.

 0 <= Y <= 1 ; -0.5 <= CbCr <= 0.5 ; 0 <= RGB <= 1 

 Y' =  0.299 *R' + 0.587 *G' + 0.114 *B'
 Cb = -0.169 *R' - 0.331 *G' + 0.500 *B'
 Cr =  0.500 *R' - 0.419 *G' - 0.081 *B'
00160 {
00161   Y  = (T)( 0.299f *R + 0.587f *G + 0.114f *B);
00162   Cb = (T)(-0.169f *R - 0.331f *G + 0.500f *B + (float)zero);
00163   Cr = (T)( 0.500f *R - 0.419f *G - 0.081f *B + (float)zero);
00164 
00165   // there is no need for cropping here, YCrCr is already at the limits
00166 }

template<class T>
void imColorCMYK2RGB const T  C,
const T  M,
const T  Y,
const T  K,
T &  R,
T &  G,
T &  B,
const T &  max
[inline]
 

Converts C'M'Y'K' to R'G'B' (all nonlinear).
This is a poor conversion that works for a simple visualization.

  0 <= CMYK <= 1 ; 0 <= RGB <= 1 

  R = (1 - K) * (1 - C)
  G = (1 - K) * (1 - M)
  B = (1 - K) * (1 - Y)
00181 {
00182   T W = max - K;
00183   R = (T)((W * (max - C)) / max);
00184   G = (T)((W * (max - M)) / max);
00185   B = (T)((W * (max - Y)) / max);
00186 
00187   // there is no need for cropping here, RGB is already at the limits
00188 }

template<class T>
void imColorXYZ2RGB const T  X,
const T  Y,
const T  Z,
T &  R,
T &  G,
T &  B,
const T &  max
[inline]
 

Converts CIE XYZ to Rec 709 RGB (all linear).
ITU-R Recommendation BT.709 (D65 white point).

  0 <= XYZ <= 1 ; 0 <= RGB <= 1    

  R =  3.2406 *X - 1.5372 *Y - 0.4986 *Z
  G = -0.9689 *X + 1.8758 *Y + 0.0415 *Z
  B =  0.0557 *X - 0.2040 *Y + 1.0570 *Z
00203 {
00204   float r =  3.2406f *X - 1.5372f *Y - 0.4986f *Z;
00205   float g = -0.9689f *X + 1.8758f *Y + 0.0415f *Z;
00206   float b =  0.0557f *X - 0.2040f *Y + 1.0570f *Z;
00207 
00208   // we need to crop because not all XYZ colors are visible
00209 
00210   R = (T)IM_CROPMAX(r, max);
00211   G = (T)IM_CROPMAX(g, max);
00212   B = (T)IM_CROPMAX(b, max);
00213 }

template<class T>
void imColorRGB2XYZ const T  R,
const T  G,
const T  B,
T &  X,
T &  Y,
T &  Z
[inline]
 

Converts Rec 709 RGB to CIE XYZ (all linear).
ITU-R Recommendation BT.709 (D65 white point).

  0 <= XYZ <= 1 ; 0 <= RGB <= 1    

  X = 0.4124 *R + 0.3576 *G + 0.1805 *B
  Y = 0.2126 *R + 0.7152 *G + 0.0722 *B
  Z = 0.0193 *R + 0.1192 *G + 0.9505 *B
00228 {
00229   X = (T)(0.4124f *R + 0.3576f *G + 0.1805f *B);
00230   Y = (T)(0.2126f *R + 0.7152f *G + 0.0722f *B);
00231   Z = (T)(0.0193f *R + 0.1192f *G + 0.9505f *B);
00232 
00233   // there is no need for cropping here, XYZ is already at the limits
00234 }

void imColorXYZ2Lab const float  X,
const float  Y,
const float  Z,
float &  L,
float &  a,
float &  b
[inline]
 

Converts CIE XYZ (linear) to CIE L*a*b* (nonlinear).
The white point is D65.

  0 <= L <= 1 ; -0.5 <= ab <= +0.5 ; 0 <= XYZ <= 1 

  if (t > 0.008856)
    f(t) = pow(t, 1/3)
  else
    f(t) = 7.787*t + 16/116

  fX = f(X / Xn)      fY = f(Y / Yn)      fZ = f(Z / Zn)

  L = 1.16 * fY - 0.16
  a = 2.5 * (fX - fY)
  b = (fY - fZ)

00260 {
00261   float fX = X / 0.9505f;  // white point D65
00262   float fY = Y / 1.0f;
00263   float fZ = Z / 1.0890f;
00264 
00265   fX = IM_FWLAB(fX);
00266   fY = IM_FWLAB(fY);
00267   fZ = IM_FWLAB(fZ);
00268 
00269   L = 1.16f * fY - 0.16f;
00270   a = 2.5f * (fX - fY);
00271   b = (fY - fZ);
00272 }

void imColorLab2XYZ const float  L,
const float  a,
const float  b,
float &  X,
float &  Y,
float &  Z
[inline]
 

Converts CIE L*a*b* (nonlinear) to CIE XYZ (linear).
The white point is D65.
0 <= L <= 1 ; -0.5 <= ab <= +0.5 ; 0 <= XYZ <= 1

00285 {
00286   float fY = (L + 0.16f) / 1.16f;
00287   float gY = IM_GWLAB(fY);
00288 
00289   float fgY = IM_FWLAB(gY);
00290   float gX = fgY + a / 2.5f;
00291   float gZ = fgY - b;
00292   gX = IM_GWLAB(gX);
00293   gZ = IM_GWLAB(gZ);
00294 
00295   X = gX * 0.9505f;     // white point D65
00296   Y = gY * 1.0f;
00297   Z = gZ * 1.0890f;
00298 }

void imColorXYZ2Luv const float  X,
const float  Y,
const float  Z,
float &  L,
float &  u,
float &  v
[inline]
 

Converts CIE XYZ (linear) to CIE L*u*v* (nonlinear).
The white point is D65.

  0 <= L <= 1 ; -1 <= uv <= +1 ; 0 <= XYZ <= 1

  Y = Y / 1.0      (for D65)
  if (Y > 0.008856)
    fY = pow(Y, 1/3)
  else
    fY = 7.787 * Y + 0.16/1.16
  L = 1.16 * fY - 0.16

  U(x, y, z) = (4 * x)/(x + 15 * y + 3 * z)
  V(x, y, z) = (9 * x)/(x + 15 * y + 3 * z)
  un = U(Xn, Yn, Zn) = 0.1978      (for D65)
  vn = V(Xn, Yn, Zn) = 0.4683      (for D65)
  fu = U(X, Y, Z) 
  fv = V(X, Y, Z) 

  u = 13 * L * (fu - un)
  v = 13 * L * (fv - vn)
00325 {
00326   float XYZ = (float)(X + 15 * Y + 3 * Z);
00327   float fY = Y / 1.0f;
00328 
00329   if (XYZ != 0)
00330   {
00331     L = 1.16f * IM_FWLAB(fY) - 0.16f;
00332     u = 6.5f * L * ((4 * X)/XYZ - 0.1978f);
00333     v = 6.5f * L * ((9 * Y)/XYZ - 0.4683f);
00334   }
00335   else
00336   {
00337     L = u = v = 0;
00338   }
00339 }

void imColorLuv2XYZ const float  L,
const float  u,
const float  v,
float &  X,
float &  Y,
float &  Z
[inline]
 

Converts CIE L*u*v* (nonlinear) to CIE XYZ (linear).
The white point is D65. 0 <= L <= 1 ; -0.5 <= uv <= +0.5 ; 0 <= XYZ <= 1

00348 {
00349   float fY = (L + 0.16f) / 1.16f;
00350   Y = IM_GWLAB(fY) * 1.0f;
00351 
00352   float ul = 0.1978f, vl = 0.4683f;
00353   if (L != 0)
00354   {
00355     ul = u / (6.5f * L) + 0.1978f;
00356     vl = v / (6.5f * L) + 0.4683f;
00357   }
00358 
00359   X = ((9 * ul) / (4 * vl)) * Y;
00360   Z = ((12 - 3 * ul - 20 * vl) / (4 * vl)) * Y;
00361 }

float imColorTransfer2Linear const float &  nonlinear_value  )  [inline]
 

Converts nonlinear values to linear values.
We use the sRGB transfer function. sRGB uses ITU-R 709 primaries and D65 white point.

  0 <= l <= 1 ; 0 <= v <= 1 

  if (v < 0.03928)
    l = v / 12.92
  else
    l = pow((v + 0.055) / 1.055, 2.4)
00375 {
00376   if (nonlinear_value < 0.03928f)
00377     return nonlinear_value / 12.92f;
00378   else
00379     return powf((nonlinear_value + 0.055f) / 1.055f, 2.4f);
00380 }

float imColorTransfer2Nonlinear const float &  value  )  [inline]
 

Converts linear values to nonlinear values.
We use the sRGB transfer function. sRGB uses ITU-R 709 primaries and D65 white point.

  0 <= l <= 1 ; 0 <= v <= 1 

  if (l < 0.0031308)
    v = 12.92 * l
  else
    v = 1.055 * pow(l, 1/2.4) - 0.055
00394 {
00395   if (value < 0.0031308f)
00396     return 12.92f * value;
00397   else
00398     return 1.055f * powf(value, 1.0f/2.4f) - 0.055f;
00399 }

void imColorRGB2RGBNonlinear const float  RL,
const float  GL,
const float  BL,
float &  R,
float &  G,
float &  B
[inline]
 

Converts RGB (linear) to R'G'B' (nonlinear).

00405 {
00406   R = imColorTransfer2Nonlinear(RL);
00407   G = imColorTransfer2Nonlinear(GL);
00408   B = imColorTransfer2Nonlinear(BL);
00409 }

template<class T>
T imColorRGB2Luma const T  R,
const T  G,
const T  B
[inline]
 

Converts R'G'B' to Y' (all nonlinear).

 Y'  =  0.299 *R' + 0.587 *G' + 0.114 *B'
00418 {
00419   return (T)((299 * R + 587 * G + 114 * B) / 1000);
00420 }

float imColorLuminance2Lightness const float &  Y  )  [inline]
 

Converts Luminance (CIE Y) to Lightness (CIE L*) (all linear).
The white point is D65.

  0 <= Y <= 1 ; 0 <= L* <= 1

  Y = Y / 1.0      (for D65)
  if (Y > 0.008856)
    fY = pow(Y, 1/3)
  else
    fY = 7.787 * Y + 0.16/1.16
  L = 1.16 * fY - 0.16
00436 {
00437   return 1.16f * IM_FWLAB(Y) - 0.16f;
00438 }

float imColorLightness2Luminance const float &  L  )  [inline]
 

Converts Lightness (CIE L*) to Luminance (CIE Y) (all linear).
The white point is D65.

  0 <= Y <= 1 ; 0 <= L* <= 1

  fY = (L + 0.16)/1.16
  if (fY > 0.20689)
    Y = pow(fY, 3)
  else
    Y = 0.1284 * (fY - 0.16/1.16)
  Y = Y * 1.0      (for D65)
00454 {
00455   float fY = (L + 0.16f) / 1.16f;
00456   return IM_GWLAB(fY);
00457 }