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?
Color distance is not a precisely defined thing. So here are three methods to measure it:
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:
color.GetBrightness()
method. This is, to put it mildly, totally useless. To wit: Blue
and Yellow
have the same value of 0.5
!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!
// 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);
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With