Yes. First contrast is added to the L channel (not relevant here), then brightness. Saturation is multiplied into the a and b channels.
The LUT for the brightness part is computed as follows (slightly simplified):
const float gamma = (brightness >= 0.0f) ? 1.0f / (1.0f + brightness)
: (1.0f - brightness);
for(int k = 0; k < 0x10000; k++)
{
ltable[k] = 100.0f * powf((float)k / 0x10000, gamma);
}
and it’s applied with
L_new = ltable[CLAMP((int)(L_old / 100.0f * 0x10000ul), 0, 0xffff)];
I removed all the parts that deal with extrapolation for L >= 100.0.
When looking at the two extreme values of L = 0 and L = 100 we get:
L_0_new = ltable[CLAMP((0.0 / 100.0 * 0x10000), 0, 0xffff)] = ltable[0]
L_100_new = ltable[CLAMP((100.0 / 100.0 * 0x10000), 0, 0xffff)] = ltable[0xffff]
with
ltable[0] = 100.0 * powf(0 / 0x10000, gamma)
= 0
ltable[0xffff] = 100.0 * powf(0xffff / 0x10000, gamma)
= 100.0 * powf(0.9999, gamma)
= 100.0
The math isn’t 100% correct in the last step but it’s close enough and the differences are not visible, even for big brightness bossts, resulting in gamma quite different from 1.0.
I hope that explained your question. The real code can be found here for the processing and here for the LUT creation.