As a follow up to this question: (How can I draw legible text on a bitmap (Winforms)?), I'm drawing legible but small text on top of a bitmap by calculating the "average" color beneath the text, and choosing an appropriately contrasting color for the text.
I've stolen Till's code from https://stackoverflow.com/a/6185448/3784949 for calculating "average" bmp color. Now I'm looking at the "color difference" algorithm suggested by http://www.w3.org/TR/AERT#color-contrast.
This suggests that I need to make my color brightness at least 125 "units" greater, and my color difference at least 500 units greater, where brightness and difference are calculated like this:
Color brightness is determined by the following formula:
((Red value X 299) + (Green value X 587) + (Blue value X 114)) / 1000
Color difference is determined by the following formula:
(maximum (Red value 1, Red value 2) - minimum (Red value 1, Red value 2)) + (maximum (Green value 1, Green value 2) - minimum (Green value 1, Green value 2)) + (maximum (Blue value 1, Blue value 2) - minimum (Blue value 1, Blue value 2))
How do I implement this? I can set my color by ARGB (I believe, it's a label foreground color); but how do I calculate how much to change each individual value to achieve the difference being required here? I'm not familiar with the math required to break the "difference" units down into their component parts.
As an example, my "average" for one bitmap is: Color [A=255, R=152, G=138, B=129]. How do I "add" enough to each part to achieve the two differences?
EDIT: To be specific, my confusion lies here:
it looks like I need to add to three separate values (R,G,B) to achieve two different goals (new RGB adds up to original plus 125, new RGB adds up to original plus 500
it looks like I may need to "weight" my added brighness values to add more to G than R than B.
I have no idea how to address #1. And I'm not positive I'm correct about #2.
EDIT: Proposed solution
I'm currently experimenting with this:
private Color GetContrastingFontColor(Color AverageColorOfBitmap,
List<Color> FavoriteColors)
{
IEnumerable<Color> AcceptableColors =
(IEnumerable<Color>)FavoriteColors.Where(clr =>
(GetColorDifferenceAboveTarget(AverageColorOfBitmap, clr, (float)200) > 0)
&& (GetBrightnessAboveTarget(AverageColorOfBitmap, clr, (float).125) > 0))
.OrderBy(clr => GetColorDifferenceAboveTarget(
AverageColorOfBitmap, clr, (float)200));
return AcceptableColors.DefaultIfEmpty(Color.Aqua).First();
}
It's a good framework, but I need to work on selecting the "best" candidate from the list. Right now it's just returning "the qualifying color with the greatest color difference that meets the brightness criteria". However, this allows me to modify the float values (W3's "500 color difference required" is complete crap, zero KnownColors qualify) and experiment.
Support code:
private float GetBrightnessAboveTarget(Color AverageColorOfBitmap,
Color proposed, float desiredDifference)
{
float result = proposed.GetBrightness() - AverageColorOfBitmap.GetBrightness();
return result - desiredDifference;
}
private float GetColorDifferenceAboveTarget(Color avg, Color proposed,
float desiredDifference)
{
float r1 = Convert.ToSingle(MaxByte(Color.Red, avg, proposed));
float r2 = Convert.ToSingle(MinByte(Color.Red, avg, proposed));
float r3 = Convert.ToSingle(MaxByte(Color.Green, avg, proposed));
float r4 = Convert.ToSingle(MinByte(Color.Green, avg, proposed));
float r5 = Convert.ToSingle(MaxByte(Color.Blue, avg, proposed));
float r6 = Convert.ToSingle(MinByte(Color.Blue, avg, proposed));
float result = (r1 - r2) + (r3 - r4) + (r5 - r6);
return result - desiredDifference;
}
private byte MaxByte(Color rgb, Color x, Color y)
{
if (rgb == Color.Red) return (x.R >= y.R) ? x.R : y.R;
if (rgb == Color.Green) return (x.G >= y.G) ? x.G : y.G;
if (rgb == Color.Blue) return (x.B >= y.B) ? x.B : y.B;
return byte.MinValue;
}
private byte MinByte(Color rgb, Color x, Color y)
{
if (rgb == Color.Red) return (x.R <= y.R) ? x.R : y.R;
if (rgb == Color.Green) return (x.G <= y.G) ? x.G : y.G;
if (rgb == Color.Blue) return (x.B <= y.B) ? x.B : y.B;
return byte.MinValue;
}