HSLColor Class

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

namespace ColorDemo
{
    public class HSLColor
    {
        // Private data members below are on scale 0-1
        // They are scaled for use externally based on scale
        private double hue = 1.0;
        private double saturation = 1.0;
        private double luminosity = 1.0;

        private const double scale = 240.0;

        public double Hue
        {
            get { return hue * scale; }
            set { hue = CheckRange(value / scale); }
        }
        public double Saturation
        {
            get { return saturation * scale; }
            set { saturation = CheckRange(value / scale); }
        }
        public double Luminosity
        {
            get { return luminosity * scale; }
            set { luminosity = CheckRange(value / scale); }
        }

        private double CheckRange(double value)
        {
            if (value < 0.0)
                value = 0.0;
            else if (value > 1.0)
                value = 1.0;
            return value;
        }

        public override string ToString()
        {
            return String.Format("H: {0:#0.##} S: {1:#0.##} L: {2:#0.##}"HueSaturationLuminosity);
        }

        public string ToRGBString()
        {
            Color color = (Color)this;
            return String.Format("R: {0:#0.##} G: {1:#0.##} B: {2:#0.##}"color.Rcolor.Gcolor.B);
        }

        #region Casts to/from System.Drawing.Color
        public static implicit operator Color(HSLColor hslColor)
        {
            double r = 0, g = 0, b = 0;
            if (hslColor.luminosity != 0)
            {
                if (hslColor.saturation == 0)
                    r = g = b = hslColor.luminosity;
                else
                {
                    double temp2 = GetTemp2(hslColor);
                    double temp1 = 2.0 * hslColor.luminosity - temp2;

                    r = GetColorComponent(temp1temp2hslColor.hue + 1.0 / 3.0);
                    g = GetColorComponent(temp1temp2hslColor.hue);
                    b = GetColorComponent(temp1temp2hslColor.hue - 1.0 / 3.0);
                }
            }
            return Color.FromArgb((int)(255 * r), (int)(255 * g), (int)(255 * b));
        }

        private static double GetColorComponent(double temp1double temp2double temp3)
        {
            temp3 = MoveIntoRange(temp3);
            if (temp3 < 1.0 / 6.0)
                return temp1 + (temp2 - temp1) * 6.0 * temp3;
            else if (temp3 < 0.5)
                return temp2;
            else if (temp3 < 2.0 / 3.0)
                return temp1 + ((temp2 - temp1) * ((2.0 / 3.0) - temp3) * 6.0);
            else
                return temp1;
        }
        private static double MoveIntoRange(double temp3)
        {
            if (temp3 < 0.0)
                temp3 += 1.0;
            else if (temp3 > 1.0)
                temp3 -= 1.0;
            return temp3;
        }
        private static double GetTemp2(HSLColor hslColor)
        {
            double temp2;
            if (hslColor.luminosity < 0.5)  //<=??
                temp2 = hslColor.luminosity * (1.0 + hslColor.saturation);
            else
                temp2 = hslColor.luminosity + hslColor.saturation - (hslColor.luminosity * hslColor.saturation);
            return temp2;
        }

        public static implicit operator HSLColor(Color color)
        {
            HSLColor hslColor = new HSLColor();
            hslColor.hue = color.GetHue() / 360.0; // we store hue as 0-1 as opposed to 0-360 
            hslColor.luminosity = color.GetBrightness();
            hslColor.saturation = color.GetSaturation();
            return hslColor;
        }
        #endregion

        public void SetRGB(int redint greenint blue)
        {
            HSLColor hslColor = (HSLColor)Color.FromArgb(redgreenblue);
            this.hue = hslColor.hue;
            this.saturation = hslColor.saturation;
            this.luminosity = hslColor.luminosity;
        }

        public HSLColor() { }
        public HSLColor(Color color)
        {
            SetRGB(color.Rcolor.Gcolor.B);
        }
        public HSLColor(int redint greenint blue)
        {
            SetRGB(redgreenblue);
        }
        public HSLColor(double huedouble saturationdouble luminosity)
        {
            this.Hue = hue;
            this.Saturation = saturation;
            this.Luminosity = luminosity;
        }

    }
}

31 thoughts on “HSLColor Class

  1. Rich,

    Thank you very much. This code has been a godsend. I really wish that there was something native in .NET…

    One minor point – in the cast from color to HSLColor you are directly casting the RGB values to int, which is truncating them (253.99 -> 253). So, if you do a round-trip conversion of a color to an HSLColor and back, you might get a different color. I discovered this when I started with “#fefffe” translated to HSL and back to HTML which came back as “#fdfffd”.

    The correct code should be:

    return Color.FromArgb(
    Convert.ToInt32(255 * r),
    Convert.ToInt32(255 * g),
    Convert.ToInt32(255 * b));

    Thanks again posting. Very much appreciated.

    1. I don’t think this is accurate either. I’m having the same problem, but the color is hsl(210,83,48) which is a blue comes back in rgb as a red in both the OP and your correction. :/

      1. I think your problem is that you are using the standard scale for S and L of 0 – 100, while this program (and Microsoft) use a scale of 0 – 240 for S and L.

    2. Steve, thanks for this fix, and thanks to Rich Newman for the original series of articles. I’ve seen a couple of similar programs on the Internet, and they all seem to share this same bug.

  2. Fantastic work, my humble contrib

    Hey you can remove the setRGB method by using constructor chaining

    public HslColor(Color Color) : this(Color.R, Color.G, Color.B)
    {
    }

    public HslColor(int Red, int Green, int Blue)
    {
    HslColor HslColor = Color.FromArgb(Red, Green, Blue);
    _Hue = HslColor._Hue;
    _Saturation = HslColor._Saturation;
    _Luminosity = HslColor._Luminosity;
    }

  3. And you can remove the 4 redundant else keyword in GetColorComponent() and use the objectinitializer in Color to HSLColor implicit conversion method

  4. I seem to be having problems getting certain colors to show up as anything but white.

    Here’s an example (scale in hslcolor is 360):

    System.Drawing.Color[] colorArray = new System.Drawing.Color[19];
    for (int hue = 0; hue < 19; hue++)
    {
    colorArray[hue] = (System.Drawing.Color)new HSLColor(hue * 20, 0.25 * 360, 1.0 * 360);
    }

    Every single one of these colors will come out as RGB(255,255,255)

  5. Hi guys,Can this be used to change the color of some part of the image? can anybody guide me how?

  6. Thanks for sharing this code. It worked fine with a gradient coloring test app I made. I need to know, are there any license terms and conditions this listing is under?
    Thanks!

    Petar T.

    1. It’s just an implicit cast, so you can do something like:

      HSLColor hslColor = new HSLColor(hue: 50.0, saturation: 50.0, luminosity: 50.0);
      System.Drawing.Color color = hslColor;

  7. I have made a few improvements for anyone that is working with modern CSS will appreciate…

    First, I changed the signature for RGB to take in ARGB. That way I can pass HSL as int values that are implicitly changed. A la I can pass 20 vs 20.0.

    Second, I broke out the scales so Hue is 360. Saturation is 100, and Luminance is 100. Enjoy!

    public class HSLColor
    {
    // Private data members below are on scale 0-1
    // They are scaled for use externally based on scale
    private double hue = 1.0;
    private double saturation = 1.0;
    private double luminosity = 1.0;

    private const double scaleH = 360.0;
    private const double scaleS = 100.0;
    private const double scaleL = 100.0;

    #region Constructors

    public HSLColor() { }
    public HSLColor(Color color) : this(color.A, color.R, color.G, color.B)
    {
    }

    public HSLColor(int alpha, int red, int green, int blue)
    {
    HSLColor hslColor = (HSLColor)Color.FromArgb(alpha, red, green, blue);
    this.hue = hslColor.hue;
    this.saturation = hslColor.saturation;
    this.luminosity = hslColor.luminosity;
    }

    public HSLColor(double hue, double saturation, double luminosity)
    {
    this.Hue = hue;
    this.Saturation = saturation;
    this.Luminosity = luminosity;
    }

    #endregion

    public double Hue
    {
    get { return hue * scaleH; }
    set { hue = CheckRange(value / scaleH); }
    }

    public double Saturation
    {
    get { return saturation * scaleS; }
    set { saturation = CheckRange(value / scaleS); }
    }

    public double Luminosity
    {
    get { return luminosity * scaleL; }
    set { luminosity = CheckRange(value / scaleL); }
    }

    private double CheckRange(double value)
    {
    if (value 1.0)
    value = 1.0;
    return value;
    }

    public override string ToString()
    {
    return String.Format(“H: {0:#0.##} S: {1:#0.##} L: {2:#0.##}”, Hue, Saturation, Luminosity);
    }

    public string ToRGBString()
    {
    Color color = (Color)this;
    return String.Format(“R: {0:#0.##} G: {1:#0.##} B: {2:#0.##}”, color.R, color.G, color.B);
    }

    #region Casts to/from System.Drawing.Color
    public static implicit operator Color(HSLColor hslColor)
    {
    double r = 0, g = 0, b = 0;
    if (hslColor.luminosity != 0) {
    if (hslColor.saturation == 0)
    r = g = b = hslColor.luminosity;
    else {
    double temp2 = GetTemp2(hslColor);
    double temp1 = 2.0 * hslColor.luminosity – temp2;

    r = GetColorComponent(temp1, temp2, hslColor.hue + 1.0 / 3.0);
    g = GetColorComponent(temp1, temp2, hslColor.hue);
    b = GetColorComponent(temp1, temp2, hslColor.hue – 1.0 / 3.0);
    }
    }
    return Color.FromArgb((int)(255 * r), (int)(255 * g), (int)(255 * b));
    }

    private static double GetColorComponent(double temp1, double temp2, double temp3)
    {
    temp3 = MoveIntoRange(temp3);
    if (temp3 < 1.0 / 6.0)
    return temp1 + (temp2 – temp1) * 6.0 * temp3;
    else if (temp3 < 0.5)
    return temp2;
    else if (temp3 < 2.0 / 3.0)
    return temp1 + ((temp2 – temp1) * ((2.0 / 3.0) – temp3) * 6.0);
    else
    return temp1;
    }

    private static double MoveIntoRange(double temp3)
    {
    if (temp3 1.0)
    temp3 -= 1.0;
    return temp3;
    }

    private static double GetTemp2(HSLColor hslColor)
    {
    double temp2;
    if (hslColor.luminosity < 0.5) //<=??
    temp2 = hslColor.luminosity * (1.0 + hslColor.saturation);
    else
    temp2 = hslColor.luminosity + hslColor.saturation – (hslColor.luminosity * hslColor.saturation);
    return temp2;
    }

    public static implicit operator HSLColor(Color color)
    {
    HSLColor hslColor = new HSLColor();
    hslColor.hue = color.GetHue() / 360.0; // we store hue as 0-1 as opposed to 0-360
    hslColor.luminosity = color.GetBrightness();
    hslColor.saturation = color.GetSaturation();
    return hslColor;
    }

    #endregion

    }

Leave a comment