Compare Rgb Colors in C#

Compare RGB colors in c#

What you are looking for is called Delta-E.

http://www.colorwiki.com/wiki/Delta_E:_The_Color_Difference

It is the distance between two colors in LAB color space. It is said that the human eye cannot distinguish colors below 1 DeltaE (I find that my eyes can find differences in colors below 1 DeltaE, each person is different.)

There are 4 formulas for 'color difference'.

  • Delta E (CIE 1976)
  • Delta E (CIE 1994)
  • Delta E (CIE 2000)
  • Delta E (CMC)

Check the math link on this site:

  • http://www.brucelindbloom.com/

So the proper answer is to convert your RGB to LAB using the formula given, then use DeltaE 1976 to determine the 'difference' in your colors. A result of 0 would indicate identical colors. Any value higher than 0 could be judged by the rule 'A delta e of 1 or less is indistinguishable by most people'.

What is the fastest way to compare two RGB colors in .NET?

Can you store your colors as text?

Comparing text would be faster than comparing a Color object and calling the ToArgb method every time.

So do this instead:

bool ColorChanged2(string a, string b)
{
return a != b;
}

See the following code ... comparing strings is usually twice as fast.

    string runTest()
{
int howMany = 1000000;

Color c1 = Color.Red;
Color c2 = Color.Green;
string c3 = "FF0000";
string c4 = "00FF00";

string result = "";
Stopwatch sw = new Stopwatch();

// Compare using Color object
sw.Start();
for (int i = 0; i < howMany; i++)
{
bool response = ColorChanged1(c1, c2);
}
sw.Stop();

result += "Compare Color objects" + Environment.NewLine;
result += "Elapsed: " + sw.Elapsed.ToString() + Environment.NewLine;
result += "" + Environment.NewLine;

// Compare strings
sw.Reset();
sw.Start();
for (int i = 0; i < howMany; i++)
{
bool response = ColorChanged2(c3, c4);
}
sw.Stop();

result += "Compare strings" + Environment.NewLine;
result += "Elapsed: " + sw.Elapsed.ToString() + Environment.NewLine;
result += "" + Environment.NewLine;

return result;
}

bool ColorChanged1(Color a, Color b)
{
return a.ToArgb() != b.ToArgb();
}

bool ColorChanged2(string a, string b)
{
return a != b;
}

Results

Compare Color objects
Elapsed: 00:00:00.0611088

Compare strings
Elapsed: 00:00:00.0285529

Compare RGB colors in a pixel and change its original color to the closest one

Your code actually works, although there is a bit of overthinking put into it.

Try this:

The code has been moved to the Update section at the bottom of the post

Result:

Result


I've removed the overthinking part.

  • There's no reason (at least from reading your question) why you need to invert the color component values;

    1. Simply doing R = pixelColor.R is enough;

    2. And through this you don't have to think of it as "which has the least amount of
      red", but rather, "if it has the most amount of red, it's red!"

  • As LightStriker pointed out: You are missing (it is nowhere in your code) the code to set new value back into the image;

    1. This is accomplished using img.SetPixel(x, y, pixelColor).
  • I've added an else clause to match pixels where no single color component is greater than both others.

    1. For example, Yellow (255, 255, 0) would not be matched by your rules;

    2. Using the version in this answer, it gets replaced by a Black pixel.


Update: per the comments below asking for additional clarification. Here's how you would add more conditional statements:

// NEW (start) --------------------------------------------------
Color[] randomizedColors = new Color[] { Color.Red, Color.Green, Color.Blue };
Random randomizer = new Random();
// NEW (end) --------------------------------------------------

Bitmap img = new Bitmap(InputPictureBox.Image);
byte R, G, B;
Color pixelColor;

// NEW (start) --------------------------------------------------
Func<int, Color> ColorRandomizer = (numberOfColors) =>
{
if (numberOfColors > randomizedColors.Length)
{
numberOfColors = randomizedColors.Length;
}
return randomizedColors[randomizer.Next(numberOfColors)];
};
// NEW (end) --------------------------------------------------

for (int x = 0; x < img.Width; x++)
{
for (int y = 0; y < img.Height; y++)
{
pixelColor = img.GetPixel(x, y);
R = pixelColor.R;
G = pixelColor.G;
B = pixelColor.B;

if (R > G && R > B)
{
pixelColor = Color.Red;
}
else if (G > R && G > B)
{
pixelColor = Color.Green;
}
else if (B > R && B > G)
{
pixelColor = Color.Blue;
}
// NEW (start) --------------------------------------------------
else if (pixelColor == Color.Yellow)
{
// 2 = Red or Green
pixelColor = ColorRandomizer(2);
}
else if (pixelColor = Color.FromArgb(152, 152, 152))
{
// 3 = Red, Green, or Blue
pixelColor = ColorRandomizer(3);
}
/* else if (pixelColor = Some_Other_Color)
{
// 3 = Red, Green, or Blue
pixelColor = ColorRandomizer(3);
} */
// NEW (end) --------------------------------------------------
else
{
pixelColor = Color.Black;
}
img.SetPixel(x, y, pixelColor);
}
}

OutputPictureBox.Image = img;

With this updated code, add all colors that should be picked randomly to the randomizedColors array. Use the lambda function, ColorRandomizer, to assist in choosing a color randomly; keep in mind that this function will randomly pick between the first element and the one specified.

Compare colors with toleration

You may check defince a tolarance value and check if their difference is less than that:

Color blah = screenshot.GetPixel(x, y);
if (Math.Abs(Color.Red.GetHue() - blah.GetHue()) <= tolorance)
{
// ...
}

Compare two Color objects

Always read the documentation first:

"To compare colors based solely on their ARGB values, you should use
the ToArgb method. This is because the Equals and Equality members
determine equivalency using more than just the ARGB value of the
colors. For example, Black and FromArgb(0,0,0) are not considered
equal, since Black is a named color and FromArgb(0,0,0) is not"

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

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

Is there a better way to compare colors?

I was looking for a quick way to visually transfer a threshold for a "shade" of blue into its integer values. Using this chart and a bit of c# code and the suggestion of @jdphenix I am able to this.

enter image description here

    private Color FromHex(string hex)
{
if (hex.StartsWith("#"))
hex = hex.Substring(1);

if (hex.Length != 6) throw new Exception("Color not valid");

return Color.FromArgb(
int.Parse(hex.Substring(0, 2), System.Globalization.NumberStyles.HexNumber),
int.Parse(hex.Substring(2, 2), System.Globalization.NumberStyles.HexNumber),
int.Parse(hex.Substring(4, 2), System.Globalization.NumberStyles.HexNumber));
}

Putting the two together:

            // Starting blue threshold, or whatever your desired threshold is
Color BlueLowThreshold = FromHex("#00B4FF");
int blueLowThreshold = BlueLowThreshold.ToArgb();
// Ending blue threshold, or whatever your desired end threshold is
Color BlueHighThreshold = FromHex("#5000FF");
int blueHighThreshold = BlueHighThreshold.ToArgb();

Thank you for your suggestions.

How to determine if a color is close to another color

How about something like this?

bool ColorsAreClose(Color a, Color z, int threshold = 50)
{
int r = (int)a.R - z.R,
g = (int)a.G - z.G,
b = (int)a.B - z.B;
return (r*r + g*g + b*b) <= threshold*threshold;
}

(I just guessed at the default threshold, but you should set it to what you want.)

Basically this just computes, on average, whether the three color channels are close enough between the two colors.



Related Topics



Leave a reply



Submit