GDI+ FAQ: RGB and HSL Colour Space Conversions.
The classic RGB
colour space used in GDI+ is excellent for choosing or defining a specific colour as a mixture of
primary colour and intensity values but what happens if you want to
take a particular colour and make it a bit lighter or a bit darker or
change its saturation. For this you need to be able to use the HSL (Hue,
Saturation and Luminance) colour space.
Note: HSB ( Hue Saturation and Brightness) is
synonymous with HSL.
The .NET framework
actually provides RGB-HSL conversion but for some reason, the conversion
of HSL to RGB is not provided as a public method. The Color class has
three methods, GetHue, GetSaturation and GetBrightness that provide the
basic components of the HSL (or HSB) colour space. The conversion of HSL
to RGB is a well known algorithm that you can find in numerous places on
the web. Oddly enough, down in the bowels of the ControlPaint class, RGB
to HSL is employed when you use the ControlPaint.Light(…) or
ControlPaint.DarkDark(…) methods but also unfortunately the scaling of the
Light, Dark and DarkDark settings are, to say the least, brutal and offer
no finesse.
A specific use for
RGB-HSL conversions I have used in the past, is to provide colours that
enhance objects drawn in a psuedo-3D or isometric style. In such a
drawing, a cube having a specific colour, say red, may be shown with a
lighter red on top to simulate a shine on the object and a darker colour
on one side to simulate a shadow. Figure1 shows two such cubes with and
without shading yet still using red as the basic colour.

Figure
1
In addition to
making a chosen colour lighter or darker, you may wish to choose a range
of colour values that all
have a similar brightness. Not an easy task where RGB is concerned but
trivial using the HSL colour space. Similarly, you may wish to choose
random colours but ensure that they have a uniform level of saturation to
ensure bright, non-pastel colours for ease of visibility. Once again, a
task eminently possible if using HSL colour definitions.
If you're interested
in further reading on the differences between colour spaces,
this
link will take you to an excellent article on the Apple site which
explains them nicely.
To accompany this
FAQ entry I have provided the source code shown in listing 1. It provides
an RGB-HSL and HSL-RGB conversion plus a few methods that assist in
setting or modifying a brightness.
Listing 1:
/*
This tool is part of the xRay Toolkit and is provided free of charge by
Bob Powell.
* This code is not guaranteed to be
free from defects or fit for merchantability in any way.
* By using this tool in your own
programs you agree to hold Robert W. Powell free from all
* damages direct or incidental that
arise from such use.
* You may use this code free of
charge in your own projects on condition that you place the
* following paragraph (enclosed in
quotes below) in your applications help or about dialog.
* "Portions of this code provided
by Bob Powell. http://www.bobpowell.net"
* If you found this code useful and
would like to make a donation towards the upkeep of the
* GDI+ FAQ you can send a sum of
your choice via PayPal to bob@bobpowell.net.
*/
using
System;
using
System.Drawing;
namespace
xRay.Toolkit.Utilities
{
public class
RGBHSL
{
public class
HSL
{
public HSL()
{
_h=0;
_s=0;
_l=0;
}
double _h;
double _s;
double _l;
public double
H
{
get{return
_h;}
set
{
_h=value;
_h=_h>1
? 1 : _h<0 ? 0 : _h;
}
}
public double
S
{
get{return
_s;}
set
{
_s=value;
_s=_s>1
? 1 : _s<0 ? 0 : _s;
}
}
public double
L
{
get{return
_l;}
set
{
_l=value;
_l=_l>1
? 1 : _l<0 ? 0 : _l;
}
}
}
public RGBHSL()
{
}
/// <summary>
/// Sets the
absolute brightness of a colour
/// </summary>
/// <param name="c">Original colour</param>
/// <param name="brightness">The luminance level to impose</param>
/// <returns>an
adjusted colour</returns>
public static
Color SetBrightness(Color c, double brightness)
{
HSL hsl = RGB_to_HSL(c);
hsl.L=brightness;
return HSL_to_RGB(hsl);
}
/// <summary>
/// Modifies an
existing brightness level
/// </summary>
/// <remarks>
/// To reduce
brightness use a number smaller than 1. To increase brightness use a
number larger tnan 1
/// </remarks>
/// <param name="c">The original colour</param>
/// <param name="brightness">The luminance delta</param>
/// <returns>An
adjusted colour</returns>
public static
Color ModifyBrightness(Color c, double brightness)
{
HSL hsl = RGB_to_HSL(c);
hsl.L*=brightness;
return HSL_to_RGB(hsl);
}
/// <summary>
/// Sets the
absolute saturation level
/// </summary>
/// <remarks>Accepted values 0-1</remarks>
/// <param name="c">An original colour</param>
/// <param name="Saturation">The saturation value to impose</param>
/// <returns>An
adjusted colour</returns>
public static
Color SetSaturation(Color c, double Saturation)
{
HSL hsl = RGB_to_HSL(c);
hsl.S=Saturation;
return HSL_to_RGB(hsl);
}
/// <summary>
/// Modifies an
existing Saturation level
/// </summary>
/// <remarks>
/// To reduce
Saturation use a number smaller than 1. To increase Saturation use a
number larger tnan 1
/// </remarks>
/// <param name="c">The original colour</param>
/// <param name="Saturation">The saturation delta</param>
/// <returns>An
adjusted colour</returns>
public static
Color ModifySaturation(Color c, double Saturation)
{
HSL hsl = RGB_to_HSL(c);
hsl.S*=Saturation;
return HSL_to_RGB(hsl);
}
/// <summary>
/// Sets the
absolute Hue level
/// </summary>
/// <remarks>Accepted values 0-1</remarks>
/// <param name="c">An original colour</param>
/// <param name="Hue">The Hue value to impose</param>
/// <returns>An
adjusted colour</returns>
public static
Color SetHue(Color c, double
Hue)
{
HSL hsl = RGB_to_HSL(c);
hsl.H=Hue;
return HSL_to_RGB(hsl);
}
/// <summary>
/// Modifies an
existing Hue level
/// </summary>
/// <remarks>
/// To reduce Hue
use a number smaller than 1. To increase Hue use a number larger tnan
1
/// </remarks>
/// <param name="c">The original colour</param>
/// <param name="Hue">The Hue delta</param>
/// <returns>An
adjusted colour</returns>
public static
Color ModifyHue(Color c, double
Hue)
{
HSL hsl = RGB_to_HSL(c);
hsl.H*=Hue;
return HSL_to_RGB(hsl);
}
/// <summary>
/// Converts a
colour from HSL to RGB
/// </summary>
/// <remarks>Adapted from the algoritm in Foley and
Van-Dam</remarks>
/// <param name="hsl">The HSL value</param>
/// <returns>A
Color structure containing the equivalent RGB values</returns>
public static
Color HSL_to_RGB(HSL hsl)
{
double r=0,g=0,b=0;
double temp1,temp2;
if(hsl.L==0)
{
r=g=b=0;
}
else
{
if(hsl.S==0)
{
r=g=b=hsl.L;
}
else
{
temp2
= ((hsl.L<=0.5) ? hsl.L*(1.0+hsl.S) : hsl.L+hsl.S-(hsl.L*hsl.S));
temp1
= 2.0*hsl.L-temp2;
double[] t3=new double[]{hsl.H+1.0/3.0,hsl.H,hsl.H-1.0/3.0};
double[] clr=new double[]{0,0,0};
for(int
i=0;i<3;i++)
{
if(t3[i]<0)
t3[i]+=1.0;
if(t3[i]>1)
t3[i]-=1.0;
if(6.0*t3[i] < 1.0)
clr[i]=temp1+(temp2-temp1)*t3[i]*6.0;
else if(2.0*t3[i] < 1.0)
clr[i]=temp2;
else if(3.0*t3[i] < 2.0)
clr[i]=(temp1+(temp2-temp1)*((2.0/3.0)-t3[i])*6.0);
else
clr[i]=temp1;
}
r=clr[0];
g=clr[1];
b=clr[2];
}
}
return Color.FromArgb((int)(255*r),(int)(255*g),(int)(255*b));
}
//
/// <summary>
/// Converts RGB to
HSL
/// </summary>
/// <remarks>Takes
advantage of whats already built in to
.NET by using the Color.GetHue,
Color.GetSaturation and Color.GetBrightness methods</remarks>
/// <param name="c">A Color to convert</param>
/// <returns>An
HSL value</returns>
public static
HSL RGB_to_HSL (Color c)
{
HSL hsl = new HSL();
hsl.H=c.GetHue()/360.0; // we store hue as 0-1 as opposed to 0-360
hsl.L=c.GetBrightness();
hsl.S=c.GetSaturation();
return hsl;
}
}
}
Example using the RGBHSL class to modify
brightness
This example shows
how colours may be modified for brightness by simply setting their
luminance levels. The application below uses the RGBHSL class to display a
palette of colours taken from the seven classic colours of the rainbow.
Figure 2 shows the program in action.

Figure
2
Listing 2 is the
complete listing for this test application.
Listing 2.
using System;
using System.Drawing;
using
System.Collections;
using
System.ComponentModel;
using
System.Windows.Forms;
using System.Data;
using
xRay.Toolkit.Utilities;
namespace rgbhsltest
{
/// <summary>
/// Summary
description for Form1.
/// </summary>
public class
Form1 : System.Windows.Forms.Form
{
/// <summary>
/// Required
designer variable.
/// </summary>
private System.ComponentModel.Container
components = null;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
SetStyle(ControlStyles.ResizeRedraw,true);
}
/// <summary>
/// Clean up any
resources being used.
/// </summary>
protected override void
Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer
generated code
/// <summary>
/// Required method
for Designer support - do not modify
/// the contents of
this method with the code editor.
/// </summary>
private void
InitializeComponent()
{
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.BackColor =
System.Drawing.Color.White;
this.ClientSize = new System.Drawing.Size(600, 349);
this.Name = "Form1";
this.Text = "Form1";
this.Paint += new
System.Windows.Forms.PaintEventHandler(this.Form1_Paint);
}
#endregion
/// <summary>
/// The main entry
point for the application.
/// </summary>
[STAThread]
static void
Main()
{
Application.Run(new Form1());
}
private void
Form1_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
Color[]
rainbow=new
Color[]{Color.Red,
Color.Orange,Color.Yellow,Color.Green,Color.Blue,Color.Indigo,Color.Violet};
int n=0;
foreach(Color c in rainbow)
{
for(double
b=0;b<=1;b+=0.1)
{
SolidBrush
br=new
SolidBrush(RGBHSL.SetBrightness(c,b));
e.Graphics.FillRectangle(br,
(float)(this.ClientSize.Width*b),
(float)(this.ClientSize.Height/7*n), (float)(this.ClientSize.Width/10), (float)(this.ClientSize.Height/6));
br.Dispose();
}
n++;
}
}
}
}
Example using the RGBHSL class to
force brightness and saturation
This example creates a set of random
colours and then enforces a particular saturation and brightness. This
tends to maintain the visual strength of colours so that when juxtaposed,
one colour does not dominate the others and draw the eye to a particular
shade. This is particularly useful in graphing applications where you want
the visual representation of the data to be evenly balanced. Figure 3 shows 100 randomly coloured tiles
that have all been forced to a brightness of 0.6 (60%) and saturation of 1
(100%).

Figure 3.
Listing 3 shows the application that
produced the effect.
using System;
using System.Drawing;
using
System.Collections;
using
System.ComponentModel;
using
System.Windows.Forms;
using System.Data;
using
xRay.Toolkit.Utilities;
namespace rgbhlstest2
{
/// <summary>
/// Summary
description for Form1.
/// </summary>
public class
Form1 : System.Windows.Forms.Form
{
/// <summary>
/// Required
designer variable.
/// </summary>
private System.ComponentModel.Container
components = null;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
|