Converting RGB to HSL differently
Sometimes when working with bitmaps you might find the need to work in a HSL (Hue, Saturation, Luminance) mode, for example when trying to detect skintones in an image. So what you do is probably to use the classic formula from Wikipedia that uses min/max and a whole lot of ifs: http://en.wikipedia.org/wiki/HSL_and_HSV

Well, here is a little alternative method: whilst working with the YUV colorspace I figured that since Y contains all the luminance information, the U + V channels must thus contain the hue and saturation. And it turns out that indeed when looking at u and v as the coordinates of a vector, its angle will represent the hue and its length will be the saturation.

So a formula that does not need any min/max and ifs to convert rgb to hsl looks like this:

// r,b and b are assumed to be in the range 0...1
luminance = r * 0.299 + g * 0.587 + b * 0.114;
u = - r * 0.1471376975169300226 - g * 0.2888623024830699774 + b * 0.436;
v = r * 0.615 - g * 0.514985734664764622 - b * 0.100014265335235378;
hue = atan( v, u );
saturation = Math.sqrt( u*u + v*v );

In this case hue will be between -Pi and Pi and saturation will be between 0 and 1/sqrt(2), so you might want to multiply the saturation by sqrt(2) to get it in a range between 0 and 1.

Of course this also works the other way round - hsl to rgb looks like this:

// hue is an angle in radians (-Pi...Pi)
// for saturation the range 0...1/sqrt(2) equals 0% ... 100%
// luminance is in the range 0...1
u = cos( hue ) * saturation;
v = sin( hue ) * saturation;
r = luminance + 1.139837398373983740 * v;
g = luminance - 0.3946517043589703515 * u - 0.5805986066674976801 * v;
b = luminance + 2.03211091743119266 * u;

And here is a demo of a Pixel Bender kernel that allows you to adjust hue, saturation and brightness of a bitmap using this technique: HSL Adjust Demo, Source: hsladjust.zip

[Edit]As it was correctly noticed in the comments, the values that this method returns are not the HSL values that you get when you are using the classic formula. Nevertheless I think that this method has its uses, for example if you want to quickly create color schemes or if you are using it like in the demo to change the hue/saturation/Luminance of a photo.[/Edit]

[Edit]I updated the factors in the matrix one more time with values I found in the Wikipedia YUV discussion page. Those look better to me at least.[edit]

Posted at May 24, 2010 02:21 PM | Further reading

Very cool!

Something strange happens though, when I try this out in ActionScript.

If for example I input 0xffcc66 I can see the RGB values (after converting to 0...1 range) as 1, 0.8 and 4 and it yields HSL values using this. The other way around however, when I use the exact output in HSL from the first algorithm, it produces RGB values of 1.01199684932, 0.81200059... and 0.41199908... translating to for example a value for red that's > 255.

So you have any idea why this could be? Could it have something to do with the precision with which AS3 can store floating numbers or something?

Cheers, Ralph

Oh that's not good. Thanks for seeing this problem! To be honest, I did not test it this way. On the bitmap the colors looked correct, but it seems that maybe some of the values of the colormatrix are not exact. That's what you get for trusting Wikipedia ;-).

Let me see if I can fix that.

Well, you are absolutely right, the resuting values when you do a RGB->HSL->RGB transform are always bigger than the incoming ones. From a first look it even seems that the offset is pretty constant, e.g. when I multiply the values with 255 the difference seems to be around 3. So maybe subtracting 3/255 already fixes it.

The reason for this difference is not quite clear to me. I can also just guess here that this must be some kind of numerical precision problem.

Okay - I found the problem: the reason was that my code to calculate the inverse rgb2yuv matrix was not precise enough. Now I had Wolfram Alpha do the work and I'm getting slightly different factors which seems to do the work. I will update the post with these values.

Great stuff Mario! Duly appreciated!

Thanks a lot for sharing this! <3

bravo! I'd always wondered if the factors I'd seen published were trustworthy...

I put your RGB to HSL equation into an Excel spread sheet to compare the results with the standard (read as Wikipedia and most everywhere else) RGB/HSL equations. The resulting values are nowhere near similar.

Using the standard equations, an HSL .11,1,.7 gives an RGB output of FF,BC,67. The standard equations convert that RGB back to .11,1,.7. But yours give an HSL of .71,.37,.83.

I've seen the L equation you used in multiple places, and it's the easiest to understand. But the value doesn't get back to the .7 that I started with, it gives .83.

Is this a different HSL space? Am I just out in left field and missing something basic?

Looks like I should better not call it RGB to HSL then. Seems like the color space is different after all. Nevertheless if you use it for creating color schemes or changing photos is still works quite nicely.

Great!

To make it behave more like Photoshop's Hue/Saturation filter don't adjust Y but add this after converting back from YUV to RGB:

// adjustL [0.0 .. 2.0]

float4 factors = float4(adjustL, adjustL, adjustL, 1.0);

if (adjustL < 1.0) dst *= factors ;

else dst += (1.0 - dst) * (factors - 1.0);

Could anyone imitate Photoshop's HSL and LAB accurately?

Thanks.

I just signed up to your blogs rss feed. Will you post more on this subject?

this was a really nice post, thanks