## Sunday, 4 November 2018

### RGB/HSV in HLSL 8

Andrew (K-Be) emailed me this week with a problem he found in my HLSL code for HCL-to-RGB conversion. Here's the original code:

 ``` float HCLgamma = 3; float HCLy0 = 100; float HCLmaxL = 0.530454533953517; // == exp(HCLgamma / HCLy0) - 0.5 float PI = 3.1415926536; float3 HCLtoRGB(in float3 HCL) { float3 RGB = 0; if (HCL.z != 0) { float H = HCL.x; float C = HCL.y; float L = HCL.z * HCLmaxL; float Q = exp((1 - C / (2 * L)) * (HCLgamma / HCLy0)); float U = (2 * L - C) / (2 * Q - 1); float V = C / Q; // PROBLEM HERE... float T = tan((H + min(frac(2 * H) / 4, frac(-2 * H) / 8)) * PI * 2); H *= 6; if (H <= 1) { RGB.r = 1; RGB.g = T / (1 + T); } else if (H <= 2) { RGB.r = (1 + T) / T; RGB.g = 1; } else if (H <= 3) { RGB.g = 1; RGB.b = 1 + T; } else if (H <= 4) { RGB.g = 1 / (1 + T); RGB.b = 1; } else if (H <= 5) { RGB.r = -1 / T; RGB.b = 1; } else { RGB.r = 1; RGB.b = -T; } RGB = RGB * V + U; } return RGB; }```

Note the calculation of 'T'. Let's split that expression in two:

 ``` float A = H + min(frac(2 * H) / 4, frac(-2 * H) / 8); float T = tan(A * PI * 2);```

We can see that 'T' will tend to infinity when 'A' approaches 0.25 or 0.75. A bit of careful graphing of 'H' against 'A' suggests that this only occurs when the input hue approaches 1/6 or 2/3 respectively, so we can put extra checks in the sextant clauses:

 ``` float A = (H + min(frac(2 * H) / 4, frac(-2 * H) / 8)) * PI * 2; float T; H *= 6; if (H <= 0.999) { T = tan(A); RGB.r = 1; RGB.g = T / (1 + T); } else if (H <= 1.001) { RGB.r = 1; RGB.g = 1; } else if (H <= 2) { T = tan(A); RGB.r = (1 + T) / T; RGB.g = 1; } else if (H <= 3) { T = tan(A); RGB.g = 1; RGB.b = 1 + T; } else if (H <= 3.999) { T = tan(A); RGB.g = 1 / (1 + T); RGB.b = 1; } else if (H <= 4.001) { RGB.g = 0; RGB.b = 1; } else if (H <= 5) { T = tan(A); RGB.r = -1 / T; RGB.b = 1; } else { T = tan(A); RGB.r = 1; RGB.b = -T; }```

Of course, if you're confident that your platform won't throw too much of a wobbly when computing 'tan' of half pi et al, you can hoist the calculation of 'T' to its declaration before the 'if' clauses. You never know your luck: your shader compiler might to that for you!

Having said all that, the more I read about the HCL colour space, the less I'm convinced it's actually worthwhile.