/// <summary> /// Find the distance between two points. /// </summary> /// <param name="one"></param> /// <param name="two"></param> /// <returns>the distance between point one and two</returns> private static double GetDistanceBetweenTwoPoints(CGPoint one, CGPoint two) { double dx = one.x - two.x; // horizontal difference double dy = one.y - two.y; // vertical difference double dist = Math.Sqrt(dx * dx + dy * dy); return dist; }
/// <summary> /// Get XY from red,green,blue ints /// </summary> /// <param name="red"></param> /// <param name="green"></param> /// <param name="blue"></param> /// <returns></returns> public static CGPoint XyFromColor(int red, int green, int blue) { double r = (red > 0.04045f) ? Math.Pow((red + 0.055f) / (1.0f + 0.055f), 2.4f) : (red / 12.92f); double g = (green > 0.04045f) ? Math.Pow((green + 0.055f) / (1.0f + 0.055f), 2.4f) : (green / 12.92f); double b = (blue > 0.04045f) ? Math.Pow((blue + 0.055f) / (1.0f + 0.055f), 2.4f) : (blue / 12.92f); double X = r * 0.4360747f + g * 0.3850649f + b * 0.0930804f; double Y = r * 0.2225045f + g * 0.7168786f + b * 0.0406169f; double Z = r * 0.0139322f + g * 0.0971045f + b * 0.7141733f; double cx = X / (X + Y + Z); double cy = Y / (X + Y + Z); if (Double.IsNaN(cx)) { cx = 0.0f; } if (Double.IsNaN(cy)) { cy = 0.0f; } //Check if the given XY value is within the colourreach of our lamps. CGPoint xyPoint = new CGPoint(cx, cy); bool inReachOfLamps = HueColorConverter.CheckPointInLampsReach(xyPoint); if (!inReachOfLamps) { //It seems the colour is out of reach //let's find the closes 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 = HueColorConverter.GetClosestPointToPoint(Red, Lime, xyPoint); CGPoint pAC = HueColorConverter.GetClosestPointToPoint(Blue, Red, xyPoint); CGPoint pBC = HueColorConverter.GetClosestPointToPoint(Lime, Blue, xyPoint); //Get the distances per point and see which point is closer to our Point. double dAB = HueColorConverter.GetDistanceBetweenTwoPoints(xyPoint, pAB); double dAC = HueColorConverter.GetDistanceBetweenTwoPoints(xyPoint, pAC); double dBC = HueColorConverter.GetDistanceBetweenTwoPoints(xyPoint, pBC); double 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 = closestPoint.x; cy = closestPoint.y; } return new CGPoint(cx, cy); }
/// <summary> /// Calculates crossProduct of two 2D vectors / points. /// </summary> /// <param name="p1"> p1 first point used as vector</param> /// <param name="p2">p2 second point used as vector</param> /// <returns>crossProduct of vectors</returns> private static double CrossProduct(CGPoint p1, CGPoint p2) { return (p1.x * p2.y - p1.y * p2.x); }
/// <summary> /// Find the closest point on a line. /// This point will be within reach of the lamp. /// </summary> /// <param name="A">A the point where the line starts</param> /// <param name="B">B the point where the line ends</param> /// <param name="P">P the point which is close to a line.</param> /// <returns> the point which is on the line.</returns> private static CGPoint GetClosestPointToPoint(CGPoint A, CGPoint B, CGPoint P) { CGPoint AP = new CGPoint(P.x - A.x, P.y - A.y); CGPoint AB = new CGPoint(B.x - A.x, B.y - A.y); double ab2 = AB.x * AB.x + AB.y * AB.y; double ap_ab = AP.x * AB.x + AP.y * AB.y; double t = ap_ab / ab2; if (t < 0.0f) t = 0.0f; else if (t > 1.0f) t = 1.0f; CGPoint newPoint = new CGPoint(A.x + AB.x * t, A.y + AB.y * t); return newPoint; }
private static RGBColor ColorFromXY(CGPoint xy, string model) { List<CGPoint> colorPoints = ColorPointsForModel(model); bool inReachOfLamps = CheckPointInLampsReach(xy, 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], xy); CGPoint pAC = GetClosestPointToPoints(colorPoints[2], colorPoints[0], xy); CGPoint pBC = GetClosestPointToPoints(colorPoints[1], colorPoints[2], xy); //Get the distances per point and see which point is closer to our Point. float dAB = GetDistanceBetweenTwoPoints(xy, pAB); float dAC = GetDistanceBetweenTwoPoints(xy, pAC); float dBC = GetDistanceBetweenTwoPoints(xy, 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. xy.x = closestPoint.x; xy.y = closestPoint.y; } float x = (float)xy.x; float y = (float)xy.y; float z = 1.0f - x - y; float Y = 1.0f; float X = (Y / y) * x; float Z = (Y / y) * z; // sRGB D65 conversion float r = X * 1.656492f - Y * 0.354851f - Z * 0.255038f; float g = -X * 0.707196f + Y * 1.655397f + Z * 0.036152f; float b = X * 0.051713f - Y * 0.121364f + Z * 1.011530f; if (r > b && r > g && r > 1.0f) { // red is too big g = g / r; b = b / r; r = 1.0f; } else if (g > b && g > r && g > 1.0f) { // green is too big r = r / g; b = b / g; g = 1.0f; } else if (b > r && b > g && b > 1.0f) { // blue is too big r = r / b; g = g / b; b = 1.0f; } // Apply gamma correction if (r <= 0.0031308f) { r = 12.92f * r; } else { r = (1.0f + 0.055f) * (float)Math.Pow(r, (1.0f / 2.4f)) - 0.055f; } if (g <= 0.0031308f) { g = 12.92f * g; } else { g = (1.0f + 0.055f) * (float)Math.Pow(g, (1.0f / 2.4f)) - 0.055f; } if (b <= 0.0031308f) { b = 12.92f * b; } else { b = (1.0f + 0.055f) * (float)Math.Pow(b, (1.0f / 2.4f)) - 0.055f; } if (r > b && r > g) { // red is biggest if (r > 1.0f) { g = g / r; b = b / r; r = 1.0f; } } else if (g > b && g > r) { // green is biggest if (g > 1.0f) { r = r / g; b = b / g; g = 1.0f; } } else if (b > r && b > g) { // blue is biggest if (b > 1.0f) { r = r / b; g = g / b; b = 1.0f; } } return new RGBColor() { R = (byte)r, G = (byte)g, B = (byte)b, A = (byte)1.0f }; }
/// <summary> /// Method to see if the given XY value is within the reach of the lamps. /// </summary> /// <param name="p">p the point containing the X,Y value</param> /// <returns>true if within reach, false otherwise.</returns> private static bool CheckPointInLampsReach(CGPoint p) { CGPoint v1 = new CGPoint(Lime.x - Red.x, Lime.y - Red.y); CGPoint v2 = new CGPoint(Blue.x - Red.x, Blue.y - Red.y); CGPoint q = new CGPoint(p.x - Red.x, p.y - Red.y); double s = HueColorConverter.CrossProduct(q, v2) / HueColorConverter.CrossProduct(v1, v2); double t = HueColorConverter.CrossProduct(v1, q) / HueColorConverter.CrossProduct(v1, v2); if ((s >= 0.0f) && (t >= 0.0f) && (s + t <= 1.0f)) { return true; } else { return false; } }
private static float CrossProduct(CGPoint p1, CGPoint p2) { return (float)(p1.x * p2.y - p1.y * p2.x); }
private static bool CheckPointInLampsReach(CGPoint p, List<CGPoint> colorPoints) { CGPoint red = colorPoints[0]; CGPoint green = colorPoints[1]; CGPoint blue = colorPoints[2]; CGPoint v1 = new CGPoint(green.x - red.x, green.y - red.y); CGPoint v2 = new CGPoint(blue.x - red.x, blue.y - red.y); CGPoint q = new CGPoint(p.x - red.x, p.y - red.y); float s = CrossProduct(q, v2) / CrossProduct(v1, v2); float t = CrossProduct(v1, q) / CrossProduct(v1, v2); if ((s >= 0.0f) && (t >= 0.0f) && (s + t <= 1.0f)) { return true; } else { return false; } }
private static float GetDistanceBetweenTwoPoints(CGPoint one, CGPoint two) { float dx = (float)(one.x - two.x); // horizontal difference float dy = (float)(one.y - two.y); // vertical difference return (float)Math.Sqrt(dx * dx + dy * dy); }
private static CGPoint GetClosestPointToPoints(CGPoint A, CGPoint B, CGPoint P) { CGPoint AP = new CGPoint(P.x - A.x, P.y - A.y); CGPoint AB = new CGPoint(B.x - A.x, B.y - A.y); float ab2 = (float)(AB.x * AB.x + AB.y * AB.y); float ap_ab = (float)(AP.x * AB.x + AP.y * AB.y); float t = ap_ab / ab2; if (t < 0.0f) { t = 0.0f; } else if (t > 1.0f) { t = 1.0f; } return new CGPoint(A.x + AB.x * t, A.y + AB.y * t); }
/***************************************************************************************** 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); }