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}" );
}