private static double GetDistanceBetweenTwoPoints(CIE1931Point one, CIE1931Point two) { double dx = one.x - two.x; // horizontal difference double dy = one.y - two.y; // vertical difference return(Math.Sqrt(dx * dx + dy * dy)); }
public CIE1931Point NearestContainedPoint(CIE1931Point point) { if (Contains(point)) { // If this gamut already contains the point, then no adjustment is required. return(point); } // Find the closest point on each line in the triangle. CIE1931Point pAB = GetClosestPointOnLine(Red, Green, point); CIE1931Point pAC = GetClosestPointOnLine(Red, Blue, point); CIE1931Point pBC = GetClosestPointOnLine(Green, Blue, point); //Get the distances per point and see which point is closer to our Point. double dAB = GetDistanceBetweenTwoPoints(point, pAB); double dAC = GetDistanceBetweenTwoPoints(point, pAC); double dBC = GetDistanceBetweenTwoPoints(point, pBC); double lowest = dAB; CIE1931Point closestPoint = pAB; if (dAC < lowest) { lowest = dAC; closestPoint = pAC; } if (dBC < lowest) { lowest = dBC; closestPoint = pBC; } return(closestPoint); }
private static bool IsAbove(CIE1931Point blue, CIE1931Point red, CIE1931Point point) { double slope = (blue.y - red.y) / (blue.x - red.x); double intercept = blue.y - slope * blue.x; double minY = point.x * slope + intercept; return(point.y >= minY); }
private static bool IsBelow(CIE1931Point a, CIE1931Point b, CIE1931Point point) { double slope = (a.y - b.y) / (a.x - b.x); double intercept = a.y - slope * a.x; double maxY = point.x * slope + intercept; return(point.y <= maxY); }
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); }
public static RGBColor XYToRgb(CIE1931Point point, string model) { if (model != null) { CIE1931Gamut gamut = CIE1931Gamut.ForModel(model); // If the color is outside the lamp's gamut, adjust to the nearest color // inside the lamp's gamut. point = gamut.NearestContainedPoint(point); } // Also adjust it to be in the Philips "Wide Gamut" if not already. // The wide gamut used for XYZ->RGB conversion does not quite contain all colors // all of the hue bulbs support. // ReSharper disable once ImpureMethodCallOnReadonlyValueField point = CIE1931Gamut.PhilipsWideGamut.NearestContainedPoint(point); // Convert from xyY to XYZ coordinates. double Y = 1.0; // Luminance double X = (Y / point.y) * point.x; double Z = (Y / point.y) * point.z; // The Philips implementation comments this matrix with "sRGB D65 conversion" // However, this is not the XYZ -> RGB conversion matrix for sRGB. Instead // the matrix that is the inverse of that in RgbToXY() is used. // See comment in RgbToXY() for more info. double r = X * 1.656492 - Y * 0.354851 - Z * 0.255038; double g = -X * 0.707196 + Y * 1.655397 + Z * 0.036152; double b = X * 0.051713 - Y * 0.121364 + Z * 1.011530; // Downscale color components so that largest component has an intensity of 1.0, // as we can't display colors brighter than that. double maxComponent = Math.Max(Math.Max(r, g), b); if (maxComponent > 1.0) { r /= maxComponent; g /= maxComponent; b /= maxComponent; } // We now have the (linear) amounts of R, G and B corresponding to the specified XY coordinates. // Since displays are non-linear, we must apply a gamma correction to get the pixel value. // For example, a pixel red value of 1.0 (255) is more than twice as bright as 0.5 (127). // We need to correct for this non-linearity. r = Gamma(r); g = Gamma(g); b = Gamma(b); // Philips applies a second round of downscaling here, but that should be unnecessary given // gamma returns a value between 0.0 and 1.0 for every input between 0.0 and 1.0. return(new RGBColor(r, g, b)); }
public bool Contains(CIE1931Point point) { // Arrangement of points in color space: // // ^ G // y| // | R // | B // .-------------------> // x // return(IsBelow(Blue, Green, point) && IsBelow(Green, Red, point) && IsAbove(Red, Blue, point)); }
private static CIE1931Point GetClosestPointOnLine(CIE1931Point a, CIE1931Point b, CIE1931Point p) { CIE1931Point AP = new CIE1931Point(p.x - a.x, p.y - a.y); CIE1931Point AB = new CIE1931Point(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; // Bound to ends of line between A and B. if (t < 0.0f) { t = 0.0f; } else if (t > 1.0f) { t = 1.0f; } return(new CIE1931Point(a.x + AB.x * t, a.y + AB.y * t)); }
public CIE1931Gamut(CIE1931Point red, CIE1931Point green, CIE1931Point blue) { Red = red; Green = green; Blue = blue; }