5#error "cielab_impl.h should not be included directly; include cielab.h instead"
12constexpr double DELTA{6.0 / 29.0};
13constexpr double DELTA_CUBED{DELTA * DELTA * DELTA};
14constexpr double KAPPA{1.0 / (3.0 * DELTA * DELTA)};
15constexpr double EPSILON{16.0 / 116.0};
18constexpr double SRGB_LINEAR_THRESHOLD{0.04045};
19constexpr double SRGB_LINEAR_FACTOR{12.92};
20constexpr double SRGB_GAMMA_OFFSET{0.055};
21constexpr double SRGB_GAMMA{2.4};
22constexpr double SRGB_GAMMA_INV{1.0 / SRGB_GAMMA};
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};
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};
75constexpr double D65_Xn{0.95047};
76constexpr double D65_Yn{1.0};
77constexpr double D65_Zn{1.08883};
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};
85inline double xyz_to_lab(
const double t) {
87 const double safe_t{std::max(0.0, t)};
88 return (safe_t > DELTA_CUBED) ? std::cbrt(safe_t) : (KAPPA * safe_t) + EPSILON;
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);
100template <
typename Tin,
typename Tout>
101void rgb_to_lab(
const Tin r_u8,
const Tin g_u8,
const Tin b_u8, Tout &out_l, Tout &out_a,
105 double _r =
static_cast<double>(r_u8);
106 double _g =
static_cast<double>(g_u8);
107 double _b =
static_cast<double>(b_u8);
109 double r{srgb_to_linear(_r / 255.0)};
110 double g{srgb_to_linear(_g / 255.0)};
111 double b{srgb_to_linear(_b / 255.0)};
115 const double x{SRGB_R_TO_X * r + SRGB_G_TO_X * g + SRGB_B_TO_X * b};
116 const double y{SRGB_R_TO_Y * r + SRGB_G_TO_Y * g + SRGB_B_TO_Y * b};
117 const double z{SRGB_R_TO_Z * r + SRGB_G_TO_Z * g + SRGB_B_TO_Z * b};
120 const double Xr{x / D65_Xn};
121 const double Yr{y / D65_Yn};
122 const double Zr{z / D65_Zn};
125 const double fx{xyz_to_lab(Xr)};
126 const double fy{xyz_to_lab(Yr)};
127 const double fz{xyz_to_lab(Zr)};
130 out_l =
static_cast<Tout
>(LAB_L_FACTOR * fy - LAB_L_OFFSET);
131 out_a =
static_cast<Tout
>(LAB_A_FACTOR * (fx - fy));
132 out_b =
static_cast<Tout
>(LAB_B_FACTOR * (fy - fz));
134 out_l = std::clamp(out_l,
static_cast<Tout
>(0.0),
static_cast<Tout
>(100.0));
137constexpr double inverse_xyz_to_lab(
double t) {
138 return (t > DELTA) ? (t * t * t) : (3 * DELTA * DELTA * (t - EPSILON));
141inline double gamma_encode(
double u) {
143 u = std::max(0.0, u);
144 return (u <= SRGB_LINEAR_THRESHOLD / SRGB_LINEAR_FACTOR)
145 ? SRGB_LINEAR_FACTOR * u
146 : (1.0 + SRGB_GAMMA_OFFSET) * std::pow(u, SRGB_GAMMA_INV) - SRGB_GAMMA_OFFSET;
149template <
typename Tin,
typename Tout>
150void lab_to_rgb(
const Tin L,
const Tin A,
const Tin B, Tout &out_r_u8, Tout &out_g_u8,
152 const double _L =
static_cast<double>(L);
153 const double _A =
static_cast<double>(A);
154 const double _B =
static_cast<double>(B);
157 const double fy{(_L + LAB_L_OFFSET) / LAB_L_FACTOR};
158 const double fx{fy + _A / LAB_A_FACTOR};
159 const double fz{fy - _B / LAB_B_FACTOR};
161 const double X{D65_Xn * inverse_xyz_to_lab(fx)};
162 const double Y{D65_Yn * inverse_xyz_to_lab(fy)};
163 const double Z{D65_Zn * inverse_xyz_to_lab(fz)};
166 double r{SRGB_X_TO_R * X + SRGB_Y_TO_R * Y + SRGB_Z_TO_R * Z};
167 double g{SRGB_X_TO_G * X + SRGB_Y_TO_G * Y + SRGB_Z_TO_G * Z};
168 double b{SRGB_X_TO_B * X + SRGB_Y_TO_B * Y + SRGB_Z_TO_B * Z};
171 r = gamma_encode(std::clamp(r, 0.0, 1.0));
172 g = gamma_encode(std::clamp(g, 0.0, 1.0));
173 b = gamma_encode(std::clamp(b, 0.0, 1.0));
176 out_r_u8 =
static_cast<Tout
>(std::round(255.0 * std::clamp(r, 0.0, 1.0)));
177 out_g_u8 =
static_cast<Tout
>(std::round(255.0 * std::clamp(g, 0.0, 1.0)));
178 out_b_u8 =
static_cast<Tout
>(std::round(255.0 * std::clamp(b, 0.0, 1.0)));
181template <
typename Tin,
typename Tout>
187 rgb_to_lab(rgba.red, rgba.green, rgba.blue, l, a, b);
193 laba.alpha =
static_cast<Tout
>(rgba.alpha);
196template <
typename Tin,
typename Tout>
198 rgb_to_lab<Tin, Tout>(rgb.red, rgb.green, rgb.blue, lab.l, lab.a, lab.b);
201template <
typename Tin,
typename Tout>
207 lab_to_rgb<Tin, Tout>(laba.l, laba.a, laba.b, r, g, b);
213 rgba.alpha =
static_cast<Tout
>(laba.alpha);
216template <
typename Tin,
typename Tout>
218 lab_to_rgb<Tin, Tout>(lab.l, lab.a, lab.b, rgb.red, rgb.green, rgb.blue);