I have something to confess.
I have never truly understood the difference between RGB and HSL. There, I said it. :-D
Sure, I understood the basic idea, but I have never made an effort to investigate exactly how to convert from HSL to RGB. So, in an attempt to form a better understanding, I wrote a simple class that can convert from a HSL colour to a RGB colour. C# already exposes the GetHue(), GetSaturation() and GetBrigtness() methods on an object instance of type System.Drawing.Color, so the conversion was a one way affair from HSL to RGB.
The code below is pretty self-explanatory. I implemented the conversion routine from Wikipedia and tested drawing some graphs using GDI+.
//----------------------------------------------------------------------
// Namespace Christo.GFX.Conversion
// Author: Christo Greeff
// Date: 24/June/2008
//----------------------------------------------------------------------
// 24/June/2008 : New: GFXConversionException, HSL
//----------------------------------------------------------------------
using System;
using System.Drawing;
namespace Christo.GFX.Conversion
{
/// <summary>
/// Color Util Exception
/// </summary>
public class GFXConversionException : Exception
{
public GFXConversionException(string message, Exception innerException)
: base(message, innerException)
{
//ToDo: Implement if needed
}
}
/// <summary>
/// HSL (Hue/Saturation/Luminance) Class
/// </summary>
public class HSL
{
#region Private members
/// <summary>
/// private hue
/// </summary>
private double _h;
/// <summary>
/// private saturation
/// </summary>
private double _s;
/// <summary>
/// private luminance
/// </summary>
private double _l;
#endregion
/// <summary>
/// Constructor
/// </summary>
public HSL()
{
try
{
this.HSLHelper(0, 0, 0);
}
catch (Exception ee)
{
throw new GFXConversionException("HSL Constructor Error", ee);
}
}
/// <summary>
/// Constructor with ARGB color
/// </summary>
/// <param name="color">System.Drawing.Color value</param>
public HSL(Color color)
{
try
{
this.HSLHelper(color.GetHue() / 360.0, color.GetSaturation(), color.GetBrightness());
}
catch (Exception ee)
{
throw new GFXConversionException("HSL Constructor Error", ee);
}
}
/// <summary>
/// Constructor with RGB color
/// </summary>
/// <param name="R">Red component with a value from 0 to 255</param>
/// <param name="G">Green component with a value from 0 to 255</param>
/// <param name="B">Blue component with a value from 0 to 255</param>
public HSL(byte R, byte G, byte B)
{
try
{
Color temp = Color.FromArgb(R, G, B);
this.HSLHelper(temp.GetHue() / 360.0, temp.GetSaturation(), temp.GetBrightness());
}
catch (Exception ee)
{
throw new GFXConversionException("HSL Constructor Error", ee);
}
}
/// <summary>
/// Constructor with HSL
/// </summary>
/// <param name="Hue">Varies from magenta - red - yellow - green - cyan - blue - magenta, described as an angle around a circle from 0.0 - 360.0 degrees</param>
/// <param name="Saturation">Varies from 0.0 and 1.0 and describes how "grey" the colour is, with 0 being completely unsaturated (grey, white or black) and 1 being completely saturated</param>
/// <param name="Luminance">Varies from 0.0 and 1.0 and ranges from black at 0.0, through the standard colour itself at 0.5 to white at 1.0</param>
public HSL(double Hue, double Saturation, double Luminance)
{
try
{
this.HSLHelper(Hue, Saturation, Luminance);
}
catch (Exception ee)
{
throw new GFXConversionException("HSL Constructor Error", ee);
}
}
/// <summary>
/// HSL Helper
/// </summary>
/// <param name="Hue">Varies from magenta - red - yellow - green - cyan - blue - magenta, described as an angle around a circle from 0.0 - 360.0 degrees</param>
/// <param name="Saturation">Varies from 0.0 and 1.0 and describes how "grey" the colour is, with 0 being completely unsaturated (grey, white or black) and 1 being completely saturated</param>
/// <param name="Luminance">Varies from 0.0 and 1.0 and ranges from black at 0.0, through the standard colour itself at 0.5 to white at 1.0</param>
private void HSLHelper(double Hue, double Saturation, double Luminance)
{
try
{
this.H = Hue;
this.S = Saturation;
this.L = Luminance;
}
catch (Exception ee)
{
throw new GFXConversionException("HSL HSLHelper Error", ee);
}
}
/// <summary>
/// Set helper function
/// </summary>
/// <param name="value">value that must be between 0 and 1</param>
/// <returns>double value</returns>
private double SetHelper(double value)
{
try
{
return (value > 1) ? 1.0 : (value < 0) ? 0 : value;
}
catch (Exception ee)
{
throw new GFXConversionException("HSL SetHelper Error", ee);
}
}
/// <summary>
/// Gets or sets the Hue value
/// </summary>
public double H
{
get
{
return this._h;
}
set
{
this._h = this.SetHelper(value);
}
}
/// <summary>
/// Gets or sets the Saturation value
/// </summary>
public double S
{
get
{
return this._s;
}
set
{
this._s = this.SetHelper(value);
}
}
/// <summary>
/// Gets or sets the Luminance value
/// </summary>
public double L
{
get
{
return this._l;
}
set
{
this._l = this.SetHelper(value);
}
}
/// <summary>
/// <para>Convert from the current HSL to RGB</para>
/// <para>http://en.wikipedia.org/wiki/HSV_color_space#Conversion_from_HSL_to_RGB</para>
/// </summary>
public Color Color
{
get
{
double[] t = new double[] { 0, 0, 0 };
try
{
double tH = this._h;
double tS = this._s;
double tL = this._l;
if (tS.Equals(0))
{
t[0] = t[1] = t[2] = tL;
}
else
{
double q, p;
q = tL < 0.5 ? tL * (1 + tS) : tL + tS - (tL * tS);
p = 2 * tL - q;
t[0] = tH + (1.0 / 3.0);
t[1] = tH;
t[2] = tH - (1.0 / 3.0);
for (byte i = 0; i < 3; i++)
{
t[i] = t[i] < 0 ? t[i] + 1.0 : t[i] > 1 ? t[i] - 1.0 : t[i];
if (t[i] * 6.0 < 1.0)
t[i] = p + ((q - p) * 6 * t[i]);
else
if (t[i] * 2.0 < 1.0)
t[i] = q;
else
if (t[i] * 3.0 < 2.0)
t[i] = p + ((q - p) * 6 * ((2.0 / 3.0) - t[i]));
else
t[i] = p;
}
}
}
catch (Exception ee)
{
throw new GFXConversionException("HSL Color Error", ee);
}
return Color.FromArgb((int)(t[0] * 255), (int)(t[1] * 255), (int)(t[2] * 255));
}
}
/// <summary>
/// Modify the current HSL brightness
/// </summary>
/// <param name="brightness">brightness value</param>
/// <returns>HSL</returns>
private HSL BrightnessHelper(double brightness)
{
try
{
this.L = this.L * brightness;
}
catch (Exception ee)
{
throw new GFXConversionException("HSL BrightnessHelper Error", ee);
}
return new HSL(H, S, L);
}
/// <summary>
/// Modify the current HSL brightness
/// </summary>
/// <param name="brightness">brightness value</param>
/// <returns>HSL</returns>
public HSL Brightness(double brightness)
{
return this.BrightnessHelper(brightness);
}
/// <summary>
/// Modify the current HSL brightness
/// </summary>
/// <param name="brightness">brightness value</param>
/// <returns>Color</returns>
public Color BrightnessC(double brightness)
{
return this.BrightnessHelper(brightness).Color;
}
}
}
As always, comments are welcome. Yes, even the critiquing ones.
Environment:
Microsoft ® Visual Studio 2008
Microsoft ® .NET framework 3.5
Microsoft ® Windows XP (SP3)
3 comments:
Hi,
I never understood it either. My first thought was: Oh, now I can create a new color :-/
Never had the slightest bit of enthousiasm to investigate it, because of my brain.
It seems more like another perspective/approach of dealing with colors, I suppose. Perhaps eases up things I can't think of right now because of my brain.
Anyway, I'm gonna push the execute code button soon ...from a safe distance ;-)
The thing is that when you have to compare for example the color red with another redshade color you can use HSL to change the saturation value and get the full bright color, if you get what i mean ;)
A simpler routine for HSV->RGB:
private static Color HsvToRgbColor(double hue, double saturation, double value)
{
Debug.Assert(hue >= 0);
Debug.Assert(hue <= 1);
Debug.Assert(saturation >= 0);
Debug.Assert(saturation <= 1);
Debug.Assert(value >= 0);
Debug.Assert(value <= 1);
double chroma = value * saturation;
double h = 6 * hue;
double x = chroma * (1 - Math.Abs(h % 2 - 1));
double r1 = 0;
double g1 = 0;
double b1 = 0;
switch ((int)Math.Floor(h))
{
case 0: r1 = chroma; g1 = x; break;
case 1: r1 = x; g1 = chroma; break;
case 2: g1 = chroma; b1 = x; break;
case 3: g1 = x; b1 = chroma; break;
case 4: r1 = chroma; b1 = x; break;
case 5: r1 = x; b1 = chroma; break;
default: break;
}
double m = value - chroma;
double r = r1 + m;
double g = g1 + m;
double b = b1 + m;
return Color.FromArgb((int)(255 * r), (int)(255 * g), (int)(255 * b));
}
Post a Comment