We help IT Professionals succeed at work.

We've partnered with Certified Experts, Carl Webster and Richard Faulkner, to bring you a podcast all about Citrix Workspace, moving to the cloud, and analytics & intelligence. Episode 2 coming soon!Listen Now


RGBtoHSL HSLtoRGB C to delphi conversion

miauw asked
Medium Priority
Last Modified: 2012-05-04
I think this is a question about numeric programming
and not so much about colors:

I want to be able to convert Red, Green and Blue
(each having a value from 0 to 255) to
Hue, Saturation and Luminance (the HSL/HSB model),
(each having a value from 0 to 240), and back.

The delphi (WINAPI) help has the C code for this
(search help on RGBtoHSL).
I tried to port this C code to Delphi. It worked
for most cases but sometimes there is a slight difference
(roundoff error).
I mean: when I put use RGBtoHSL on some legal RGB values
and convert it back with HSLtoRGB the RGB values in rare
cases are slighty different (e.g. R=R, G=G+1, B=B-1).

First, I used TRUNC() for the real to integer conversions,
and then I only used reals instead of integers and
rounded off later. The result is about the same, the
latter being a little worse.

Has any one ported this functions such that you
can convert back and forth as many times as you please
without this slight differences?

To be explicit suppose R=x, G=y, B=z

I want to be able to call RGBtoHSL and then HSLtoRGB and repeat this process a 1000 times,
and still the result must be R=x, G=y and B=z.

Please give the code.
Watch Question


Edited text of question
I can't find the c source code which you describe but if you puth this code here i can made translation.


OK Mirek,
I copied it from the Delphi help (don't know why you
can not find it).
If you can translate it such that is takes care of the rounding errors, great. In my translation most errors occurs when
the Luminance is different from 120.

PSS ID Number: Q29240

Authored 26-Apr-1988                  Last modified 25-May-1995

The information in this article applies to:

 - Microsoft Windows Software Development Kit (SDK) for Windows,
   versions 3.0 and 3.1
 - Microsoft Win32 SDK, versions 3.5, 3.51, and 4.0


The code fragment below converts colors between RGB (Red, Green,
Blue) and HLS/HBS (Hue, Lightness, Saturation/Hue, Brightness,


/* Color Conversion Routines --

RGBtoHLS() takes a DWORD RGB value, translates it to HLS, and
stores the results in the global vars H, L, and S. HLStoRGB takes the
current values of H, L, and S and returns the equivalent value in an
RGB DWORD. The vars H, L, and S are only written to by:

   1. RGBtoHLS (initialization)
   2. The scroll bar handlers

A point of reference for the algorithms is Foley and Van Dam,
"Fundamentals of Interactive Computer Graphics," Pages 618-19. Their

algorithm is in floating point. CHART implements a less general
(hardwired ranges) integral algorithm.

There are potential round-off errors throughout this sample.
((0.5 + x)/y) without floating point is phrased ((x + (y/2))/y),
yielding a very small round-off error. This makes many of the
following divisions look strange.

#define  HLSMAX   RANGE /* H,L, and S vary over 0-HLSMAX */
#define  RGBMAX   255   /* R,G, and B vary over 0-RGBMAX */
                        /* HLSMAX BEST IF DIVISIBLE BY 6 */

                        /* RGBMAX, HLSMAX must each fit in a byte. */

/* Hue is undefined if Saturation is 0 (grey-scale) */
/* This value determines where the Hue scrollbar is */
/* initially set for achromatic colors */
#define UNDEFINED (HLSMAX*2/3)

void  RGBtoHLS(lRGBColor)
   WORD R,G,B;          /* input RGB values */
   BYTE cMax,cMin;      /* max and min RGB values */
   WORD  Rdelta,Gdelta,Bdelta; /* intermediate value: % of spread from max


   /* get R, G, and B out of DWORD */
   R = GetRValue(lRGBColor);
   G = GetGValue(lRGBColor);
   B = GetBValue(lRGBColor);

   /* calculate lightness */
   cMax = max( max(R,G), B);
   cMin = min( min(R,G), B);
   L = ( ((cMax+cMin)*HLSMAX) + RGBMAX )/(2*RGBMAX);

   if (cMax == cMin) {           /* r=g=b --> achromatic case */
      S = 0;                     /* saturation */
      H = UNDEFINED;             /* hue */
   else {                        /* chromatic case */

      /* saturation */
      if (L <= (HLSMAX/2))
         S = ( ((cMax-cMin)*HLSMAX) + ((cMax+cMin)/2) ) / (cMax+cMin);
         S = ( ((cMax-cMin)*HLSMAX) + ((2*RGBMAX-cMax-cMin)/2) )
            / (2*RGBMAX-cMax-cMin);

      /* hue */
      Rdelta = ( ((cMax-R)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin);
      Gdelta = ( ((cMax-G)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin);
      Bdelta = ( ((cMax-B)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin);

      if (R == cMax)
         H = Bdelta - Gdelta;
      else if (G == cMax)
         H = (HLSMAX/3) + Rdelta - Bdelta;
      else /* B == cMax */
         H = ((2*HLSMAX)/3) + Gdelta - Rdelta;

      if (H < 0)
         H += HLSMAX;
      if (H > HLSMAX)
         H -= HLSMAX;

/* utility routine for HLStoRGB */
WORD HueToRGB(n1,n2,hue)
WORD n1;
WORD n2;
WORD hue;

   /* range check: note values passed add/subtract thirds of range */
   if (hue < 0)

      hue += HLSMAX;

   if (hue > HLSMAX)
      hue -= HLSMAX;

   /* return r,g, or b value from this tridrant */
   if (hue < (HLSMAX/6))
      return ( n1 + (((n2-n1)*hue+(HLSMAX/12))/(HLSMAX/6)) );
   if (hue < (HLSMAX/2))
      return ( n2 );
   if (hue < ((HLSMAX*2)/3))
      return ( n1 + (((n2-n1)*(((HLSMAX*2)/3)-hue)+(HLSMAX/12))/(HLSMAX/6))
      return ( n1 );

DWORD HLStoRGB(hue,lum,sat)
WORD hue;
WORD lum;
WORD sat;
   WORD R,G,B;                /* RGB component values */

   WORD  Magic1,Magic2;       /* calculated magic numbers (really!) */

   if (sat == 0) {            /* achromatic case */
      if (hue != UNDEFINED) {
         /* ERROR */
   else  {                    /* chromatic case */
      /* set up magic numbers */
      if (lum <= (HLSMAX/2))
         Magic2 = (lum*(HLSMAX + sat) + (HLSMAX/2))/HLSMAX;
         Magic2 = lum + sat - ((lum*sat) + (HLSMAX/2))/HLSMAX;

      Magic1 = 2*lum-Magic2;

      /* get RGB, change units from HLSMAX to RGBMAX */
      R = (HueToRGB(Magic1,Magic2,hue+(HLSMAX/3))*RGBMAX +
      G = (HueToRGB(Magic1,Magic2,hue)*RGBMAX + (HLSMAX/2)) / HLSMAX;
      B = (HueToRGB(Magic1,Magic2,hue-(HLSMAX/3))*RGBMAX +


Additional reference words: 3.00 3.10 3.50 4.00 95  color RGB HLS HBS
KBCategory: kbprg
KBSubcategory: GdiPal

 The problem is that HSL to RGB does not produce a direct integer mapping. If you wish to retain the exact values, then you will have to store the values as real numbers throughout the process and not use integers AT ALL. Even this would not completely solve the problem, since as you approach either end of the Luminance range, the Hue and Saturation values become less and less significant, until at the extremes (black and white) the Hue and Saturation are redundant (ie there is no correct value for H or S for  RGB(0,0,0) or RGB(255,255,255)). I suspect that this is why you are finding rounding errors in your conversions.
  The functions which I have written for the conversions are :

procedure HSLtoRGB(H,S,L:Integer;var R,G,B:Integer);
  Sat,Lum : Double;
  R := 0;
  G := 0;
  B := 0;
  if (H < 360) and (H >= 0) and (S <= 100) and (S >= 0) and (L <= 100) and (L >= 0) then begin
    if H <=60 then begin
      R := 255;
      G := Round((255/60)*H);
      B := 0;
    else if H <=120 then begin
      R := Round(255-(255/60)*(H-60));
      G := 255;
      B := 0;
    else if H <=180 then begin
      R := 0;
      G := 255;
      B := Round((255/60)*(H-120));
    else if H <=240 then begin
      R := 0;
      G := Round(255-(255/60)*(H-180));
      B := 255;
    else if H <=300 then begin
      R := Round((255/60)*(H-240));
      G := 0;
      B := 255;
    else if H <360 then begin
      R := 255;
      G := 0;
      B := Round(255-(255/60)*(H-300));

    Sat := Abs((S-100)/100);
    R := Round(R-((R-128)*Sat));
    G := Round(G-((G-128)*Sat));
    B := Round(B-((B-128)*Sat));

    Lum := (L-50)/50;
    if Lum > 0 then begin
      R := Round(R+((255-R)*Lum));
      G := Round(G+((255-G)*Lum));
      B := Round(B+((255-B)*Lum));
    else if Lum < 0 then begin
      R := Round(R+(R*Lum));
      G := Round(G+(G*Lum));
      B := Round(B+(B*Lum));

procedure RGBtoHSL(R,G,B:Integer;var H,S,L:Integer);
  Delta : Double;
  CMax,CMin : Double;
  Red,Green,Blue,Hue,Sat,Lum : Double;
  Red := R/255;
  Green := G/255;
  Blue := B/255;
  CMax := Max(Red,Max(Green,Blue));
  CMin := Min(Red,Min(Green,Blue));
  Lum := (CMax+CMin)/2;
  if CMax = CMin then begin
    Sat := 0;
    Hue := 0;
  else begin
    if Lum < 0.5 then Sat := (CMax-CMin)/(CMax+CMin)
    else Sat := (cmax-cmin)/(2-cmax-cmin);
    delta := CMax-CMin;
    If Red = CMax then Hue := (Green-Blue)/Delta
    else if Green = CMax then Hue := 2+(Blue-Red)/Delta
    else Hue := 4.0+(Red-Green)/Delta;
    Hue := Hue / 6;
    If Hue < 0 then Hue := Hue + 1;
  H := Round(Hue*360);
  S := Round(Sat*100);
  L := Round(Lum*100);

function Max(a,b:Double):Double;
  if a > b then result := a
  else result := b;

function Min(a,b:Double):Double;
  if a < b then result := a
  else result := b;

  You should have little difficulty converting these to work with real numbers should you wish. The HSL values are also in a different range to the ones you specify, however this is also pretty simple to rectify. I hope this goes some way towards solving your problem, however it is implicitly impossible to completely solve the problem, for the reasons which I suggest.



Thank you Steven, but I have to reject your answer.

I tried your procedures.
The values returned are no better as
my lousy C to Delphi translation of the Delphi help.

E.g. R 21 G 234 B 85
   ->  RGBtoHSL
   ->  HSLtoRGB
   ->R 20 G 235 B 84

This error occurs with a lot of combinations!

It can be done better. I know. Check out e.g. the color
selection box of Windows 95 with a screen capture.
(Screen->Appearance (color item) -> Colors -> others).
The back and forth conversions are always perfect.
The coloured bar on the right must be based upon HSLtoRGB and
RGBtoHSL conversions, because the colors selected are
the active H and S value, and it varies over the L.

Not rounding of at all as you do, does not work. You have
to round off in the algoritme itself at some places.

Can anyone do a better job?

Sorry miauw but Steven give you good answer, howewer proc can be better.
Try define HSL as (56,134,120) - You can't You'r values will be changed to  (56,135,120).

answer = for some integer values of RGB exist more than 1 integer values of HSL and vice versa. If you need call this function 1000 times you need store values in real numbers.



1)To Mirek: I don't think the answer of Steven is good.
  My translation of C to Delphi procedure, although C is
  a very, very long time ago, clearly works better
  (I checked it thoroughly).

2) I don't think using only reals works better.
   You even get more errors. I tried.
   The algoritme needs some rounding otherwise
   the error gets larger and larger every call,
   by not rounding it off.

3) I still try to get the function better.
   I use now HLSMAX = 24.000
             RGBMAX = 25.500
   and vary with different places in the mainprogram
   to do some rounding/truncing off.
   This works better, but not good enough.
   I don't care if I must use Int,Longint, real, double or
   whatever, but I must be able to convert back and forth.
   I'm loosy in numeric programming. So can anyone help me?

1. do you try  set values of HSL to (56,134,120) ?
2. Yes i wrote so proc from Steven can be better.
3. I will try resolve this problem with any solution - wait.

Sorry for my english.

I just read "Introduction To Computer Graphics" of James D.Foley and ...

I found so you'r problem is not numeric programming.
You can't convert RGB to HSL in all cases ! It is imposiible in theory !

Answer = if You use real numbers the problem is the same. You can convert uniformly RGB to CMYK but not RGB to HSL.

p.s. Do You know so some colors is inacessible with RGB ? (some colors don't have RGB representation).


Thank you very much for your reaction Mirek.

1 My translation: HSL=56,134,120 RGB =141.74,198.68,56.31
                          truncated   141 198 56
                  HSL=..,135,..  RGB =141.84,199.22,55.78  
                          truncated   141 199 55
  so they are different!

  Windows 95 translation is different too:

                  HSL=56,134,120 RGB =144,199,56
                  HSL=..,135,..  RGB =142,200,55

Think about this Mirek:
look at Windows 95 interface colorsettings:
WIndows 95 can translate ANY RGB value to a approximately
(maybe not absolutely correct in theory) HSL value.
If you put the HSL values in, you get the RGB value.
This is what i want. if f(x)=y then Finverse(y)=x!

I want to do this:

I have a bitmap which is the same as the colorselection box
in Windows 95 (appearances->color->item) , when mousedown
occurs, I want to change the right colorbar:
When I MOUSEDOWN on the colorselectionbox, I get a RGB value
(canvas.pixels). Then I call RGBtoHSL. Then I make the
In the whole bar, in Windows 95 H=H, S=S and L=0..240.

In my program, H=H or H=H+1 or H=H-1, S=S or S=S+1 or S=S-1
and so on. I hate this error.

Windows 95 proves it can be done. Other programs (e.g. Photoshop)
show too it is possible to convert back and forth from

This is what I would like.


What i think you should do, is to implement a "hardcoded" table which maps the range 0.240 to 0-255. This can be done by 2 small arrays. As you descibed, it can not be an axact match, because you have to truncate the values. But as it's "hardcoded" you won't get any truncation errors.
HSB2RGB[240] would return 255
Create the RGB2HSB array by picking the cell index of the closest match in HSB2RGB.

Hope it can help.


 Yeah, I know my functions are not perfect, but I found that they work pretty well for my purposes. As I tried to point out and as Mirek confirms, the translation is 'theoretically' impossible. (By the way I also recomend the book "Introduction To Computer Graphics" by James D.Foley if you want a complete overview of colour models, a topic which is too lengthy to go into here, but well worth studying if you intend to do much graphics work).
  KE has an interesting solution, however it's still not perfect. Your arrays would obviously have to be 3 dimensional : (eg RGB2HSL[r,g,b]) and the result would be at least a 24 bit number to hold the three 8 bit values. However as there are different numbers of enties in each of your colour models (240^3 for HSL and 255^3 for RGB) the translation would not be exact even with this solution.
  The answer still stands, that the problem CANNOT be completely solved, especially towards the extremes of the luminance range. This has however piqued my interest, so I'll keep looking at it when I have the time.


I do not agree with you Steven. First of all we do not need to produce a fulle scale array of both RGB. The problem arises in the conversion of range 240 to 255. What you do is to convert the RGB.255 scale to a HSB.255 scale - after this conversion you map the HSB.255 to a HSB.240 scale - this is done by a single [256] array on each component of the H,S,B color.
The only problem to this solution is to calculate the arrays correct. I shouldn't have called the arrays HSB2RGB in my previous answer, when it's not what it actually does. It only converts between range 240 to range 255.

My english is not good but i try describe why this problem can't be resolved.

1.We have two sets of values A-with values for RGB and B-with values for HSV.
2.For two integer values of A exist one corresponding value of B.
3 If you put to conversion one of this not uniform values then when you made reconversion you can't get correct values. For Example A1 and A2 have the same B1. When you made conversion of A1 you get B1 and When you made reconversion you can get A1, but if You put A2 to conversion you get B1 and after reconversion you get A1 not A2. This is the reason why this is impossible.
3. I made small mistake with my previous comment - in real values you can do conversion and reconversion but any rounding make this problem impossible, because the confersion is not afinity conversion.
4.I have now small algorithm "from book of graphics" for real numbers and i go to test this problem in real numbers.
5. Mayby you can solve the problem in different way
6.Sorry for my english.


Thanks everyone. I think this is a very interesting point.

KE: I reject your answer. A one dimension problem solves

Mirek, you are absolutely right in some ways.

Since the highest value of HSL doesn't have to be the
same as the hightest values of RGB an inverse function
is theoretically impossible.

Suppose the values HSL can get are 0..240,~,~
and RGB can go  0..255,~,~
then this must be possible:

HSLtoRGB -> RGBtoHSL must yield the same HSL value:
because there exist 1 unique value in RGB for each HSL.
so if f(r,g,b)=h,s,l      : Finverse(h,s,l)=r,g,b can exist.

RGBtoHSL -> HSLroRGB CAN NOT always give the same value,
because there are more RGB values then there are HSL's.
So two different RGB values can yield the same HSL value.

But then again must be possible:

I call:
RGBtoHSL(R1,G1,B1, (var) H,S,L)
HSLtoRGB(H,S,L, (var) R2,G2,B2)
RGBtoRGB(R2,G2,B2, (var) H2,S2,L2)
then H2 must be equal to H, and S2 to S and L2 to L.

I think an good conversion algoritme must be able to do this.
I made a procedure which is a loop converting back and forth
from HSL to RGB and I will try to display the results of the
different implementations soon. Send in your algoritme.

This discussion is much too interesting to let go, but
I found a solution to work around my original problem.
(See my comment above for understanding the next lines).
I get the RGB value from the selection field. I use RGBtoHSL
to get the HSL value, then I make the right bar by using
H=H,S=S,L=0..255 and convert to RGB using HSLtoRGB,
and put the HSL values in an array.
When you then click the right bar, I don't use RGBtoHSL to
get the HSL value put I pick it from the array.

Nevertheless, the question about a good RGBtoHSL algoritme
stays open.


 I hate to keep making the same point, but the problem remains IMPOSIBLE no matter how good the conversion algorithm. This has nothing to do with the differing ranges (0-255) vs. (0-240) of the respective ranges, this just forces the use of non integer values, but then that was always going to be the case.
  There exist two singularities in the RGB color map (Black and White) that map to an infinite number of points in the HSL color map (Lum = Min and Max respectivly). As you approach these points in RGB space, the precision of your HSL storage becomes more and more significant, until it spirals off into infinity, never to be reconverted.

I now working at this problem because this is very interesting in other ways, but you write ....because there exist 1 unique value in RGB for each HSL.... I think so this is incorrect !
I wrote the example later. (56,134,120 and 56,135,120) have the same RGB.
It is possibble only to made algorithm which sometimes after first conversion give you difference by 1 and all next conversion without differences.

My english is poor and i think so you don't fully understand my previous comment. Here is example :

HSLtoRGB( 56,134,120)=(142,200,55)
RGBtoHSL( 142,200,55)=( 56,134,120)

but what when you put 56,135,120 ?

HSLtoRGB( 56,135,120 = (142,200,55)
RGBtoHSL( 142,200,55)=(56,134,120) !!!!!!!!!!!!!!!!!

The conversion in integer numbers is NOT UNIFORM and you can't construct good for you allgorithm.


StevenB and Mirek, thank you very much for your contributions.

As I wrote in a comment earlier, I now have a practical
solution to the problem.

StevenB, my translation is better than yours when L=120,
and not worse when L=different. I use the delphi help C
algoritme with some modifications.

Mirek: you also give good advise.

I don't now: whom should I give the points for this question?
I don't want to delete the question.

StevenB: who should get the points you think you or Mirek?
Mirek: who should get the points you think you or StevenB?

If you agree I accept.

Steven give you working example so give the points to Steven.
 Mirek, Cheers, very generous of you.

  Miauw, Sorry we couldn't completely solve your problem. I'm glad to hear that you've found a practical solution. If you want to assign the points, then I'll be happy to accept them.

  This has been an interesting discussion, if anyone comes up with anything new, then keep posting it here, or mail it to me:


Not the solution you were looking for? Getting a personalized solution is easy.

Ask the Experts
Access more of Experts Exchange with a free account
Thanks for using Experts Exchange.

Create a free account to continue.

Limited access with a free account allows you to:

  • View three pieces of content (articles, solutions, posts, and videos)
  • Ask the experts questions (counted toward content limit)
  • Customize your dashboard and profile

*This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.


Please enter a first name

Please enter a last name

8+ characters (letters, numbers, and a symbol)

By clicking, you agree to the Terms of Use and Privacy Policy.