public override LightCommand GetLightCommand(IDictionary<string, string> values, PhilipsHueDevice bulb) { string hexColor; if (!values.TryGetValue("Color", out hexColor)) { throw new ArgumentException("Color"); } // TODO: http://www.developers.meethue.com/documentation/color-conversions-rgb-xy var rgb = new RGBColor(hexColor); var xy = RgbToCieConverter.Convert(bulb.Model, rgb.R, rgb.G, rgb.B); if (Equals(xy, default(RgbToCieConverter.CieResult))) { return new LightCommand(); } var command = new LightCommand { On = true, ColorCoordinates = new[] {xy.X, xy.Y} }; TimeSpan transitionTime; if (TryGetTransitionTime(values, out transitionTime)) { command.TransitionTime = transitionTime; } return command; }
public static CIE1931Point RgbToXY(RGBColor color, string model) { // Apply gamma correction. Convert non-linear RGB colour components // to linear color intensity levels. double r = InverseGamma(color.R); double g = InverseGamma(color.G); double b = InverseGamma(color.B); // Hue bulbs (depending on the type) can display colors outside the sRGB gamut supported // by most computer screens. // To make sure all colors are selectable by the user, Philips in its implementation // decided to interpret all RGB colors as if they were from a wide (non-sRGB) gamut. // The effect of this is to map colors in sRGB to a broader gamut of colors the hue lights // can produce. // // This also results in some deviation of color on screen vs color in real-life. // // The Philips implementation describes the matrix below with the comment // "Wide Gamut D65", but the values suggest this is infact not a standard // gamut but some custom gamut. // // The coordinates of this gamut have been identified as follows: // red: (0.700607, 0.299301) // green: (0.172416, 0.746797) // blue: (0.135503, 0.039879) // // (substitute r = 1, g = 1, b = 1 in sequence into array below and convert // from XYZ to xyY coordinates). // The plotted chart can be seen here: http://imgur.com/zelKnSk // // Also of interest, the white point is not D65 (0.31271, 0.32902), but a slightly // shifted version at (0.322727, 0.32902). This might be because true D65 is slightly // outside Gamut B (the position of D65 in the linked chart is slightly inaccurate). double X = r * 0.664511f + g * 0.154324f + b * 0.162028f; double Y = r * 0.283881f + g * 0.668433f + b * 0.047685f; double Z = r * 0.000088f + g * 0.072310f + b * 0.986039f; CIE1931Point xyPoint = new CIE1931Point(0.0, 0.0); if ((X + Y + Z) > 0.0) { // Convert from CIE XYZ to CIE xyY coordinates. xyPoint = new CIE1931Point(X / (X + Y + Z), Y / (X + Y + Z)); } if (model != null) { //Check if the given XY value is within the colourreach of our lamps. CIE1931Gamut gamut = CIE1931Gamut.ForModel(model); // The point, adjusted it to the nearest point that is within the gamut of the lamp, if neccessary. return gamut.NearestContainedPoint(xyPoint); } return xyPoint; }
/***************************************************************************************** The code below is based on http://www.developers.meethue.com/documentation/color-conversions-rgb-xy Converted to C# by Niels Laute *****************************************************************************************/ public static CGPoint CalculateXY(RGBColor color, string model) { //CGColorRef cgColor = [color CGColor]; //const CGFloat* components = CGColorGetComponents(cgColor); //long numberOfComponents = CGColorGetNumberOfComponents(cgColor); // Default to white float red = 1.0f; float green = 1.0f; float blue = 1.0f; //if (numberOfComponents == 4) //{ // Full color red = color.R; green = color.G; blue = color.B; //} //else if (numberOfComponents == 2) //{ // // Greyscale color // red = green = blue = color.A; //} // Apply gamma correction float r; float g; float b; if (red > 0.04045f) { r = (float)Math.Pow((red + 0.055f) / (1.0f + 0.055f), 2.4f); } else { r = red / 12.92f; } if (green > 0.04045f) { g = (float)Math.Pow((green + 0.055f) / (1.0f + 0.055f), 2.4f); } else { g = green / 12.92f; } if (blue > 0.04045f) { b = (float)Math.Pow((blue + 0.055f) / (1.0f + 0.055f), 2.4f); } else { b = blue / 12.92f; } // Wide gamut conversion D65 float X = r * 0.664511f + g * 0.154324f + b * 0.162028f; float Y = r * 0.283881f + g * 0.668433f + b * 0.047685f; float Z = r * 0.000088f + g * 0.072310f + b * 0.986039f; float cx = 0.0f; if((X + Y + Z) != 0) cx = X / (X + Y + Z); float cy = 0.0f; if((X + Y + Z) != 0) cy = Y / (X + Y + Z); //Check if the given XY value is within the colourreach of our lamps. CGPoint xyPoint = new CGPoint(cx, cy); List<CGPoint> colorPoints = ColorPointsForModel(model); bool inReachOfLamps = CheckPointInLampsReach(xyPoint, colorPoints); if (!inReachOfLamps) { //It seems the colour is out of reach //let's find the closest colour we can produce with our lamp and send this XY value out. //Find the closest point on each line in the triangle. CGPoint pAB = GetClosestPointToPoints(colorPoints[0], colorPoints[1], xyPoint); CGPoint pAC = GetClosestPointToPoints(colorPoints[2], colorPoints[0], xyPoint); CGPoint pBC = GetClosestPointToPoints(colorPoints[1], colorPoints[2], xyPoint); //Get the distances per point and see which point is closer to our Point. float dAB = GetDistanceBetweenTwoPoints(xyPoint, pAB); float dAC = GetDistanceBetweenTwoPoints(xyPoint, pAC); float dBC = GetDistanceBetweenTwoPoints(xyPoint, pBC); float lowest = dAB; CGPoint closestPoint = pAB; if (dAC < lowest) { lowest = dAC; closestPoint = pAC; } if (dBC < lowest) { lowest = dBC; closestPoint = pBC; } //Change the xy value to a value which is within the reach of the lamp. cx = (float)closestPoint.x; cy = (float)closestPoint.y; } return new CGPoint(cx, cy); }
public static LightCommand SetColor(this LightCommand lightCommand, RGBColor color, string model) { if (lightCommand == null) throw new ArgumentNullException("lightCommand"); var point = HueColorConverter.CalculateXY(color, model); return lightCommand.SetColor(point.x, point.y); }
/***************************************************************************************** * The code below is based on http://www.developers.meethue.com/documentation/color-conversions-rgb-xy * Converted to C# by Niels Laute *****************************************************************************************/ public static CGPoint CalculateXY(RGBColor color, string model) { //CGColorRef cgColor = [color CGColor]; //const CGFloat* components = CGColorGetComponents(cgColor); //long numberOfComponents = CGColorGetNumberOfComponents(cgColor); // Default to white float red = 1.0f; float green = 1.0f; float blue = 1.0f; //if (numberOfComponents == 4) //{ // Full color red = color.R; green = color.G; blue = color.B; //} //else if (numberOfComponents == 2) //{ // // Greyscale color // red = green = blue = color.A; //} // Apply gamma correction float r; float g; float b; if (red > 0.04045f) { r = (float)Math.Pow((red + 0.055f) / (1.0f + 0.055f), 2.4f); } else { r = red / 12.92f; } if (green > 0.04045f) { g = (float)Math.Pow((green + 0.055f) / (1.0f + 0.055f), 2.4f); } else { g = green / 12.92f; } if (blue > 0.04045f) { b = (float)Math.Pow((blue + 0.055f) / (1.0f + 0.055f), 2.4f); } else { b = blue / 12.92f; } // Wide gamut conversion D65 float X = r * 0.664511f + g * 0.154324f + b * 0.162028f; float Y = r * 0.283881f + g * 0.668433f + b * 0.047685f; float Z = r * 0.000088f + g * 0.072310f + b * 0.986039f; float cx = 0.0f; if ((X + Y + Z) != 0) { cx = X / (X + Y + Z); } float cy = 0.0f; if ((X + Y + Z) != 0) { cy = Y / (X + Y + Z); } //Check if the given XY value is within the colourreach of our lamps. CGPoint xyPoint = new CGPoint(cx, cy); List <CGPoint> colorPoints = ColorPointsForModel(model); bool inReachOfLamps = CheckPointInLampsReach(xyPoint, colorPoints); if (!inReachOfLamps) { //It seems the colour is out of reach //let's find the closest colour we can produce with our lamp and send this XY value out. //Find the closest point on each line in the triangle. CGPoint pAB = GetClosestPointToPoints(colorPoints[0], colorPoints[1], xyPoint); CGPoint pAC = GetClosestPointToPoints(colorPoints[2], colorPoints[0], xyPoint); CGPoint pBC = GetClosestPointToPoints(colorPoints[1], colorPoints[2], xyPoint); //Get the distances per point and see which point is closer to our Point. float dAB = GetDistanceBetweenTwoPoints(xyPoint, pAB); float dAC = GetDistanceBetweenTwoPoints(xyPoint, pAC); float dBC = GetDistanceBetweenTwoPoints(xyPoint, pBC); float lowest = dAB; CGPoint closestPoint = pAB; if (dAC < lowest) { lowest = dAC; closestPoint = pAC; } if (dBC < lowest) { lowest = dBC; closestPoint = pBC; } //Change the xy value to a value which is within the reach of the lamp. cx = (float)closestPoint.x; cy = (float)closestPoint.y; } return(new CGPoint(cx, cy)); }