private Rgb ApplyConstraintsToRgbColor(Rgb rgb) { double minHue = MinHue; double maxHue = MaxHue; var minSaturation = MinSaturation / 100.0; var maxSaturation = MaxSaturation / 100.0; var minValue = MinValue / 100.0; var maxValue = MaxValue / 100.0; var hsv = ColorConversion.RgbToHsv(rgb); hsv.H = Math.Min(Math.Max(hsv.H, minHue), maxHue); hsv.S = Math.Min(Math.Max(hsv.S, minSaturation), maxSaturation); hsv.V = Math.Min(Math.Max(hsv.V, minValue), maxValue); return(ColorConversion.HsvToRgb(hsv)); }
private void OnHexTextChanging(object sender, TextInputEventArgs args) { // If we're in the process of updating controls in response to a color change, // then we don't want to do anything in response to a control being updated, // since otherwise we'll get into an infinite loop of updating. if (m_updatingControls) { return; } var hexTextBox = (TextBox)sender; var hexTextBoxText = hexTextBox.Text; // If the user hasn't entered a #, we'll do that for them, keeping the cursor // where it was before. if (hexTextBoxText.Length == 0 || hexTextBoxText[0] != '#') { hexTextBox.Text = hexTextBoxText = "#" + hexTextBoxText; // TODO should it use saved before length? hexTextBox.CaretIndex = hexTextBoxText.Length; } // We'll respond to the text change if the user has entered a valid value. // Otherwise, we'll do nothing except mark the text box's contents as invalid. var isAlphaEnabled = IsAlphaEnabled; if (Color.TryParse(hexTextBoxText, out var parsedColor)) { var rgbValue = new Rgb(parsedColor.R / 255.0, parsedColor.G / 255.0, parsedColor.B / 255.0); var alphaValue = parsedColor.A / 255.0; if (!isAlphaEnabled) { alphaValue = 1.0; } m_isFocusedTextBoxValid = true; UpdateColor(ApplyConstraintsToRgbColor(rgbValue), ColorUpdateReason.HexTextBoxChanged); UpdateColor(alphaValue, ColorUpdateReason.HexTextBoxChanged); } else { m_isFocusedTextBoxValid = false; } }
public static Hsv RgbToHsv(Rgb rgb) { double hue; double saturation; double value; var max = rgb.R >= rgb.G ? (rgb.R >= rgb.B ? rgb.R : rgb.B) : (rgb.G >= rgb.B ? rgb.G : rgb.B); var min = rgb.R <= rgb.G ? (rgb.R <= rgb.B ? rgb.R : rgb.B) : (rgb.G <= rgb.B ? rgb.G : rgb.B); // The value, a number between 0 and 1, is the largest of R, G, and B (divided by 255). // Conceptually speaking, it represents how much color is present. // If at least one of R, G, B is 255, then there exists as much color as there can be. // If RGB = (0, 0, 0), then there exists no color at all - a value of zero corresponds // to black (i.e., the absence of any color). value = max; // The "chroma" of the color is a value directly proportional to the extent to which // the color diverges from greyscale. If, for example, we have RGB = (255, 255, 0), // then the chroma is maximized - this is a pure yellow, no grey of any kind. // On the other hand, if we have RGB = (128, 128, 128), then the chroma being zero // implies that this color is pure greyscale, with no actual hue to be found. var chroma = max - min; // If the chrome is zero, then hue is technically undefined - a greyscale color // has no hue. For the sake of convenience, we'll just set hue to zero, since // it will be unused in this circumstance. Since the color is purely grey, // saturation is also equal to zero - you can think of saturation as basically // a measure of hue intensity, such that no hue at all corresponds to a // nonexistent intensity. if (chroma == 0) { hue = 0.0; saturation = 0.0; } else { // In this block, hue is properly defined, so we'll extract both hue // and saturation information from the RGB color. // Hue can be thought of as a cyclical thing, between 0 degrees and 360 degrees. // A hue of 0 degrees is red; 120 degrees is green; 240 degrees is blue; and 360 is back to red. // Every other hue is somewhere between either red and green, green and blue, and blue and red, // so every other hue can be thought of as an angle on this color wheel. // These if/else statements determines where on this color wheel our color lies. if (rgb.R == max) { // If the red channel is the most pronounced channel, then we exist // somewhere between (-60, 60) on the color wheel - i.e., the section around 0 degrees // where red dominates. We figure out where in that section we are exactly // by considering whether the green or the blue channel is greater - by subtracting green from blue, // then if green is greater, we'll nudge ourselves closer to 60, whereas if blue is greater, then // we'll nudge ourselves closer to -60. We then divide by chroma (which will actually make the result larger, // since chroma is a value between 0 and 1) to normalize the value to ensure that we get the right hue // even if we're very close to greyscale. hue = 60 * (rgb.G - rgb.B) / chroma; } else if (rgb.G == max) { // We do the exact same for the case where the green channel is the most pronounced channel, // only this time we want to see if we should tilt towards the blue direction or the red direction. // We add 120 to center our value in the green third of the color wheel. hue = 120 + (60 * (rgb.B - rgb.R) / chroma); } else // rgb.B == max { // And we also do the exact same for the case where the blue channel is the most pronounced channel, // only this time we want to see if we should tilt towards the red direction or the green direction. // We add 240 to center our value in the blue third of the color wheel. hue = 240 + (60 * (rgb.R - rgb.G) / chroma); } // Since we want to work within the range [0, 360), we'll add 360 to any value less than zero - // this will bump red values from within -60 to -1 to 300 to 359. The hue is the same at both values. if (hue < 0.0) { hue += 360.0; } // The saturation, our final HSV axis, can be thought of as a value between 0 and 1 indicating how intense our color is. // To find it, we divide the chroma - the distance between the minimum and the maximum RGB channels - by the maximum channel (i.e., the value). // This effectively normalizes the chroma - if the maximum is 0.5 and the minimum is 0, the saturation will be (0.5 - 0) / 0.5 = 1, // meaning that although this color is not as bright as it can be, the dark color is as intense as it possibly could be. // If, on the other hand, the maximum is 0.5 and the minimum is 0.25, then the saturation will be (0.5 - 0.25) / 0.5 = 0.5, // meaning that this color is partially washed out. // A saturation value of 0 corresponds to a greyscale color, one in which the color is *completely* washed out and there is no actual hue. saturation = chroma / value; } return(new Hsv(hue, saturation, value)); }