iup-stack/im/include/im_color.h

493 lines
13 KiB
C
Raw Normal View History

2023-02-20 16:44:45 +00:00
/** \file
* \brief Color Manipulation
*
* See Copyright Notice in im_lib.h
*/
#ifndef __IM_COLOR_H
#define __IM_COLOR_H
#include "im_math.h"
/** \defgroup color Color Manipulation
*
* \par
* Functions to convert from one color space to another,
* and color gamut utilities.
* \par
* See \ref im_color.h
*
* \section s1 Some Color Science
* \par
* Y is luminance, a linear-light quantity.
* It is directly proportional to physical intensity
* weighted by the spectral sensitivity of human vision.
* \par
* L* is lightness, a nonlinear luminance
* that approximates the perception of brightness.
* It is nearly perceptual uniform.
* It has a range of 0 to 100.
* \par
* Y' is luma, a nonlinear luminance that approximates lightness.
* \par
* Brightness is a visual sensation according to which an area
* appears to exhibit more or less light.
* It is a subjective quantity and can not be measured.
* \par
* One unit of Euclidian distance in CIE L*u*v* or CIE L*a*b* corresponds
* roughly to a just-noticeable difference (JND) of color.
* \par
\verbatim
ChromaUV = sqrt(u*u + v*v)
HueUV = atan2(v, u)
SaturationUV = ChromaUV / L (called psychometric saturation)
(the same can be calculated for Lab)
\endverbatim
* \par
* IEC 61966-2.1 Default RGB color space - sRGB
* \li ITU-R Recommendation BT.709 (D65 white point).
* \li D65 White Point (X,Y,Z) = (0.9505 1.0000 1.0890)
* \par
* Documentation extracted from Charles Poynton - Digital Video and HDTV - Morgan Kaufmann - 2003.
*
* \section Links
* \li www.color.org - ICC
* \li www.srgb.com - sRGB
* \li www.poynton.com - Charles Poynton
* \li www.littlecms.com - A free Color Management System (use this if you need precise color conversions)
*
* \section cci Color Component Intervals
* \par
* When minimum and maximum values must be pre-defined values,
* the following values are used:
* \par
\verbatim
byte [0,255] (1 byte)
short [-32768,32767] (2 bytes)
ushort [0,65535] (2 bytes)
int [-8388608,+8388607] (3 bytes of 4 possible)
float [0,1] (4 bytes)
double [0,1] (8 bytes)
\endverbatim
* Usually this intervals are used when converting from real to integer,
* and when demoting an integer data type.
* \ingroup util */
/** Returns the zero value for YCbCr color conversion. \n
* When data type is unsigned Cb and Cr are shifted to 0-max.
* So before they can be used in conversion equations
* Cb and Cr values must be shifted back to fix the zero position.
* \ingroup color */
inline double imColorZeroShift(int data_type)
{
double zero[] = { 128.0, // [-128,+127]
0,
32768.0, // [-32768,+32767]
0,
0,
0,
0,
0 };
return zero[data_type];
}
/** Returns the maximum value for pre-defined color conversion purposes. \n
* See \ref cci.
* \ingroup color */
inline int imColorMax(int data_type)
{
int max[] = {255,
32767,
65535,
8388607,
1,
1,
0,
0 };
return max[data_type];
}
/** Returns the minimum value for pre-defined color conversion purposes. \n
* See \ref cci.
* \ingroup color */
inline int imColorMin(int data_type)
{
int min[] = {0,
-32768,
0,
-8388608,
0,
0,
0,
0 };
return min[data_type];
}
/** Quantize 0-1 values into min-max. \n
* Value are usually integers,
* but the dummy quantizer uses real values.
* See also \ref math.
* \ingroup color */
template <class T, class R>
inline T imColorQuantize(const R& value, const T& min, const T& max)
{
if (max == 1) return (T)value; // to allow a dummy quantizer
if (value >= 1) return max;
if (value <= 0) return min;
R range = (R)max - (R)min + (R)1;
return (T)imRound(value*range - (R)0.5) + min;
}
/** Reconstruct min-max values into 0-1. \n
* Values are usually integers,
* but the dummy re-constructor uses real values.
* See also \ref math.
* \ingroup color */
template <class T>
inline double imColorReconstruct(const T& value, const T& min, const T& max)
{
if (max == 1) return (double)value; // to allow a dummy re-constructor
if (value <= min) return 0;
if (value >= max) return 1;
double range = (double)max - (double)min + 1.0;
return (((double)value - (double)min + 0.5) / range);
}
/** Converts Y'CbCr to R'G'B' (all nonlinear). \n
* ITU-R Recommendation 601-1 with no headroom/footroom.
\verbatim
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
\endverbatim
* \ingroup color */
template <class T>
inline void imColorYCbCr2RGB(const T Y, const T Cb, const T Cr,
T& R, T& G, T& B,
const T& zero, const T& min, const T& max)
{
double r = double(Y + 1.402 * (Cr - zero));
double g = double(Y - 0.344 * (Cb - zero) - 0.714 * (Cr - zero));
double b = double(Y + 1.772 * (Cb - zero));
// now we should enforce min <= rgb <= max
R = (T)IM_CROPMINMAX(r, min, max);
G = (T)IM_CROPMINMAX(g, min, max);
B = (T)IM_CROPMINMAX(b, min, max);
}
/** Converts R'G'B' to Y'CbCr (all nonlinear). \n
* ITU-R Recommendation 601-1 with no headroom/footroom.
\verbatim
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'
\endverbatim
* \ingroup color */
template <class T>
inline void imColorRGB2YCbCr(const T R, const T G, const T B,
T& Y, T& Cb, T& Cr,
const T& zero)
{
Y = (T)( 0.299 *R + 0.587 *G + 0.114 *B);
Cb = (T)(-0.169 *R - 0.331 *G + 0.500 *B + (double)zero);
Cr = (T)( 0.500 *R - 0.419 *G - 0.081 *B + (double)zero);
}
/** Converts C'M'Y'K' to R'G'B' (all nonlinear). \n
* This is a poor conversion that works for a simple visualization.
\verbatim
0 <= CMYK <= 1 ; 0 <= RGB <= 1
R = (1 - K) * (1 - C)
G = (1 - K) * (1 - M)
B = (1 - K) * (1 - Y)
\endverbatim
* \ingroup color */
template <class T>
inline void imColorCMYK2RGB(const T C, const T M, const T Y, const T K,
T& R, T& G, T& B, const T& max)
{
T W = max - K;
R = (T)((W * (max - C)) / max);
G = (T)((W * (max - M)) / max);
B = (T)((W * (max - Y)) / max);
}
/** Converts CIE XYZ to Rec 709 RGB (all linear). \n
* ITU-R Recommendation BT.709 (D65 white point). \n
\verbatim
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
\endverbatim
* \ingroup color */
template <class T>
inline void imColorXYZ2RGB(const T X, const T Y, const T Z,
T& R, T& G, T& B)
{
double r = 3.2406 *X - 1.5372 *Y - 0.4986 *Z;
double g = -0.9689 *X + 1.8758 *Y + 0.0415 *Z;
double b = 0.0557 *X - 0.2040 *Y + 1.0570 *Z;
// we need to crop because not all XYZ colors are visible
R = (T)IM_FLOATCROP(r);
G = (T)IM_FLOATCROP(g);
B = (T)IM_FLOATCROP(b);
}
/** Converts Rec 709 RGB to CIE XYZ (all linear). \n
* ITU-R Recommendation BT.709 (D65 white point). \n
\verbatim
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
\endverbatim
* \ingroup color */
template <class T>
inline void imColorRGB2XYZ(const T R, const T G, const T B,
T& X, T& Y, T& Z)
{
X = (T)(0.4124 *R + 0.3576 *G + 0.1805 *B);
Y = (T)(0.2126 *R + 0.7152 *G + 0.0722 *B);
Z = (T)(0.0193 *R + 0.1192 *G + 0.9505 *B);
}
#define IM_FWLAB(_w) (_w > 0.008856? \
pow(_w, 1.0/3.0): \
7.787 * _w + 0.16/1.16)
/** Converts CIE XYZ (linear) to CIE L*a*b* (nonlinear). \n
* The white point is D65. \n
\verbatim
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)
\endverbatim
* \ingroup color */
inline void imColorXYZ2Lab(const double X, const double Y, const double Z,
double& L, double& a, double& b)
{
double fX = X / 0.9505; // white point D65
double fY = Y / 1.0;
double fZ = Z / 1.0890;
fX = IM_FWLAB(fX);
fY = IM_FWLAB(fY);
fZ = IM_FWLAB(fZ);
L = 1.16 * fY - 0.16;
a = 2.5 * (fX - fY);
b = (fY - fZ);
}
#define IM_GWLAB(_w) (_w > 0.20689? \
pow(_w, 3.0): \
0.1284 * (_w - 0.16/1.16))
/** Converts CIE L*a*b* (nonlinear) to CIE XYZ (linear). \n
* The white point is D65. \n
* 0 <= L <= 1 ; -0.5 <= ab <= +0.5 ; 0 <= XYZ <= 1
* \ingroup color */
inline void imColorLab2XYZ(const double L, const double a, const double b,
double& X, double& Y, double& Z)
{
double fY = (L + 0.16) / 1.16;
double gY = IM_GWLAB(fY);
double fgY = IM_FWLAB(gY);
double gX = fgY + a / 2.5;
double gZ = fgY - b;
gX = IM_GWLAB(gX);
gZ = IM_GWLAB(gZ);
X = gX * 0.9505; // white point D65
Y = gY * 1.0;
Z = gZ * 1.0890;
}
/** Converts CIE XYZ (linear) to CIE L*u*v* (nonlinear). \n
* The white point is D65. \n
\verbatim
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)
\endverbatim
* \ingroup color */
inline void imColorXYZ2Luv(const double X, const double Y, const double Z,
double& L, double& u, double& v)
{
double XYZ = (double)(X + 15 * Y + 3 * Z);
double fY = Y / 1.0;
if (XYZ != 0)
{
L = 1.16 * IM_FWLAB(fY) - 0.16;
u = 6.5 * L * ((4 * X)/XYZ - 0.1978);
v = 6.5 * L * ((9 * Y)/XYZ - 0.4683);
}
else
{
L = u = v = 0;
}
}
/** Converts CIE L*u*v* (nonlinear) to CIE XYZ (linear). \n
* The white point is D65.
* 0 <= L <= 1 ; -0.5 <= uv <= +0.5 ; 0 <= XYZ <= 1 \n
* \ingroup color */
inline void imColorLuv2XYZ(const double L, const double u, const double v,
double& X, double& Y, double& Z)
{
double fY = (L + 0.16) / 1.16;
Y = IM_GWLAB(fY) * 1.0;
double ul = 0.1978, vl = 0.4683;
if (L != 0)
{
ul = u / (6.5 * L) + 0.1978;
vl = v / (6.5 * L) + 0.4683;
}
X = ((9 * ul) / (4 * vl)) * Y;
Z = ((12 - 3 * ul - 20 * vl) / (4 * vl)) * Y;
}
/** Converts nonlinear values to linear values. \n
* We use the sRGB transfer function. sRGB uses ITU-R 709 primaries and D65 white point. \n
\verbatim
0 <= l <= 1 ; 0 <= v <= 1
if (v < 0.03928)
l = v / 12.92
else
l = pow((v + 0.055) / 1.055, 2.4)
\endverbatim
* \ingroup color */
inline double imColorTransfer2Linear(const double& nonlinear_value)
{
if (nonlinear_value < 0.03928)
return nonlinear_value / 12.92;
else
return pow((nonlinear_value + 0.055) / 1.055, 2.4);
}
/** Converts linear values to nonlinear values. \n
* We use the sRGB transfer function. sRGB uses ITU-R 709 primaries and D65 white point. \n
\verbatim
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
\endverbatim
* \ingroup color */
inline double imColorTransfer2Nonlinear(const double& value)
{
if (value < 0.0031308)
return 12.92 * value;
else
return 1.055 * pow(value, 1.0/2.4) - 0.055;
}
/** Converts RGB (linear) to R'G'B' (nonlinear).
* \ingroup color */
inline void imColorRGB2RGBNonlinear(const double RL, const double GL, const double BL,
double& R, double& G, double& B)
{
R = imColorTransfer2Nonlinear(RL);
G = imColorTransfer2Nonlinear(GL);
B = imColorTransfer2Nonlinear(BL);
}
/** Converts R'G'B' to Y' (all nonlinear). \n
\verbatim
Y' = 0.299 *R' + 0.587 *G' + 0.114 *B'
\endverbatim
* \ingroup color */
template <class T>
inline T imColorRGB2Luma(const T R, const T G, const T B)
{
return (T)((299 * R + 587 * G + 114 * B) / 1000);
}
/** Converts Luminance (CIE Y) to Lightness (CIE L*) (all linear). \n
* The white point is D65.
\verbatim
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
\endverbatim
* \ingroup color */
inline double imColorLuminance2Lightness(const double& Y)
{
return 1.16 * IM_FWLAB(Y) - 0.16;
}
/** Converts Lightness (CIE L*) to Luminance (CIE Y) (all linear). \n
* The white point is D65.
\verbatim
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)
\endverbatim
* \ingroup color */
inline double imColorLightness2Luminance(const double& L)
{
double fY = (L + 0.16) / 1.16;
return IM_GWLAB(fY);
}
#undef IM_FWLAB
#undef IM_GWLAB
#undef IM_CROPL
#undef IM_CROPC
#endif