A centered explosion of colorful powder on a black background, hand-drawn by van gough

C# HSB – RGB vv conversion

The Phillips hue lamp can display colors out of two colorsystems: HSB/HSV and CIE 1931 xyY, while xyY consists of two values from 0-1 with floating point that display a point on the color systems color palette. This color system is very hard to display in a canvas cause of the proportions of the xy values.

This is why a color conversion is needed to have a proper interface between hardware and software, in this case the Phillips hue lamp and a canvas based control to configure the lamp’s color.

In generall HSB/HSV  consists of these components:

  • Hue in degrees from 0-359
  • Saturation from 0 – 1 with floating point
  • Brightness/Value from 0 – 1 with floating point

The hue lamps HSB is a little bit different:

  • Hue from 0 – 65535 (Hue in degrees * 182.04)
  • Saturation from 0 – 255
  • Brightness from 0 – 255

Implementation


To use for example a WPF colorcanvas to set the color on your hue lamp and receive the color from the hue lamp again to display it in your colorcanvas a little color conversion is needed.
Common colorpickers/canvas work on the RGB/HEX color system. So it is necessary to convert form RGB/HEX to HSB vice versa.

To make it easy to follow i want to make my own classes RGB and HSB like the following (the values can easily be parsed to a inbuilt .NET color classes :

public class RGB
{
   public int R { get; set; }
   public int G { get; set; }
   public int B { get; set; }
}

public class HSB
{
   public int Hue { get; set; }
   public int Saturation { get; set; }
   public int Brightness { get; set; }
}

To make the code as clean as possible I want to make class methods for each RGB and HSB. I will add some additional extensions for float and numbers to keep the code as simple as possible.

public static class FloatExtension
{
///
/// Tests equality with a certain amount of precision. Default to smallest possible double
///

    ///first value ///second value ///optional, smallest possible double value ///
    public static bool AlmostEquals(this float a, float b, double precision = float.Epsilon)
    {
        return Math.Abs(a - b) <= precision;
    }
    

    public static class Numbers
    {
        public static float Max(params float[] numbers)
        {
            return numbers.Max();
        }

        public static float Min(params float[] numbers)
        {
            return numbers.Min();
        }
    }
}

RGB to HSB conversion

There shall be a class method GETHSB() that will deliver the HSB value for the hue lamp.

public HSB GetHSB()
{
   var hsb = new HSB
   {
      Hue = (int)GetHue(),
      Saturation = (int)GetSaturation(),
      Brightness = (int)GetBrightness()
   };
   return hsb;
}

And this are the methods for conversion itself:

private float GetHue()
{
   if (R == G && G == B)
   return 0;

   var r = R / 255f;
   var g = G / 255f;
   var b = B / 255f;

   float hue;

   var min = Numbers.Min(r, g, b);
   var max = Numbers.Max(r, g, b);

   var delta = max - min;

   if (r.AlmostEquals(max))
      hue = (g - b) / delta; // between yellow & magenta
   else if (g.AlmostEquals(max))
      hue = 2 + (b - r) / delta; // between cyan & yellow
   else
      hue = 4 + (r - g) / delta; // between magenta & cyan

   hue *= 60; // degrees

   if (hue < 0)
   hue += 360;

   return hue * 182.04f;
}

private float GetSaturation()
{
   var r = R / 255f;
   var g = G / 255f;
   var b = B / 255f;

   var min = Numbers.Min(r, g, b);
   var max = Numbers.Max(r, g, b);

   if (max.AlmostEquals(min))
   return 0;
   return ((max.AlmostEquals(0f)) ? 0f : 1f - (1f * min / max)) * 255;
}

private float GetBrightness()
{
   var r = R / 255f;
   var g = G / 255f;
   var b = B / 255f;

   return Numbers.Max(r, g, b) * 255;
}

Thats it. The HSB object delivered can be directly used for the hue lamp.

Now comes the more tricky part.

HSB to RGB conversion

There also shall be a class method GetRGB that delivers common RGB values.

public RGB GetRGB()
{
   var hue = (double)Hue;
   var saturation = (double)Saturation;
   var brightness = (double)Brightness;

   //Convert Hue into degrees for HSB
   hue = hue / 182.04;
   //Bri and Sat must be values from 0-1 (~percentage)
   brightness = brightness / 255.0;
   saturation = saturation / 255.0;

   double r = 0;
   double g = 0;
   double b = 0;

   if (saturation == 0)
   {
      r = g = b = brightness;
   }
   else
   {
      // the color wheel consists of 6 sectors.
      double sectorPos = hue / 60.0;
      int sectorNumber = (int)(Math.Floor(sectorPos));
      // get the fractional part of the sector
      double fractionalSector = sectorPos - sectorNumber;

      // calculate values for the three axes of the color.
      double p = brightness * (1.0 - saturation);
      double q = brightness * (1.0 - (saturation * fractionalSector));
      double t = brightness * (1.0 - (saturation * (1 - fractionalSector)));

      // assign the fractional colors to r, g, and b based on the sector the angle is in.
      switch (sectorNumber)
      {
         case 0:
            r = brightness;
            g = t;
            b = p;
            break;
         case 1:
            r = q;
            g = brightness;
            b = p;
            break;
         case 2:
            r = p;
            g = brightness;
            b = t;
            break;
         case 3:
            r = p;
            g = q;
            b = brightness;
            break;
         case 4:
            r = t;
            g = p;
            b = brightness;
            break;
         case 5:
            r = brightness;
            g = p;
            b = q;
            break;
      }
}

      //Check if any value is out of byte range
      if (r < 0)
      {
         r = 0;
      }
      if (g < 0)
      {
         g = 0;
      }
      if (b < 0)
      {
         b = 0;
      }
      return new RGB() { R = (int)(r * 255.0), G = (int)(g * 255.0), B = (int)(b * 255.0) };
}

Now the classes can be use like this:

var rgb = new RGB() { R= 255, G = 255, B = 255}
var hsb = rgb.GetHSB();
var received rgb = hsb.GetRGB();
List RGBColors = new List();
            RGBColors.Add(new RGB() { R= 255, G = 255, B = 255});
            RGBColors.Add(new RGB() { R = 0, G = 255, B = 255 });
            RGBColors.Add(new RGB() { R = 255, G = 0, B = 255 });
            RGBColors.Add(new RGB() { R = 255, G = 255, B = 0 });
            RGBColors.Add(new RGB() { R = 0, G = 0, B = 255 });
            RGBColors.Add(new RGB() { R = 255, G = 0, B = 0 });
            RGBColors.Add(new RGB() { R = 0, G = 255, B = 0 });
            RGBColors.Add(new RGB() { R = 88, G = 211, B = 211 });
            RGBColors.Add(new RGB() { R = 27, G = 148, B = 9 });

   List HSBColors = new List();
   foreach (var rgbColor in RGBColors)
   {
      var hsbColor = rgbColor.GetHSB();
      HSBColors.Add(hsbColor);
      Console.WriteLine($"HSB: {hsbColor.Hue}  {hsbColor.Saturation} {hsbColor.Brightness}");
   }

   foreach (var hsbColor in HSBColors)
   {
      var rgbColor = hsbColor.GetRGB();
      RGBColors.Add(rgbColor);
      Console.WriteLine($"RGB: {rgbColor.R} {rgbColor.G} {rgbColor.B}" );
   }

Posted