Wednesday, June 25, 2008

Integer conversion from HSL to RGB

A hue-saturation-lumination to RGB conversion that uses no floating point.

Inputs and outputs are in the range 0 to 255.

Derived from an incredibly old floating-point algorithm.
void HSL_to_RGB(int hue, int sat, int lum, int* r, int* g, int* b)
{
int v;

v = (lum < 128) ? (lum * (256 + sat)) >> 8 :
(((lum + sat) << 8) - lum * sat) >> 8;
if (v <= 0) {
*r = *g = *b = 0;
} else {
int m;
int sextant;
int fract, vsf, mid1, mid2;

m = lum + lum - v;
hue *= 6;
sextant = hue >> 8;
fract = hue - (sextant << 8);
vsf = v * fract * (v - m) / v >> 8;
mid1 = m + vsf;
mid2 = v - vsf;
switch (sextant) {
case 0: *r = v; *g = mid1; *b = m; break;
case 1: *r = mid2; *g = v; *b = m; break;
case 2: *r = m; *g = v; *b = mid1; break;
case 3: *r = m; *g = mid2; *b = v; break;
case 4: *r = mid1; *g = m; *b = v; break;
case 5: *r = v; *g = m; *b = mid2; break;
}
}
}

10 Comments:

At 11/12/2010 12:22 PM, Anonymous Anonymous said...

Hi,
I found your code to convert HSL to RGB. I am working on to implement your code for 8bit ┬ÁC.
Could you please give me description what does it mean "vsf"?

 
At 11/13/2010 9:02 AM, Blogger Qwertie said...

I don't know what it means. All I did was to find a floating-point algorithm and convert it to 8-bit fixed point.

Let's see. v is a fixed-point number between lum and 1.0 (=256). If sat=0 then v=lum; as you increase the saturation, the value of v increases, to a maximum of 1.0 if lum>0.5 and 2*lum if lum<0.5.

m = 2*lum-v. So if there is no saturation, m=lum. As you increase the saturation, m approaches 0 if lum<0.5. If lum>0.5 then m = lum + (lum - 1)*sat, a formula which attenuates the effect of sat as lum approaches 1. That is, if lum is near 1.0 then m is near 1.0 and sat has almost no effect. But if lum is lower, then as sat increases, the value of m decreases toward 0.

I can see that sextant is a number in the range 0..5 representing which of 6 color regions the hue represents, and fract is the fraction (0..1.0) within that region.

It looks like vsf contains a redundancy, as v * fract * (v - m) / v should be the same as fract * (v - m). It turns out that v-m = 2*(v-lum). v-lum = lum*sat if lum<0.5; v-lum = sat - lum*sat if lum>0.5. In both cases, v-lum=0 if sat=0, and as sat increases toward 1.0, v-lum increases toward lum (or a lower value if lum>0.5).

I'll leave the rest as an exercise to the reader. So there you have it. A complicated mess that eventually gives you a color.

 
At 11/13/2010 9:16 AM, Blogger Qwertie said...

Oh wait, I see how this works. vsf is just a common factor between mid1 and mid2. Both mid1 and mid2 have a value between v and m:

mid1 = m + fract * (v-m)
mid2 = v - fract * (v-m)
(Note that v >= m.)

As fract increases, mid1 goes from m up to v, while mid2 goes from v down to m.

Depending on which sextant the hue belongs to, the result always uses v, and m, and either mid1 or mid2. I get it now! Cool.

So in summary: v represents the MOST DOMINANT COLOR, max(r,g,b). m represents the LEAST DOMINANT COLOR, min(r,g,b). mid1 and mid2 are in between these two; as you change the hue, mid1 and mid2 are used to transition from one sextant of the "color wheel" to the next.

 
At 10/29/2012 3:35 AM, Blogger forums9999 said...

Thanks for your algorithm.
I'm looking for an integer conversion from RGB to HSL. Do you know any pointer towards such an algorithm ? Thanks.

 
At 11/01/2012 2:22 PM, Blogger Qwertie said...

Ahh yes, here is my C# code for RGB to HSL.
/// <summary>Converts a color to Hue-Saturation-Luminosity representation
/// with each component in the range 0 to 255.</summary>
/// <remarks>If the color is monochrome, the hue argument is left
/// unchanged.
///
/// Note: RgbToHsl() came from a different source than HslToRgb(). The
/// conversions probably don't match up perfectly.</remarks>
public static void RgbToHsl(Color c, ref int hue, out int sat, out int lum)
{
int r = c.R, g = c.G, b = c.B;
int min = Math.Min(Math.Min(r, g), b);
int max = Math.Max(Math.Max(r, g), b);

if (min != max)
{
if (r == max && g >= b)
hue = (g - b) * 85 / (max - min) / 2 + 0;
else if (r == max && g < b)
hue = (g - b) * 85 / (max - min) / 2 + 255;
else if (g == max)
hue = (b - r) * 85 / (max - min) / 2 + 85;
else if (b == max)
hue = (r - g) * 85 / (max - min) / 2 + 171;
}

lum = (min + max) / 2;


if (min == max)
sat = 0; // gray
else if (min + max < 256)
sat = (max - min) << 8 / (min + max);
else
sat = (max - min) << 8 / (512 - min - max);
}

 
At 12/10/2012 7:54 AM, Blogger Ehsan Khodadad said...

This comment has been removed by the author.

 
At 12/10/2012 7:56 AM, Blogger Ehsan Khodadad said...

Hello
Your code executed accurately on my PC. thank you.
but I need it on my avr project. I used code vision for recompling the code, then I faced a strange problem. when I send lum greater than 80 to the function, it returns red=green=blue= 0.
I don't why?
my IC is atmega88pa.
could you help me plz?

 
At 12/10/2012 1:58 PM, Blogger Qwertie said...

I don't know why either. Maybe your ints are 16-bit and 32-bit is required?

 
At 12/13/2012 1:50 AM, Blogger Ehsan Khodadad said...

I have already changed int to long int.
OK.
Thanks.

 
At 12/13/2012 5:08 AM, Blogger Ehsan Khodadad said...

YES!
I found it.:
v = (lum < 128) ? (long int)(lum * (256 + sat)) >> 8 : (((long int)(lum + sat) << 8) - (long int)lum * sat) >> 8;

 

Post a Comment

<< Home