Learn how to build an E-Commerce site with Angular 5, a JavaScript framework used by developers to build web, desktop, and mobile applications.

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.

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.

Experts Exchange Solution brought to you by

Enjoy your complimentary solution view.

Get every solution instantly with Premium.
Start your 7-day free trial.

I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

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

if (hue < (HLSMAX/2))

return ( n2 );

if (hue < ((HLSMAX*2)/3))

return ( n1 + (((n2-n1)*(((HLSMAX*2)/3)-

);

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,hu

(HLSMAX/2))/HLSMAX;

G = (HueToRGB(Magic1,Magic2,hu

B = (HueToRGB(Magic1,Magic2,hu

(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

The functions which I have written for the conversions are :

procedure HSLtoRGB(H,S,L:Integer;var

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

var

Delta : Double;

CMax,CMin : Double;

Red,Green,Blue,Hue,Sat,Lum

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.

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?

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?

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

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)

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Delphi

From novice to tech pro — start learning today.

Experts Exchange Solution brought to you by

Enjoy your complimentary solution view.

Get every solution instantly with Premium.
Start your 7-day free trial.