8

I might not be using the correct color terminology but I want to basically be able to scale colors similar to the picture attached. I have been searching for saturation to do this, as it appears the right version is just a must less saturated version of the left.

enter image description here

I was trying this (which I found) but it is not looking correct at all:

Public Shared Function GetDesaturatedColor(color As Color) As Color
    Const kValue As Double = 0.01R

    Dim greyLevel = (color.R * 0.299R) + _
                    (color.G * 0.587R) + _
                    (color.B * 0.144R)

    Dim r = greyLevel * kValue + (color.R) * (1 - kValue)
    Dim g = greyLevel * kValue + (color.G) * (1 - kValue)
    Dim b = greyLevel * kValue + (color.B) * (1 - kValue)

    ' ColorUtils.ToByte converts the double value 
    ' to a byte safely
    Return color.FromArgb(255, _
                          ColorUtils.ToByte(r), _
                          ColorUtils.ToByte(g), _
                          ColorUtils.ToByte(b))
End Function

Does anyone know of some algorithm that can do this?

test
  • 2,432
  • 2
  • 22
  • 48

4 Answers4

16

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.

StayOnTarget
  • 7,829
  • 8
  • 42
  • 59
Alec Jacobson
  • 5,326
  • 4
  • 41
  • 75
  • Whats the explanation for this algorithm? Is there a name for it? Trying to learn image processing atm. – kosinix Jun 08 '16 at 06:30
  • This approach works for a *linear* color space, which is why e.g. https://www.w3.org/WAI/GL/wiki/Relative_luminance has the "to the power of 2.4". You need to first convert the color values to linear, then you can take the luminosity like this (although the relative luminance has better factors I think). Afterwards you can invert the power to convert back (to the power of 1/2.4). – fforw Apr 23 '21 at 14:36
3

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.

Community
  • 1
  • 1
Fabian Tamp
  • 4,026
  • 1
  • 22
  • 41
2

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:

enter image description here

If you also reduce alpha/opacity of the new color, you can achieve a better result:

enter image description here

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
Neolisk
  • 23,880
  • 16
  • 72
  • 135
  • 1
    I noticed this as well. The way I tried to solve it was decrease the saturation and up the brightness (value). I was able to get something decently similar to my picture. I will try out yours though, thanks. – test Nov 11 '12 at 04:07
  • @reedparkes: I tried playing with brigthness - the problem is at some it gets high enough (=255), so you cannot up it anymore. This is where alpha can help. Actually, you could probably use all three to achieve desired result. – Neolisk Nov 11 '12 at 13:14
1

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));
}
Andi
  • 2,347
  • 3
  • 23
  • 31