How to Desaturate a Color?
As @Brad mentioned in the comments to your post, your first step is to convert the colours from RGB to either HSL or HSV. From there, reducing the saturation is trivial - just subtract or divide the saturation by a value to reduce it.
After that, convert your HSL/HSV color back into RGB and it's ready for use.
How to change RGB color to HSV? has a good example of how to do this, as does Manipulating colors in .net.
It appears by experiment that just reducing saturation is not enough to get the result shown in the picture. I used the colors from OP's question in the code shown below. If you just reduce saturation, here is what you get:
If you also reduce alpha/opacity of the new color, you can achieve a better result:
I am assuming if you play with parameters, you should be able to get a perfect match. Try changing alpha
for reducedSaturation2
(currently =40) and GetSaturation
divider (currently =1.3)
Here is my code sample:
Public Function HSVToColor(ByVal H As Double, ByVal S As Double, ByVal V As Double) As Color
Dim Hi As Integer = (H / 60) Mod 6
Dim f As Double = H / 60 Mod 1
Dim p As Integer = V * (1 - S) * 255
Dim q As Integer = V * (1 - f * S) * 255
Dim t As Integer = V * (1 - (1 - f) * S) * 255
Select Case Hi
Case 0 : Return Color.FromArgb(V * 255, t, p)
Case 1 : Return Color.FromArgb(q, V * 255, p)
Case 2 : Return Color.FromArgb(p, V * 255, t)
Case 3 : Return Color.FromArgb(p, V * 255, q)
Case 4 : Return Color.FromArgb(t, p, V * 255)
Case 5 : Return Color.FromArgb(V * 255, q, p)
End Select
End Function
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim normalSaturation As Color = Color.FromArgb(255, 216, 53, 45)
Me.CreateGraphics.FillRectangle(New SolidBrush(normalSaturation), 100, 0, 100, 100)
Dim reducedSaturation As Color = HSVToColor(normalSaturation.GetHue, normalSaturation.GetSaturation / 1.3, normalSaturation.GetBrightness)
Dim reducedSaturation2 As Color = Color.FromArgb(40, reducedSaturation)
Me.CreateGraphics.FillRectangle(New SolidBrush(reducedSaturation2), 0, 0, 100, 100)
End Sub
For those that want to avoid converting everything to HSL/HSV and back, this works reasonably well (if not correctly depending on what one thinks the "correct" desaturated image is):
f = 0.2; // desaturate by 20%
L = 0.3*r + 0.6*g + 0.1*b;
new_r = r + f * (L - r);
new_g = g + f * (L - g);
new_b = b + f * (L - b);
This is converting r,g,b to grayscale using the common assumption that green, red and blue correspond to the Luma of an image decreasing proportions respectively. So L is a grayscale image and then f is just linearly interpolating between the input RGB image and that grayscale image.
Here is my formula for changing the saturation of a given Color
in C#. The result is equal to applying the CSS-effect filter: saturate(...)
, because this is the formula used by the W3C spec.
private static Color Saturate(Color c, double saturation) {
// Values from: https://www.w3.org/TR/filter-effects-1/#feColorMatrixElement , type="saturate"
var s = saturation;
Func<double, int> clamp = i => Math.Min(255, Math.Max(0, Convert.ToInt32(i)));
return Color.FromArgb(255,
clamp((0.213 + 0.787 * s) * c.R + (0.715 - 0.715 * s) * c.G + (0.072 - 0.072 * s) * c.B),
clamp((0.213 - 0.213 * s) * c.R + (0.715 + 0.285 * s) * c.G + (0.072 - 0.072 * s) * c.B),
clamp((0.213 - 0.213 * s) * c.R + (0.715 - 0.715 * s) * c.G + (0.072 + 0.928 * s) * c.B));
}