(X) Hide this SilverlightShow next training events: Secure & Personalize Your Silverlight App with WCF RIA Services. May 25th, 10 am PST. Sign up
XNA for Windows Phone 7 - a 3 day training by MCC Peter Kuhn. June 1-3, 9 am - 1.30 pm PST. Sign up early-bird for $199.
Skip Navigation LinksHome / Articles / View Article

HSL Colors in Silverlight

+ Add to SilverlightShow Favorites
0 comments   /   aggregated from Silverlight Brass Tacks on May 03, 2008  /  original article
(0 votes)
Categories: Tutorials , Tips and Tricks

Source code: http://www.bluerosegames.com/hslcolorsample.zip

On a recent project (showing up soon on Blue Rose Games) I had the need to create some objects that were identical except for their color. So I created a UserControl for the object, and gave it a property to let me select the color. The problem is, this object uses gradients to get the desired effect, and the color of the gradient stops was a variation of some base color, keeping the color consistent but making it lighter or darker.

If you've ever done work with color, possibly in Photoshop or something similar, you know that adjusting the lightness of a color using the typical Red, Green, and Blue color components is difficult. Photoshop typically converts the color to HSL (standing for Hue, Saturation, and Lightness) and then changes the Lightness value, leaving the Hue and Saturation consistent.

So I did a quick search on the web and found that the formulas for converting back and forth between RBG color and HSL color were on Wikipedia at http://en.wikipedia.org/wiki/HSV_color_space. Without too much work, I was able to create an HslColor struct which could convert values back and forth between RGB and HSL. This is the code:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
 
namespace HslColorSample
{
    public struct HslColor
    {
        // value from 0 to 1 
        public double A;
        // value from 0 to 360 
        public double H;
        // value from 0 to 1 
        public double S;
        // value from 0 to 1 
        public double L;
 
        private static double ByteToPct(byte v)
        {
            double d = v;
            d /= 255;
            return d;
        }
 
        private static byte PctToByte(double pct)
        {
            pct *= 255;
            pct += .5;
            if (pct > 255) pct = 255;
            if (pct < 0) pct = 0;
            return (byte)pct;
        }
 
        public static HslColor FromColor(Color c)
        {
            return HslColor.FromArgb(c.A, c.R, c.G, c.B);
        }
 
        public static HslColor FromArgb(byte A, byte R, byte G, byte B)
        {
            HslColor c = FromRgb(R, G, B);
            c.A = ByteToPct(A);
            return c;
        }
 
        public static HslColor FromRgb(byte R, byte G, byte B)
        {
            HslColor c = new HslColor();
            c.A = 1;
            double r = ByteToPct(R);
            double g = ByteToPct(G);
            double b = ByteToPct(B);
            double max = Math.Max(b, Math.Max(r, g));
            double min = Math.Min(b, Math.Min(r, g));
            if (max == min)
            {
                c.H = 0;
            }
            else if (max == r && g >= b)
            {
                c.H = 60 * ((g - b) / (max - min));
            }
            else if (max == r && g < b)
            {
                c.H = 60 * ((g - b) / (max - min)) + 360;
            }
            else if (max == g)
            {
                c.H = 60 * ((b - r) / (max - min)) + 120;
            }
            else if (max == b)
            {
                c.H = 60 * ((r - g) / (max - min)) + 240;
            }
 
            c.L = .5 * (max + min);
            if (max == min)
            {
                c.S = 0;
            }
            else if (c.L <= .5)
            {
                c.S = (max - min) / (2 * c.L);
            }
            else if (c.L > .5)
            {
                c.S = (max - min) / (2 - 2 * c.L);
            }
            return c;
        }
 
        public HslColor Lighten(double pct)
        {
            HslColor c = new HslColor();
            c.A = this.A;
            c.H = this.H;
            c.S = this.S;
            c.L = Math.Min(Math.Max(this.L + pct, 0), 1);
            return c;
        }
 
        public HslColor Darken(double pct)
        {
            return Lighten(-pct);
        }
 
        private double norm(double d)
        {
            if (d < 0) d += 1;
            if (d > 1) d -= 1;
            return d;
        }
 
        private double getComponent(double tc, double p, double q)
        {
            if (tc < (1.0 / 6.0))
            {
                return p + ((q - p) * 6 * tc);
            }
            if (tc < .5)
            {
                return q;
            }
            if (tc < (2.0 / 3.0))
            {
                return p + ((q - p) * 6 * ((2.0 / 3.0) - tc));
            }
            return p;
        }
 
        public Color ToColor()
        {
            double q = 0;
            if (L < .5)
            {
                q = L * (1 + S);
            }
            else
            {
                q = L + S - (L * S);
            }
            double p = (2 * L) - q;
            double hk = H / 360;
            double r = getComponent(norm(hk + (1.0 / 3.0)), p, q);
            double g = getComponent(norm(hk), p, q);
            double b = getComponent(norm(hk - (1.0 / 3.0)), p, q);
            return Color.FromArgb(PctToByte(A), PctToByte(r), PctToByte(g), PctToByte(b));
        }
    }
}

 

The nice thing is though that you don't really have to know how this works to use it. All you have to know about is the FromColor and ToColor methods. Let's say I want to create a glassy ball (which, conveniently, is what I needed for my project). First I went into Blend and created an approximation of how I wanted it to look, not worrying too much about the colors since those will be replaced in code anyway, but getting the gradient stops in the right places:

"HslColorSample.GlassyMarble"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Width="110" Height="110">
    "PegRoot" Width="110" Height="110" Visibility="Visible" d:IsHidden="True">
            "110" Height="110" RenderTransformOrigin=".5,.5">
                
                    
                        "1.1" ScaleY="1.2"/>
                        "-5"/>
                        "5" Y="15"/>
                    
                
                
                    
                        ".7" Color="#A0000000"/>
                        ".98" Color="#00000000"/>
                    
                
            
            "100" Height="100">
                
                    ".47,.4">
                        "stop1" Offset="0" Color="#FF9999FF"/>
                        "stop2" Offset=".55" Color="#FF4444FF"/>
                        "stop3" Offset=".7" Color="#FF2222FF"/>
                        "stop4" Offset=".8" Color="#FF0000FF"/>
                        "stop5" Offset="1" Color="#FF000080"/>
                    
                
            
 
            "65" Height="40" Margin="0,-50,0,0">
                
                    "0,0" EndPoint="0,1">
                        "0" Color="#CCFFFFFF"/>
                        ".9" Color="#33FFFFFF"/>
                    
                
            
        

  

This already gives a pretty nice visual, like this:

In the XAML, I have named the gradient stops for the color of the marble so that they can be easily accessed from code. This section of the XAML looks like this:

".47,.4">
    "stop1" Offset="0" Color="#FF9999FF"/>
    "stop2" Offset=".55" Color="#FF4444FF"/>
    "stop3" Offset=".7" Color="#FF2222FF"/>
    "stop4" Offset=".8" Color="#FF0000FF"/>
    "stop5" Offset="1" Color="#FF000080"/>

  

Now I want stop4 to be the base color, the color that is exposed as a property. All of the other colors will derive from this. For this we'll add a public Color property to the GlassyMarble:

private Color color;
 
public Color Color
{
    set
    {
        color = value;
        HslColor hslBase = HslColor.FromColor(color);
        stop4.Color = color;
        stop5.Color = hslBase.Darken(.2).ToColor();
        stop3.Color = hslBase.Lighten(.05).ToColor();
        stop2.Color = hslBase.Lighten(.1).ToColor();
        stop1.Color = hslBase.Lighten(.4).ToColor();
    }
    get
    {
        return color;
    }
}

Note that the color passed in is converted to an HslColor, and then we use the Lighten and Darken methods to adjust the Lightness value of the color. Then we convert it back into a Color so that Silverlight can use it.

Now if we specify the color of the marble:

"#FF0000BB"/>

We get something that looks like this:

And if we change the color to something else, like a dark red:

"DarkRed"/>

We get all of the other colors changed appropriately:

Here's a few in a StackPanel:

"LayoutRoot" Background="White" Orientation="Horizontal">
    "#FF0000BB"/>
    "DarkRed"/>
    "DarkGreen"/>
    "Orange"/>

  

And the corresponding display:

There are things you can do with HSL besides adjusting Lightness, for example you can use variations of hue to find colors that go together for a page, such as complementary colors, or you could vary the saturation, for more information search on art color theory, and you'll get some results like this:

http://www.colormatters.com/colortheory.html

http://en.wikipedia.org/wiki/Color_theory

http://www.worqx.com/color/

Share


Comments

Comments RSS RSS
No comments

Add Comment

 
 

   
  
  
   
Please add 4 and 1 and type the answer here: