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