File indexing completed on 2024-05-05 05:53:45
0001 /* 0002 * HSLuv-C: Human-friendly HSL 0003 * <https://github.com/hsluv/hsluv-c> 0004 * <https://www.hsluv.org/> 0005 * 0006 * SPDX-FileCopyrightText: 2015 Alexei Boronine <alexei@boronine.com> (original idea, JavaScript implementation) 0007 * SPDX-FileCopyrightText: 2015 Roger Tallada <roger.tallada@gmail.com> (Obj-C implementation) 0008 * SPDX-FileCopyrightText: 2017 Martin Mitas <mity@morous.org> (C implementation, based on Obj-C implementation) 0009 * SPDX-License-Identifier: MIT 0010 */ 0011 0012 #include "hsluv.h" 0013 0014 #include <float.h> 0015 #include <math.h> 0016 0017 typedef struct Triplet_tag Triplet; 0018 struct Triplet_tag { 0019 double a; 0020 double b; 0021 double c; 0022 }; 0023 0024 /* for RGB */ 0025 static const Triplet m[3] = {{3.24096994190452134377, -1.53738317757009345794, -0.49861076029300328366}, 0026 {-0.96924363628087982613, 1.87596750150772066772, 0.04155505740717561247}, 0027 {0.05563007969699360846, -0.20397695888897656435, 1.05697151424287856072}}; 0028 0029 /* for XYZ */ 0030 static const Triplet m_inv[3] = {{0.41239079926595948129, 0.35758433938387796373, 0.18048078840183428751}, 0031 {0.21263900587151035754, 0.71516867876775592746, 0.07219231536073371500}, 0032 {0.01933081871559185069, 0.11919477979462598791, 0.95053215224966058086}}; 0033 0034 static const double ref_u = 0.19783000664283680764; 0035 static const double ref_v = 0.46831999493879100370; 0036 0037 static const double kappa = 903.29629629629629629630; 0038 static const double epsilon = 0.00885645167903563082; 0039 0040 typedef struct Bounds_tag Bounds; 0041 struct Bounds_tag { 0042 double a; 0043 double b; 0044 }; 0045 0046 static void get_bounds(double l, Bounds bounds[6]) 0047 { 0048 double tl = l + 16.0; 0049 double sub1 = (tl * tl * tl) / 1560896.0; 0050 double sub2 = (sub1 > epsilon ? sub1 : (l / kappa)); 0051 int channel; 0052 int t; 0053 0054 for (channel = 0; channel < 3; channel++) { 0055 double m1 = m[channel].a; 0056 double m2 = m[channel].b; 0057 double m3 = m[channel].c; 0058 0059 for (t = 0; t < 2; t++) { 0060 double top1 = (284517.0 * m1 - 94839.0 * m3) * sub2; 0061 double top2 = (838422.0 * m3 + 769860.0 * m2 + 731718.0 * m1) * l * sub2 - 769860.0 * t * l; 0062 double bottom = (632260.0 * m3 - 126452.0 * m2) * sub2 + 126452.0 * t; 0063 0064 bounds[channel * 2 + t].a = top1 / bottom; 0065 bounds[channel * 2 + t].b = top2 / bottom; 0066 } 0067 } 0068 } 0069 0070 static double intersect_line_line(const Bounds *line1, const Bounds *line2) 0071 { 0072 return (line1->b - line2->b) / (line2->a - line1->a); 0073 } 0074 0075 static double dist_from_pole_squared(double x, double y) 0076 { 0077 return x * x + y * y; 0078 } 0079 0080 static double ray_length_until_intersect(double theta, const Bounds *line) 0081 { 0082 return line->b / (sin(theta) - line->a * cos(theta)); 0083 } 0084 0085 static double max_safe_chroma_for_l(double l) 0086 { 0087 double min_len_squared = DBL_MAX; 0088 Bounds bounds[6]; 0089 int i; 0090 0091 get_bounds(l, bounds); 0092 for (i = 0; i < 6; i++) { 0093 double m1 = bounds[i].a; 0094 double b1 = bounds[i].b; 0095 /* x where line intersects with perpendicular running though (0, 0) */ 0096 Bounds line2 = {-1.0 / m1, 0.0}; 0097 double x = intersect_line_line(&bounds[i], &line2); 0098 double distance = dist_from_pole_squared(x, b1 + x * m1); 0099 0100 if (distance < min_len_squared) 0101 min_len_squared = distance; 0102 } 0103 0104 return sqrt(min_len_squared); 0105 } 0106 0107 static double max_chroma_for_lh(double l, double h) 0108 { 0109 double min_len = DBL_MAX; 0110 double hrad = h * 0.01745329251994329577; /* (2 * pi / 360) */ 0111 Bounds bounds[6]; 0112 int i; 0113 0114 get_bounds(l, bounds); 0115 for (i = 0; i < 6; i++) { 0116 double len = ray_length_until_intersect(hrad, &bounds[i]); 0117 0118 if (len >= 0 && len < min_len) 0119 min_len = len; 0120 } 0121 return min_len; 0122 } 0123 0124 static double dot_product(const Triplet *t1, const Triplet *t2) 0125 { 0126 return (t1->a * t2->a + t1->b * t2->b + t1->c * t2->c); 0127 } 0128 0129 /* Used for rgb conversions */ 0130 static double from_linear(double c) 0131 { 0132 if (c <= 0.0031308) 0133 return 12.92 * c; 0134 else 0135 return 1.055 * pow(c, 1.0 / 2.4) - 0.055; 0136 } 0137 0138 static double to_linear(double c) 0139 { 0140 if (c > 0.04045) 0141 return pow((c + 0.055) / 1.055, 2.4); 0142 else 0143 return c / 12.92; 0144 } 0145 0146 static void xyz2rgb(Triplet *in_out) 0147 { 0148 double r = from_linear(dot_product(&m[0], in_out)); 0149 double g = from_linear(dot_product(&m[1], in_out)); 0150 double b = from_linear(dot_product(&m[2], in_out)); 0151 in_out->a = r; 0152 in_out->b = g; 0153 in_out->c = b; 0154 } 0155 0156 static void rgb2xyz(Triplet *in_out) 0157 { 0158 Triplet rgbl = {to_linear(in_out->a), to_linear(in_out->b), to_linear(in_out->c)}; 0159 double x = dot_product(&m_inv[0], &rgbl); 0160 double y = dot_product(&m_inv[1], &rgbl); 0161 double z = dot_product(&m_inv[2], &rgbl); 0162 in_out->a = x; 0163 in_out->b = y; 0164 in_out->c = z; 0165 } 0166 0167 /* https://en.wikipedia.org/wiki/CIELUV 0168 * In these formulas, Yn refers to the reference white point. We are using 0169 * illuminant D65, so Yn (see refY in Maxima file) equals 1. The formula is 0170 * simplified accordingly. 0171 */ 0172 static double y2l(double y) 0173 { 0174 if (y <= epsilon) 0175 return y * kappa; 0176 else 0177 return 116.0 * cbrt(y) - 16.0; 0178 } 0179 0180 static double l2y(double l) 0181 { 0182 if (l <= 8.0) { 0183 return l / kappa; 0184 } else { 0185 double x = (l + 16.0) / 116.0; 0186 return (x * x * x); 0187 } 0188 } 0189 0190 static void xyz2luv(Triplet *in_out) 0191 { 0192 double var_u = (4.0 * in_out->a) / (in_out->a + (15.0 * in_out->b) + (3.0 * in_out->c)); 0193 double var_v = (9.0 * in_out->b) / (in_out->a + (15.0 * in_out->b) + (3.0 * in_out->c)); 0194 double l = y2l(in_out->b); 0195 double u = 13.0 * l * (var_u - ref_u); 0196 double v = 13.0 * l * (var_v - ref_v); 0197 0198 in_out->a = l; 0199 if (l < 0.00000001) { 0200 in_out->b = 0.0; 0201 in_out->c = 0.0; 0202 } else { 0203 in_out->b = u; 0204 in_out->c = v; 0205 } 0206 } 0207 0208 static void luv2xyz(Triplet *in_out) 0209 { 0210 if (in_out->a <= 0.00000001) { 0211 /* Black will create a divide-by-zero error. */ 0212 in_out->a = 0.0; 0213 in_out->b = 0.0; 0214 in_out->c = 0.0; 0215 return; 0216 } 0217 0218 double var_u = in_out->b / (13.0 * in_out->a) + ref_u; 0219 double var_v = in_out->c / (13.0 * in_out->a) + ref_v; 0220 double y = l2y(in_out->a); 0221 double x = -(9.0 * y * var_u) / ((var_u - 4.0) * var_v - var_u * var_v); 0222 double z = (9.0 * y - (15.0 * var_v * y) - (var_v * x)) / (3.0 * var_v); 0223 in_out->a = x; 0224 in_out->b = y; 0225 in_out->c = z; 0226 } 0227 0228 static void luv2lch(Triplet *in_out) 0229 { 0230 double l = in_out->a; 0231 double u = in_out->b; 0232 double v = in_out->c; 0233 double h; 0234 double c = sqrt(u * u + v * v); 0235 0236 /* Grays: disambiguate hue */ 0237 if (c < 0.00000001) { 0238 h = 0; 0239 } else { 0240 h = atan2(v, u) * 57.29577951308232087680; /* (180 / pi) */ 0241 if (h < 0.0) 0242 h += 360.0; 0243 } 0244 0245 in_out->a = l; 0246 in_out->b = c; 0247 in_out->c = h; 0248 } 0249 0250 static void lch2luv(Triplet *in_out) 0251 { 0252 double hrad = in_out->c * 0.01745329251994329577; /* (pi / 180.0) */ 0253 double u = cos(hrad) * in_out->b; 0254 double v = sin(hrad) * in_out->b; 0255 0256 in_out->b = u; 0257 in_out->c = v; 0258 } 0259 0260 static void hsluv2lch(Triplet *in_out) 0261 { 0262 double h = in_out->a; 0263 double s = in_out->b; 0264 double l = in_out->c; 0265 double c; 0266 0267 /* White and black: disambiguate chroma */ 0268 if (l > 99.9999999 || l < 0.00000001) 0269 c = 0.0; 0270 else 0271 c = max_chroma_for_lh(l, h) / 100.0 * s; 0272 0273 /* Grays: disambiguate hue */ 0274 if (s < 0.00000001) 0275 h = 0.0; 0276 0277 in_out->a = l; 0278 in_out->b = c; 0279 in_out->c = h; 0280 } 0281 0282 static void lch2hsluv(Triplet *in_out) 0283 { 0284 double l = in_out->a; 0285 double c = in_out->b; 0286 double h = in_out->c; 0287 double s; 0288 0289 /* White and black: disambiguate saturation */ 0290 if (l > 99.9999999 || l < 0.00000001) 0291 s = 0.0; 0292 else 0293 s = c / max_chroma_for_lh(l, h) * 100.0; 0294 0295 /* Grays: disambiguate hue */ 0296 if (c < 0.00000001) 0297 h = 0.0; 0298 0299 in_out->a = h; 0300 in_out->b = s; 0301 in_out->c = l; 0302 } 0303 0304 static void hpluv2lch(Triplet *in_out) 0305 { 0306 double h = in_out->a; 0307 double s = in_out->b; 0308 double l = in_out->c; 0309 double c; 0310 0311 /* White and black: disambiguate chroma */ 0312 if (l > 99.9999999 || l < 0.00000001) 0313 c = 0.0; 0314 else 0315 c = max_safe_chroma_for_l(l) / 100.0 * s; 0316 0317 /* Grays: disambiguate hue */ 0318 if (s < 0.00000001) 0319 h = 0.0; 0320 0321 in_out->a = l; 0322 in_out->b = c; 0323 in_out->c = h; 0324 } 0325 0326 static void lch2hpluv(Triplet *in_out) 0327 { 0328 double l = in_out->a; 0329 double c = in_out->b; 0330 double h = in_out->c; 0331 double s; 0332 0333 /* White and black: disambiguate saturation */ 0334 if (l > 99.9999999 || l < 0.00000001) 0335 s = 0.0; 0336 else 0337 s = c / max_safe_chroma_for_l(l) * 100.0; 0338 0339 /* Grays: disambiguate hue */ 0340 if (c < 0.00000001) 0341 h = 0.0; 0342 0343 in_out->a = h; 0344 in_out->b = s; 0345 in_out->c = l; 0346 } 0347 0348 void hsluv2rgb(double h, double s, double l, double *pr, double *pg, double *pb) 0349 { 0350 Triplet tmp = {h, s, l}; 0351 0352 hsluv2lch(&tmp); 0353 lch2luv(&tmp); 0354 luv2xyz(&tmp); 0355 xyz2rgb(&tmp); 0356 0357 *pr = tmp.a; 0358 *pg = tmp.b; 0359 *pb = tmp.c; 0360 } 0361 0362 void hpluv2rgb(double h, double s, double l, double *pr, double *pg, double *pb) 0363 { 0364 Triplet tmp = {h, s, l}; 0365 0366 hpluv2lch(&tmp); 0367 lch2luv(&tmp); 0368 luv2xyz(&tmp); 0369 xyz2rgb(&tmp); 0370 0371 *pr = tmp.a; 0372 *pg = tmp.b; 0373 *pb = tmp.c; 0374 } 0375 0376 void rgb2hsluv(double r, double g, double b, double *ph, double *ps, double *pl) 0377 { 0378 Triplet tmp = {r, g, b}; 0379 0380 rgb2xyz(&tmp); 0381 xyz2luv(&tmp); 0382 luv2lch(&tmp); 0383 lch2hsluv(&tmp); 0384 0385 *ph = tmp.a; 0386 *ps = tmp.b; 0387 *pl = tmp.c; 0388 } 0389 0390 void rgb2hpluv(double r, double g, double b, double *ph, double *ps, double *pl) 0391 { 0392 Triplet tmp = {r, g, b}; 0393 0394 rgb2xyz(&tmp); 0395 xyz2luv(&tmp); 0396 luv2lch(&tmp); 0397 lch2hpluv(&tmp); 0398 0399 *ph = tmp.a; 0400 *ps = tmp.b; 0401 *pl = tmp.c; 0402 }