Whether it is a heat map or a simple legend on a graph,
color has the uncanny ability to instantly convey to users the intricacies of
their data. But like most powerful constructs, color can easily be
misunderstood and abused. I highly
recommend that all GUI developers read up on all the crazy (and cool) things
you can do to
manipulate colors.
Last year I spent a fair amount of time scouring the net trying to find a
solution to a frequently experienced problem: background - foreground color
readability. And for the most part I came up empty handed, so I did even more
digging and researching and have found the optimal way of determining whether
white or black text will look best on an arbitrary background color. At first I
tried solely looking at the "V" component of the
HSV
color space, but that would dictate the I use black text on a blue backgro
und (which
doesn't work).
I tried making my own custom metrics like if (R + G + B)/(255 * 3) was greater
than 1/2 then use black, else white, but when you test the results, it just
doesn't seem to work all that well. The problem lies with the human eye, while
the monitor has a certain color addressing space, people actually perceive
color differently. It makes sense when you think about it, for most of homo sapiens history have primarily looked at all the green vegitation surrounding us; so
naturally the human eye is more perceptive to
shades of green than red or blue. After some
extra long digging, I stumbled upon the
YIQ color space, where "Y" is the
luma a.k.a. perceived luminance. The formula for calculating Y
from RGB is Y = 0.299 * R + 0.587 * G + 0.114 * B
We can now calculate the maximum perceived luminance of white (0.299 * 255 +
0.587 * 255 + 0.114 * 255) and the midpoint at which something is
"half-bright" by dividing the max into 2. Because I'm calculating
values relative to one another, I simply made each color coefficient a whole
integer to make an infinitesimally small difference in performance. You should
try it out, it works really well.
//perceived luminance
private const int RED_LUMINANCE = 299;
private const int GREEN_LUMINANCE = 587;
private const int BLUE_LUMINANCE = 114;
//calculated from
http://en.wikipedia.org/wiki/Luminance(video)
private const int MAX_LUMINANCE = (RED_LUMINANCE * 255 + GREEN_LUMINANCE * 255
+ BLUE_LUMINANCE * 255);
private const int MID_LUMINANCE = MAX_LUMINANCE / 2;
/// <summary>
/// Finds the foreground (white or black) that will be easiest to read
/// with the given background
/// </summary>
public static Color CalculateForeColor(Color backColor)
{
int totalCustomBrightness =
((backColor.R * RED_LUMINANCE) +
(backColor.G * GREEN_LUMINANCE) + (backColor.B * BLUE_LUMINANCE));
if (totalCustomBrightness <= MID_LUMINANCE)
return Color.White;
else
return Color.Black;
}