Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to compare Color object and get closest Color in an Color[]?

Let's say I have an array with colors (with the whole color spectrum, from red to red.). A shorter version would look like this:

public Color[] ColorArray = new Color[360] { Color.FromArgb(255, 245, 244, 242), Color.FromArgb(255, 245, 244, 240), Color.FromArgb(255, 245, 244, 238) }

Now if I have a seperate

Color object (Color c = Color.FromArgb(255, 14, 4, 5))

How can I get the value in the Array that is the closest to the selected color? And is this even possible?

like image 821
Niels Avatar asked Dec 09 '14 08:12

Niels


2 Answers

Color distance is not a precisely defined thing. So here are three methods to measure it:

  • One method that checks only the hues of the colors, ignoring both saturation and brightness
  • One that only measures the direct distance in RGB space
  • And one that weighs hue, saturation and brightness in some way.

Obviously you may want to change the magic numbers in the 3rd measurement: hue is in 0-360, brightness and saturation are in 0-1, so with these numbers hue weighs about 3.6 times stronger than saturation and brightness..

Update: The original solution I posted contained several errors:

  • The Linq I used didn't find the closest but the closestFromBelow; this meant a 50% chance of being off by one.
  • In some places I used the color.GetBrightness() method. This is, to put it mildly, totally useless. To wit: Blue and Yellow have the same value of 0.5!
  • The values for hue go from 0-360, but of course they wrap around! I missed that completely..

I have replaced most of the original answer with corrected code:

These now are the new versions of the methods, each returning the index of the closest match found:

// closed match for hues only:
int closestColor1(List<Color> colors, Color target)
{
    var hue1 = target.GetHue();
    var diffs = colors.Select(n => getHueDistance(n.GetHue(), hue1));
    var diffMin = diffs.Min(n => n);
    return diffs.ToList().FindIndex(n => n == diffMin);
}

// closed match in RGB space
int closestColor2(List<Color> colors, Color target)
{
    var colorDiffs = colors.Select(n => ColorDiff(n, target)).Min(n =>n);
    return colors.FindIndex(n => ColorDiff(n, target) == colorDiffs);
}

// weighed distance using hue, saturation and brightness
int closestColor3(List<Color> colors, Color target)
{
    float hue1 = target.GetHue();
    var num1 = ColorNum(target);
    var diffs = colors.Select(n => Math.Abs(ColorNum(n) - num1) + 
                                   getHueDistance(n.GetHue(), hue1) );
    var diffMin = diffs.Min(x => x);
    return diffs.ToList().FindIndex(n => n == diffMin);
}

A few helper functions:

 // color brightness as perceived:
float getBrightness(Color c)  
    { return (c.R * 0.299f + c.G * 0.587f + c.B *0.114f) / 256f;}

// distance between two hues:
float getHueDistance(float hue1, float hue2)
{ 
    float d = Math.Abs(hue1 - hue2); return d > 180 ? 360 - d : d; }

//  weighed only by saturation and brightness (from my trackbars)
float ColorNum(Color c) { return c.GetSaturation() * factorSat + 
                                      getBrightness(c) * factorBri; }

// distance in RGB space
int ColorDiff(Color c1, Color c2) 
      { return  (int ) Math.Sqrt((c1.R - c2.R) * (c1.R - c2.R) 
                               + (c1.G - c2.G) * (c1.G - c2.G)
                               + (c1.B - c2.B) * (c1.B - c2.B)); }

Here is the handy little helper I used for the screenshot texts:

Brush tBrush(Color c) { 
      return getBrightness(c) < 0.5 ? Brushes.White : Brushes.Black; }

I have updated the screenshot to display not only 13 colors but also a number of mostly reddish colors for testing; all colors are shown with their values for hue, saturation and brightness. The last three numbers are the results of the three methods.

As you can see, the simple distance method is quite misleading hue-wise for bright and non-saturated colors: The last color (Ivory) is in fact a bright and pale yellow!

The third method which gauges all color properties is best imo. You should play around with the gauging numbers, though!

In the end it really depends on what you want to achieve; if, as it seems, you only care about the hues of the colors, simply go for the first method! You can call it, using your array like this:

int indexInArray = closestColor1(clist.ToList(), someColor);

For more on color distances see Wikipedia!

color distances

// the colors I used:
// your array
Color[] clist = new Color[13];
clist[0] = Color.Blue;
clist[1] = Color.BlueViolet;
clist[2] = Color.Magenta;
clist[3] = Color.Purple;
clist[4] = Color.Red;
clist[5] = Color.Tomato;
clist[6] = Color.Orange;
clist[7] = Color.Yellow;
clist[8] = Color.YellowGreen;
clist[9] = Color.Green;
clist[10] = Color.SpringGreen;
clist[11] = Color.Cyan;
clist[12] = Color.Ivory;

// and a list of color to test:
List<Color> targets = new List<Color>();
targets.Add(Color.Pink);
targets.Add(Color.OrangeRed);
targets.Add(Color.LightPink);
targets.Add(Color.DarkSalmon);
targets.Add(Color.LightCoral);
targets.Add(Color.DarkRed);
targets.Add(Color.IndianRed);
targets.Add(Color.LavenderBlush);
targets.Add(Color.Lavender);
like image 100
TaW Avatar answered Nov 12 '22 01:11

TaW


Try this:

    static void Main()
    {
        Color[] ColorArray =
        {
            Color.FromArgb(255, 245, 244, 242), 
            Color.FromArgb(255, 245, 244, 240),
            Color.FromArgb(255, 245, 244, 238)
        };

        var closest = GetClosestColor(ColorArray, Color.FromArgb(255, 245, 244, 241));
        Console.WriteLine(closest);
    }

    private static Color GetClosestColor(Color[] colorArray, Color baseColor)
    {
        var colors = colorArray.Select(x => new {Value = x, Diff = GetDiff(x, baseColor)}).ToList();
        var min = colors.Min(x => x.Diff);
        return colors.Find(x => x.Diff == min).Value;
    }

    private static int GetDiff(Color color, Color baseColor)
    {
        int a = color.A - baseColor.A,
            r = color.R - baseColor.R,
            g = color.G - baseColor.G,
            b = color.B - baseColor.B;
        return a*a + r*r + g*g + b*b;
    }

here I interpret closest as Euclidean distance in ARGB space

like image 3
Alex Zhukovskiy Avatar answered Nov 11 '22 23:11

Alex Zhukovskiy