Skip to content

Instantly share code, notes, and snippets.

@memononen
Created August 17, 2025 16:12
Show Gist options
  • Select an option

  • Save memononen/f67961d16c1ce27e2322c0fbe71214db to your computer and use it in GitHub Desktop.

Select an option

Save memononen/f67961d16c1ce27e2322c0fbe71214db to your computer and use it in GitHub Desktop.
hsluv
static const double ref_u = 0.19783000664283680764;
static const double ref_v = 0.46831999493879100370;
static const double kappa = 903.29629629629629629630;
static const double epsilon = 0.00885645167903563082;
static const double xyz_to_rgb[9] = {
3.24096994190452134377, -1.53738317757009345794, -0.49861076029300328366,
-0.96924363628087982613, 1.87596750150772066772, 0.04155505740717561247,
0.05563007969699360846, -0.20397695888897656435, 1.05697151424287856072,
};
static double max_chroma_for_lh(double l, double hu, double hv)
{
double min_len = DBL_MAX;
const double tl = l + 16.0;
const double sub1 = (tl * tl * tl) / 1560896.0;
const double sub2 = (sub1 > epsilon ? sub1 : (l / kappa));
for (int i = 0; i < 3; i++) {
// Intersect hue with RGB gamut bounds
const double m1 = xyz_to_rgb[i*3 + 0];
const double m2 = xyz_to_rgb[i*3 + 1];
const double m3 = xyz_to_rgb[i*3 + 2];
double top1 = (284517.0 * m1 - 94839.0 * m3) * sub2;
double top2 = (838422.0 * m3 + 769860.0 * m2 + 731718.0 * m1) * l * sub2;
double bottom = (632260.0 * m3 - 126452.0 * m2) * sub2;
const double line0_a = top1 / bottom;
const double line0_b = top2 / bottom;
top2 -= 769860.0 * l;
bottom += 126452.0;
const double line1_a = top1 / bottom;
const double line1_b = top2 / bottom;
// Intersect ray from (0,0) towards the hue direction against the gamut lines.
double len = line0_b / (hv - line0_a * hu);
if (len >= 0.0 && len < min_len)
min_len = len;
len = line1_b / (hv - line1_a * hu);
if (len >= 0.0 && len < min_len)
min_len = len;
}
return min_len;
}
static double from_linear(double c)
{
const double v = (c <= 0.0031308) ? 12.92 * c : (1.055 * pow(c, 1.0 / 2.4) - 0.055);
if (v < 0.0) return 0.0;
if (v > 1.0) return 1.0;
return v;
}
#define ARB_PI 3.14159265359
arb_color_t hsluv_to_rgb(double h, double s, double l)
{
if (l <= 0.00000001)
return (arb_color_t){0, 0, 0, 255 };
if (l >= 99.9999999)
return (arb_color_t){255, 255, 255, 255 };
const double hrad = h * ARB_PI / 180.0;
const double hu = cos(hrad);
const double hv = sin(hrad);
// hsluv2lch
const double c = max_chroma_for_lh(l, hu, hv) / 100.0 * s;
// lch2xyz
const double var_u = (hu * c) / (13.0 * l) + ref_u;
const double var_v = (hv * c) / (13.0 * l) + ref_v;
const double y = (l <= 8.0) ? (l / kappa) : pow((l + 16.0) / 116.0, 3.0);
const double x = -(9.0 * y * var_u) / ((var_u - 4.0) * var_v - var_u * var_v);
const double z = (9.0 * y - (15.0 * var_v * y) - (var_v * x)) / (3.0 * var_v);
// xyz2rgb
const double r = from_linear(x * xyz_to_rgb[0] + y * xyz_to_rgb[1] + z * xyz_to_rgb[2]);
const double g = from_linear(x * xyz_to_rgb[3] + y * xyz_to_rgb[4] + z * xyz_to_rgb[5]);
const double b = from_linear(x * xyz_to_rgb[6] + y * xyz_to_rgb[7] + z * xyz_to_rgb[8]);
return (arb_color_t) {
.r = (uint8_t)(r * 255.0),
.g = (uint8_t)(g * 255.0),
.b = (uint8_t)(b * 255.0),
.a = 255,
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment