Solved

RGBtoHSL HSLtoRGB C to delphi conversion

Posted on 1997-09-14
20
1,246 Views
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.
0
Comment
Question by:miauw
  • 7
  • 7
  • 4
  • +1
20 Comments
 

Author Comment

by:miauw
Comment Utility
Edited text of question
0
 
LVL 3

Expert Comment

by:mirek071497
Comment Utility
I can't find the c source code which you describe but if you puth this code here i can made translation.
0
 

Author Comment

by:miauw
Comment Utility
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


SUMMARY

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


MORE INFORMATION

/* 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)
DWORD 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);
      else
         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))
);
   else
      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 */
      R=G=B=(lum*RGBMAX)/HLSMAX;
      if (hue != UNDEFINED) {
         /* ERROR */
      }
   }
   else  {                    /* chromatic case */
      /* set up magic numbers */
      if (lum <= (HLSMAX/2))
         Magic2 = (lum*(HLSMAX + sat) + (HLSMAX/2))/HLSMAX;
      else
         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 +
(HLSMAX/2))/HLSMAX;
      G = (HueToRGB(Magic1,Magic2,hue)*RGBMAX + (HLSMAX/2)) / HLSMAX;
      B = (HueToRGB(Magic1,Magic2,hue-(HLSMAX/3))*RGBMAX +
(HLSMAX/2))/HLSMAX;
   }

   return(RGB(R,G,B));
}

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

Expert Comment

by:StevenB
Comment Utility
 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);
var
  Sat,Lum : Double;
begin
  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;
    end
    else if H <=120 then begin
      R := Round(255-(255/60)*(H-60));
      G := 255;
      B := 0;
    end
    else if H <=180 then begin
      R := 0;
      G := 255;
      B := Round((255/60)*(H-120));
    end
    else if H <=240 then begin
      R := 0;
      G := Round(255-(255/60)*(H-180));
      B := 255;
    end
    else if H <=300 then begin
      R := Round((255/60)*(H-240));
      G := 0;
      B := 255;
    end
    else if H <360 then begin
      R := 255;
      G := 0;
      B := Round(255-(255/60)*(H-300));
    end;

    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));
    end
    else if Lum < 0 then begin
      R := Round(R+(R*Lum));
      G := Round(G+(G*Lum));
      B := Round(B+(B*Lum));
    end;
  end;
end;

procedure RGBtoHSL(R,G,B:Integer;var H,S,L:Integer);
var
  Delta : Double;
  CMax,CMin : Double;
  Red,Green,Blue,Hue,Sat,Lum : Double;
begin
  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;
  end
  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;
  end;
  H := Round(Hue*360);
  S := Round(Sat*100);
  L := Round(Lum*100);
end;

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

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


  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.

  Steven.
0
 

Author Comment

by:miauw
Comment Utility
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?


0
 
LVL 3

Expert Comment

by:mirek071497
Comment Utility
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.
0
 

Author Comment

by:miauw
Comment Utility


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?



0
 
LVL 3

Expert Comment

by:mirek071497
Comment Utility
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.
0
 
LVL 3

Expert Comment

by:mirek071497
Comment Utility
Hi

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).
0
 

Author Comment

by:miauw
Comment Utility
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
rightbar.
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
RGB to a (SIMULATION OF, MAYBE THEORICALLY NOT EXACTLY CORRECT,
BUT VERY CLOSE) HSL model.

This is what I would like.





0
Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

 
LVL 3

Expert Comment

by:KE
Comment Utility
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.
Example:
HSB2RGB[240] would return 255
Create the RGB2HSB array by picking the cell index of the closest match in HSB2RGB.

Hope it can help.

0
 
LVL 4

Expert Comment

by:StevenB
Comment Utility
 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.

  Steven.
0
 
LVL 3

Expert Comment

by:KE
Comment Utility
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.

0
 
LVL 3

Expert Comment

by:mirek071497
Comment Utility
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.
0
 

Author Comment

by:miauw
Comment Utility
Thanks everyone. I think this is a very interesting point.

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

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.





0
 
LVL 4

Expert Comment

by:StevenB
Comment Utility
 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.

  Steven.
0
 
LVL 3

Expert Comment

by:mirek071497
Comment Utility
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.

0
 

Author Comment

by:miauw
Comment Utility
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.




0
 
LVL 3

Expert Comment

by:mirek071497
Comment Utility
Steven give you working example so give the points to Steven.
0
 
LVL 4

Accepted Solution

by:
StevenB earned 50 total points
Comment Utility
 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:
  stemail@dial.pipex.com

  Steven.
0

Featured Post

Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

Join & Write a Comment

Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
In this tutorial I will show you how to use the Windows Speech API in Delphi. I will only cover basic functions such as text to speech and controlling the speed of the speech. SAPI Installation First you need to install the SAPI type library, th…
Illustrator's Shape Builder tool will let you combine shapes visually and interactively. This video shows the Mac version, but the tool works the same way in Windows. To follow along with this video, you can draw your own shapes or download the file…
Access reports are powerful and flexible. Learn how to create a query and then a grouped report using the wizard. Modify the report design after the wizard is done to make it look better. There will be another video to explain how to put the final p…

771 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now