Img2Num C++ (Internal Developer Docs) dev
API Documentation
Loading...
Searching...
No Matches
cielab_impl.h
1#ifndef CIELAB_IMPL_H
2#define CIELAB_IMPL_H
3
4#ifndef CIELAB_H
5#error "cielab_impl.h should not be included directly; include cielab.h instead"
6#endif
7
8#include <algorithm>
9#include <cmath>
10
11// ====== Used in xyz_to_lab =======
12constexpr double DELTA {6.0 / 29.0}; // 0.2068966
13constexpr double DELTA_CUBED {DELTA * DELTA * DELTA}; // 0.008856
14constexpr double KAPPA {1.0 / (3.0 * DELTA * DELTA)}; // 7.787
15constexpr double EPSILON {16.0 / 116.0}; // 0.137931
16
17// ====== Used in srgb_to_linear ======
18constexpr double SRGB_LINEAR_THRESHOLD {0.04045}; // linear segment boundary
19constexpr double SRGB_LINEAR_FACTOR {12.92}; // scale factor for linear segment
20constexpr double SRGB_GAMMA_OFFSET {0.055}; // offset for nonlinear segment
21constexpr double SRGB_GAMMA {2.4}; // gamma exponent for nonlinear segment
22constexpr double SRGB_GAMMA_INV {1.0 / SRGB_GAMMA}; // gamma exponent for nonlinear segment
23
24/*
25 * ====== Used in rgb_to_lab ======
26 *
27 * sRGB Khronos/W3C Transformation Matrices (D65 illuminant)
28 *
29 * sRGB → CIE XYZ:
30 * ┌ ┐ ┌ ┐ ┌ ┐
31 * │ X │ │ 0.4124564 0.3575761 0.1804375 │ │ R │
32 * │ Y │ = │ 0.2126729 0.7151522 0.0721750 │ │ G │
33 * │ Z │ │ 0.0193339 0.1191920 0.9503041 │ │ B │
34 * └ ┘ └ ┘ └ ┘
35 *
36 * Reference: ITU-R BT.709 / sRGB standard (IEC 61966-2-1:1999)
37 */
38constexpr double SRGB_R_TO_X {0.4124564};
39constexpr double SRGB_G_TO_X {0.3575761};
40constexpr double SRGB_B_TO_X {0.1804375};
41constexpr double SRGB_R_TO_Y {0.2126729};
42constexpr double SRGB_G_TO_Y {0.7151522};
43constexpr double SRGB_B_TO_Y {0.0721750};
44constexpr double SRGB_R_TO_Z {0.0193339};
45constexpr double SRGB_G_TO_Z {0.1191920};
46constexpr double SRGB_B_TO_Z {0.9503041};
47
48/*
49 * ====== Used in lab_to_rgb ======
50 *
51 * sRGB Khronos/W3C Transformation Matrices (D65 illuminant)
52 * Inverse transformation (XYZ → sRGB) per Khronos/W3C, D65 white, slightly
53 * different from original BT.709 inverse.
54 *
55 * CIE XYZ → sRGB (inverse):
56 * ┌ ┐ ┌ ┐ ┌ ┐
57 * │ R │ │ 3.240970 -1.537383 -0.498611 │ │ X │
58 * │ G │ = │ -0.969244 1.875968 0.041555 │ │ Y │
59 * │ B │ │ 0.055630 -0.203977 1.056972 │ │ Z │
60 * └ ┘ └ ┘ └ ┘
61 *
62 * Reference: ITU-R BT.709 / sRGB standard (IEC 61966-2-1:1999)
63 */
64constexpr double SRGB_X_TO_R {3.240970};
65constexpr double SRGB_Y_TO_R {-1.537383};
66constexpr double SRGB_Z_TO_R {-0.498611};
67constexpr double SRGB_X_TO_G {-0.969244};
68constexpr double SRGB_Y_TO_G {1.875968};
69constexpr double SRGB_Z_TO_G {0.041555};
70constexpr double SRGB_X_TO_B {0.055630};
71constexpr double SRGB_Y_TO_B {-0.203977};
72constexpr double SRGB_Z_TO_B {1.056972};
73
74// Reference white point for D65 illuminant
75constexpr double D65_Xn {0.95047};
76constexpr double D65_Yn {1.0};
77constexpr double D65_Zn {1.08883};
78
79constexpr double LAB_L_FACTOR {116.0};
80constexpr double LAB_L_OFFSET {16.0};
81constexpr double LAB_A_FACTOR {500.0};
82constexpr double LAB_B_FACTOR {200.0};
83
84// Function for the non-linear XYZ to Lab transformation
85inline double xyz_to_lab(const double t) {
86 // prevent negative due to tiny floating errors
87 const double safe_t {std::max(0.0, t)};
88 return (safe_t > DELTA_CUBED) ? std::cbrt(safe_t) : (KAPPA * safe_t) + EPSILON;
89}
90
91// Function for the non-linear sRGB to linear RGB transformation (inverse gamma
92// correction)
93inline double srgb_to_linear(const double c) {
94 const double safe_c {std::clamp(c, 0.0, 1.0)};
95 return (safe_c <= SRGB_LINEAR_THRESHOLD)
96 ? safe_c / SRGB_LINEAR_FACTOR
97 : std::pow((safe_c + SRGB_GAMMA_OFFSET) / (1.0 + SRGB_GAMMA_OFFSET), SRGB_GAMMA);
98}
99
100template <typename Tin, typename Tout>
101void rgb_to_lab(
102 const Tin r_u8, const Tin g_u8, const Tin b_u8, Tout& out_l, Tout& out_a, Tout& out_b
103) {
104 // 1. Convert 8-bit RGB [0, 255] to linear RGB [0.0, 1.0]
105
106 double _r = static_cast<double>(r_u8);
107 double _g = static_cast<double>(g_u8);
108 double _b = static_cast<double>(b_u8);
109
110 double r {srgb_to_linear(_r / 255.0)};
111 double g {srgb_to_linear(_g / 255.0)};
112 double b {srgb_to_linear(_b / 255.0)};
113
114 // 2. Convert linear RGB to CIE XYZ (using D65 white point reference)
115 // The matrix below is for sRGB to XYZ (D65)
116 const double x {SRGB_R_TO_X * r + SRGB_G_TO_X * g + SRGB_B_TO_X * b};
117 const double y {SRGB_R_TO_Y * r + SRGB_G_TO_Y * g + SRGB_B_TO_Y * b};
118 const double z {SRGB_R_TO_Z * r + SRGB_G_TO_Z * g + SRGB_B_TO_Z * b};
119
120 // Normalize XYZ values by the white point
121 const double Xr {x / D65_Xn};
122 const double Yr {y / D65_Yn};
123 const double Zr {z / D65_Zn};
124
125 // 3. Convert CIE XYZ to CIE L*a*b*
126 const double fx {xyz_to_lab(Xr)};
127 const double fy {xyz_to_lab(Yr)};
128 const double fz {xyz_to_lab(Zr)};
129
130 // 4. Output values
131 out_l = static_cast<Tout>(LAB_L_FACTOR * fy - LAB_L_OFFSET);
132 out_a = static_cast<Tout>(LAB_A_FACTOR * (fx - fy));
133 out_b = static_cast<Tout>(LAB_B_FACTOR * (fy - fz));
134
135 out_l = std::clamp(out_l, static_cast<Tout>(0.0), static_cast<Tout>(100.0));
136}
137
138constexpr double inverse_xyz_to_lab(double t) {
139 return (t > DELTA) ? (t * t * t) : (3 * DELTA * DELTA * (t - EPSILON));
140}
141
142inline double gamma_encode(double u) {
143 // Guard against negative values from out-of-gamut colors
144 u = std::max(0.0, u);
145 return (u <= SRGB_LINEAR_THRESHOLD / SRGB_LINEAR_FACTOR)
146 ? SRGB_LINEAR_FACTOR * u
147 : (1.0 + SRGB_GAMMA_OFFSET) * std::pow(u, SRGB_GAMMA_INV) - SRGB_GAMMA_OFFSET;
148}
149
150template <typename Tin, typename Tout>
151void lab_to_rgb(
152 const Tin L, const Tin A, const Tin B, Tout& out_r_u8, Tout& out_g_u8, Tout& out_b_u8
153) {
154 const double _L = static_cast<double>(L);
155 const double _A = static_cast<double>(A);
156 const double _B = static_cast<double>(B);
157
158 // --- Lab → XYZ (D65 white point)
159 const double fy {(_L + LAB_L_OFFSET) / LAB_L_FACTOR};
160 const double fx {fy + _A / LAB_A_FACTOR};
161 const double fz {fy - _B / LAB_B_FACTOR};
162
163 const double X {D65_Xn * inverse_xyz_to_lab(fx)};
164 const double Y {D65_Yn * inverse_xyz_to_lab(fy)};
165 const double Z {D65_Zn * inverse_xyz_to_lab(fz)};
166
167 // --- XYZ → linear RGB (sRGB)
168 double r {SRGB_X_TO_R * X + SRGB_Y_TO_R * Y + SRGB_Z_TO_R * Z};
169 double g {SRGB_X_TO_G * X + SRGB_Y_TO_G * Y + SRGB_Z_TO_G * Z};
170 double b {SRGB_X_TO_B * X + SRGB_Y_TO_B * Y + SRGB_Z_TO_B * Z};
171
172 // --- linear RGB → sRGB (gamma correction)
173 r = gamma_encode(std::clamp(r, 0.0, 1.0));
174 g = gamma_encode(std::clamp(g, 0.0, 1.0));
175 b = gamma_encode(std::clamp(b, 0.0, 1.0));
176
177 // --- Clamp and convert to 8-bit
178 out_r_u8 = static_cast<Tout>(std::round(255.0 * std::clamp(r, 0.0, 1.0)));
179 out_g_u8 = static_cast<Tout>(std::round(255.0 * std::clamp(g, 0.0, 1.0)));
180 out_b_u8 = static_cast<Tout>(std::round(255.0 * std::clamp(b, 0.0, 1.0)));
181}
182
183template <typename Tin, typename Tout>
184void rgb_to_lab(const ImageLib::RGBAPixel<Tin>& rgba, ImageLib::LABAPixel<Tout>& laba) {
185 Tout l {laba.l};
186 Tout a {laba.a};
187 Tout b {laba.b};
188
189 rgb_to_lab(rgba.red, rgba.green, rgba.blue, l, a, b);
190
191 laba.l = l;
192 laba.a = a;
193 laba.b = b;
194
195 laba.alpha = static_cast<Tout>(rgba.alpha);
196}
197
198template <typename Tin, typename Tout>
199void rgb_to_lab(const ImageLib::RGBPixel<Tin>& rgb, ImageLib::LABPixel<Tout>& lab) {
200 rgb_to_lab<Tin, Tout>(rgb.red, rgb.green, rgb.blue, lab.l, lab.a, lab.b);
201}
202
203template <typename Tin, typename Tout>
204void lab_to_rgb(const ImageLib::LABAPixel<Tin>& laba, ImageLib::RGBAPixel<Tout>& rgba) {
205 Tout r {rgba.red};
206 Tout g {rgba.green};
207 Tout b {rgba.blue};
208
209 lab_to_rgb<Tin, Tout>(laba.l, laba.a, laba.b, r, g, b);
210
211 rgba.red = r;
212 rgba.green = g;
213 rgba.blue = b;
214
215 rgba.alpha = static_cast<Tout>(laba.alpha);
216}
217
218template <typename Tin, typename Tout>
219void lab_to_rgb(const ImageLib::LABPixel<Tin>& lab, ImageLib::RGBPixel<Tout>& rgb) {
220 lab_to_rgb<Tin, Tout>(lab.l, lab.a, lab.b, rgb.red, rgb.green, rgb.blue);
221}
222
223#endif