May 24, 2010
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
Comments

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

Posted by: Ralph on May 24, 2010 03:26 PM

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.

Posted by: Mario Klingemann on May 24, 2010 03:36 PM

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.

Posted by: Mario Klingemann on May 24, 2010 03:52 PM

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.

Posted by: Mario Klingemann on May 24, 2010 04:16 PM

Great stuff Mario! Duly appreciated!

Posted by: Marcus Stade on May 25, 2010 11:36 AM

Thanks a lot for sharing this! <3

Posted by: mr.doob on May 25, 2010 02:54 PM

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

Posted by: Jon Williams on June 2, 2010 05:08 AM

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?

Posted by: OPunWide on June 9, 2010 09:44 PM

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.

Posted by: Mario Klingemann on June 9, 2010 09:47 PM

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);

Posted by: Andreas on July 1, 2010 03:17 PM

Could anyone imitate Photoshop's HSL and LAB accurately?
Thanks.

Posted by: Drazick on September 4, 2010 11:13 AM

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

Posted by: badmash on October 22, 2010 09:01 PM

this was a really nice post, thanks

Posted by: mackdaniel on October 24, 2010 03:02 PM
Post a comment
Name:


Email Address:


URL:


Comments:


Remember info?



Thank you!

Most Visited Entries
Sketches, Works & Source Code
Lectures
Contact
Backlog
In Love with
Powered by
Movable Type 2.661

© Copyright Mario Klingemann

Syndicate this site:
RSS 1.0 - RSS 2.0

Quasimondo @ flickr
Quasimondo @ LinkedIn
Quasimondo @ Twitter
Quasimondo @ Facebook
Quasimondo @ MySpace
Quasimondo is a Bright
Citizen of the TRansnational Republic
My other blog in german
Impressum


My family name is written Klingemann,
not Klingelmann, Klingeman, Klingaman, Kingemann,
Kindermann, Killingaman, Klingman, Klingmann, Klingonman
Klingemman, Cleangerman, Klingerman or Kleangerman

profile for Quasimondo at Stack Overflow, Q&A for professional and enthusiast programmers