Ejemplo n.º 1
0
        public static Hsv IncrementColorChannel(
            Hsv originalHsv,
            ColorPickerHsvChannel channel,
            IncrementDirection direction,
            IncrementAmount amount,
            bool shouldWrap,
            double minBound,
            double maxBound)
        {
            var newHsv = originalHsv;

            if (amount == IncrementAmount.Small)
            {
                // In order to avoid working with small values that can incur rounding issues,
                // we'll multiple saturation and value by 100 to put them in the range of 0-100 instead of 0-1.
                newHsv.S *= 100;
                newHsv.V *= 100;

                ref var valueToIncrement = ref newHsv.H;
                double  incrementAmount  = 0;

                // If we're adding a small increment, then we'll just add or subtract 1.
                // If we're adding a large increment, then we want to snap to the next
                // or previous major value - for hue, this is every increment of 30;
                // for saturation and value, this is every increment of 10.
                switch (channel)
                {
                case ColorPickerHsvChannel.Hue:
                    valueToIncrement = ref newHsv.H;
                    incrementAmount  = amount == IncrementAmount.Small ? 1 : 30;
                    break;

                case ColorPickerHsvChannel.Saturation:
                    valueToIncrement = ref newHsv.S;
                    incrementAmount  = amount == IncrementAmount.Small ? 1 : 10;
                    break;

                case ColorPickerHsvChannel.Value:
                    valueToIncrement = ref newHsv.V;
                    incrementAmount  = amount == IncrementAmount.Small ? 1 : 10;
                    break;

                default:
                    throw new InvalidOperationException("Invalid ColorPickerHsvChannel.");
                }

                var previousValue = valueToIncrement;

                valueToIncrement += direction == IncrementDirection.Lower ? -incrementAmount : incrementAmount;

                // If the value has reached outside the bounds, we were previous at the boundary, and we should wrap,
                // then we'll place the selection on the other side of the spectrum.
                // Otherwise, we'll place it on the boundary that was exceeded.
                if (valueToIncrement < minBound)
                {
                    valueToIncrement = (shouldWrap && previousValue == minBound) ? maxBound : minBound;
                }

                if (valueToIncrement > maxBound)
                {
                    valueToIncrement = (shouldWrap && previousValue == maxBound) ? minBound : maxBound;
                }

                // We multiplied saturation and value by 100 previously, so now we want to put them back in the 0-1 range.
                newHsv.S /= 100;
                newHsv.V /= 100;
            }
        public static Rgb HsvToRgb(Hsv hsv)
        {
            var hue        = hsv.H;
            var saturation = hsv.S;
            var value      = hsv.V;

            // We want the hue to be between 0 and 359,
            // so we first ensure that that's the case.
            while (hue >= 360.0)
            {
                hue -= 360.0;
            }

            while (hue < 0.0)
            {
                hue += 360.0;
            }

            // We similarly clamp saturation and value between 0 and 1.
            saturation = saturation < 0.0 ? 0.0 : saturation;
            saturation = saturation > 1.0 ? 1.0 : saturation;

            value = value < 0.0 ? 0.0 : value;
            value = value > 1.0 ? 1.0 : value;

            // The first thing that we need to do is to determine the chroma (see above for its definition).
            // Remember from above that:
            //
            // 1. The chroma is the difference between the maximum and the minimum of the RGB channels,
            // 2. The value is the maximum of the RGB channels, and
            // 3. The saturation comes from dividing the chroma by the maximum of the RGB channels (i.e., the value).
            //
            // From these facts, you can see that we can retrieve the chroma by simply multiplying the saturation and the value,
            // and we can retrieve the minimum of the RGB channels by subtracting the chroma from the value.
            var chroma = saturation * value;
            var min    = value - chroma;

            // If the chroma is zero, then we have a greyscale color.  In that case, the maximum and the minimum RGB channels
            // have the same value (and, indeed, all of the RGB channels are the same), so we can just immediately return
            // the minimum value as the value of all the channels.
            if (chroma == 0)
            {
                return(new Rgb(min, min, min));
            }

            // If the chroma is not zero, then we need to continue.  The first step is to figure out
            // what section of the color wheel we're located in.  In order to do that, we'll divide the hue by 60.
            // The resulting value means we're in one of the following locations:
            //
            // 0 - Between red and yellow.
            // 1 - Between yellow and green.
            // 2 - Between green and cyan.
            // 3 - Between cyan and blue.
            // 4 - Between blue and purple.
            // 5 - Between purple and red.
            //
            // In each of these sextants, one of the RGB channels is completely present, one is partially present, and one is not present.
            // For example, as we transition between red and yellow, red is completely present, green is becoming increasingly present, and blue is not present.
            // Then, as we transition from yellow and green, green is now completely present, red is becoming decreasingly present, and blue is still not present.
            // As we transition from green to cyan, green is still completely present, blue is becoming increasingly present, and red is no longer present.  And so on.
            //
            // To convert from hue to RGB value, we first need to figure out which of the three channels is in which configuration
            // in the sextant that we're located in.  Next, we figure out what value the completely-present color should have.
            // We know that chroma = (max - min), and we know that this color is the max color, so to find its value we simply add
            // min to chroma to retrieve max.  Finally, we consider how far we've transitioned from the pure form of that color
            // to the next color (e.g., how far we are from pure red towards yellow), and give a value to the partially present channel
            // equal to the minimum plus the chroma (i.e., the max minus the min), multiplied by the percentage towards the new color.
            // This gets us a value between the maximum and the minimum representing the partially present channel.
            // Finally, the not-present color must be equal to the minimum value, since it is the one least participating in the overall color.
            var sextant = (int)(hue / 60);
            var intermediateColorPercentage = (hue / 60) - sextant;
            var max = chroma + min;

            double r = 0;
            double g = 0;
            double b = 0;

            switch (sextant)
            {
            case 0:
                r = max;
                g = min + (chroma * intermediateColorPercentage);
                b = min;
                break;

            case 1:
                r = min + (chroma * (1 - intermediateColorPercentage));
                g = max;
                b = min;
                break;

            case 2:
                r = min;
                g = max;
                b = min + (chroma * intermediateColorPercentage);
                break;

            case 3:
                r = min;
                g = min + (chroma * (1 - intermediateColorPercentage));
                b = max;
                break;

            case 4:
                r = min + (chroma * intermediateColorPercentage);
                g = min;
                b = max;
                break;

            case 5:
                r = max;
                g = min;
                b = min + (chroma * (1 - intermediateColorPercentage));
                break;
            }

            return(new Rgb(r, g, b));
        }
        private static void AddGradientStop(LinearGradientBrush brush, double offset, Hsv hsvColor, double alpha)
        {
            var rgbColor = ColorConversion.HsvToRgb(hsvColor);

            var color = Color.FromArgb(
                (byte)Math.Round(alpha * 255),
                (byte)Math.Round(rgbColor.R * 255),
                (byte)Math.Round(rgbColor.G * 255),
                (byte)Math.Round(rgbColor.B * 255));
            var stop = new GradientStop(color, offset);

            brush.GradientStops.Add(stop);
        }