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 }