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