Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

Possible Duplicate:
Making a symbol appear on any colour

I have to mark a certain item in a way that will make it stick-out in the background. I need it to be surrounded with the color that contrasts the background as much as possible so it will pop out and easily noticeable by the player.

Lets say I know the background is color 'x', how do I find 'y' such that it will be very contrasting to 'x' and easy to notice in a background where 'x' is a dominant color?

I first thought about inverting color 'x' and then I noticed that when 'x' is a medium shade of gray, if I invert 'x' to get 'y', then 'y' is also a medium shade of gray which does not work.

share|improve this question

marked as duplicate by Joe Wreschnig, Byte56, bummzack, Jonathan Hobbs, michael.bartnett Oct 9 '12 at 3:01

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

4  
The "most contrasting color" is often going to be really ugly, is that really what you want, or do you just want a sufficiently contrasting color? –  user744 Oct 8 '12 at 13:28
    
If you can address the concern you mention that would be exquisite. I was thinking of lowering the bar and thought it would be difficult to answer. It would be much better if it was sufficiently contrasting while taking into account looks. –  Zehelvion Oct 8 '12 at 13:52
1  
A-ha, I knew I had answered this question before, I just couldn't find it on my answers list searching for "color". UKians... –  user744 Oct 8 '12 at 14:20
    
I decided to take time testing both answers. :) –  Zehelvion Oct 8 '12 at 19:53
3  
Not to diss the great answers below, but - have you considered just using an outline (or in your case, an additional outline)? e.g. take a look how the text here is clearly visible over both light and dark backgrounds, due to it having an outline in a color that strongly contrasts with the text color. Will also make your coloring choice consistent, which is a plus imo. Just a thought. And if you need to really make it stick out, you can animate that outline! –  Oak Oct 8 '12 at 22:02

5 Answers 5

up vote 35 down vote accepted

A quick and easy way - though not 100% precise one - is to consider just the five extreme points white, black, red, green and blue.

First, let's transform RGB into linear space. Officially this is usually done by this formula (assuming the source data is in sRGB, which is the default for most graphic card operations on 8-bit data and nearly every image you see displayed on the web):

enter image description here

... where a = 0.055 for C = {R, G, B}

Or, if you just want a quick approximation, use Clinear = Csrgb2.2.

Black or White?

You can then calculate two helpful values, luminance Y and saturation S. Useful formulas (for our linear-space RGB values) are:

Y = 0.2126 R + 0.7152 G + 0.0722 B

S = (max(R, G, B) - min(R, G, B)) / max(R, G, B)

The second one is the calculation from the HSV colour space.

If the saturation is low enough (pick any value you like; something between 0.3 and 0.5 would work fine), check the luminance; if that's > 0.5, your contrasting colour is black, else if it's < 0.5 the colour is white. For exactly Y = 0.5, both work.

Red, Green or Blue?

For colours with high saturation, you can calculate a hue H and decide according to it. Since I'm lazy, I'll just copy the calculation from Wikipedia (hue is named H2):

enter image description here

enter image description here

If your hue is between 60° and 180°, your colour is green, if it's between 180° and 300°, it's blue, else it's red. Extend it to yellow, turquoise and magenta for more variation, or just use the opposite of the just-calculated hue at maximum saturation and lightness.

share|improve this answer
    
+1 This is an amazingly good answer, the main reason I picked Snake's was simplicity, I don't fully understand the part with the (alpha) (Beta) (H2) (C2).. –  Zehelvion Oct 8 '12 at 14:52
    
+1 For being pro. –  Markus von Broady Oct 8 '12 at 15:01
    
@ArthurWulfWhite Ignore the C2, and both apha and beta are just used to calculate the hue (H2) - that's all you need. They represent the coordinates in which the colour you're using is on the 2D plane below. –  Martin Sojka Oct 8 '12 at 15:02
    
This is really very cool stuff. So H2 is basically the Hue value? I really love applied mathematics and this seems like a shining example to me. –  Zehelvion Oct 8 '12 at 17:26
    
In the first part, how do I calculate the sRGB from regular RGB and what is a value? –  Zehelvion Oct 14 '12 at 23:32

Since there is some subjectivity to any answer here, I'll offer one that is easy to understand and easy to compute. Whether it suits you and your needs is up to you.

Imagine color space represented by R, G, and B values each on the interval [0,1]. Imagine the RGB value being a vector in color space, where that vector is confined to a cube. For any RGB value in that space, the most contrasting value would be geometrically as far away from that value while still being on that cube.

Offset the cube, so it is centered at the origin, with new values r, g, and b on the interval [-0.5, 0.5]. Do the by subtracting 0.5 from all of the original RGB values. Now compute the sign of each component of rgb and multiply by -0.5. This will put you at one of the vertices of the offset color space cube. Add 0.5 to each value to restore the values to the original color space cube.

share|improve this answer

Just to distill Martin Sojka's excellent answer into something simple to apply, here's how to decide whether black or white text would have higher contrast on a given background color (R, G, B) in the sRGB color space:

const float gamma = 2.2;
float L = 0.2126 * pow( R, gamma )
        + 0.7152 * pow( G, gamma )
        + 0.0722 * pow( B, gamma );

boolean use_black = ( L > 0.5 );

This assumes that R, G and B are floating-point numbers ranging from 0.0 to 1.0. If what you have is, say, integers from 0 to 255, convert them to floats and divide them by 255.

(I would not suggest using colored text, both because the human eye has much poorer spatial resolution for color than for luminance, and also because combinations of highly saturated complementary colors tend to be irritating to look at.)

Note that, if L is close to 0.5, small changes in the background color could cause the most contrasting text color to flip from black to white or vice versa. To avoid this happening too frequently, you could save the previous text color and only change it if L moves too far from 0.5:

if ( L > 0.6 ) {
    use_black = true;
} else if ( L < 0.4 ) {
    use_black = false;
} else {
    // keep previous text color
}

Ps. If you wanted an even quicker approximation, you could round the exponent gamma down to 2.0, allowing you to replace the pow() with a simple multiplication:

float L = 0.2126 * R*R + 0.7152 * G*G + 0.0722 * B*B;

This approximate formula is still within ±0.05 of the correct luminance calculated with the official piecewise formula given by Martin (or with the gamma = 2.2 approximation used above, which itself is within ±0.01 of the official formula), and so more than close enough for this purpose. Besides, you can mostly compensate for the error simply by adjusting the threshold from 0.5 to 0.54 or so.

Finally, note that, if the background has a very busy texture, it's possible that no single color may stand out from it very effectively. In such cases, you may want to consider using combinations of colors, such as black text with a white outline.

share|improve this answer

The most contrasting color would be the color that is as far as possible from color X. It's easy to get it this way (assuming 0,0,0=black and 1,1,1=white -- floating point colors):

y = rgb_color( 
    x.r > 0.5 ? 0 : 1,
    x.g > 0.5 ? 0 : 1,
    x.b > 0.5 ? 0 : 1
);

The result is quite ugly though, so you might want to consider a few more things:

1) generate only black / white colors:

v = ( x.r + x.g + x.b ) / 3 > 0.5 ? 0 : 1;
y = rgb_color( v, v, v );

2) change the values "0" and "1" to something less sharp

3) work in the HSV color space:

y = hsv_color( 
    ( x.h + 0.5 ) % 1,
    x.s > 0.5 ? 0 : 1,
    x.v > 0.5 ? 0 : 1
);
share|improve this answer
    
That is exactly what I just thought myself and as Joe Wreschnig and you mentioned it is not very pretty. It does answer the question perfectly. Working in hsv sounds interesting, however, I fear that both 0 and 1 are red in hsv so for hue I would have to do a (x+0.5)%1.0 kind of thing. and also I will have to keep value slightly above and bellow 0 and 1 to make it less sharp. –  Zehelvion Oct 8 '12 at 14:05
    
@ArthurWulfWhite You're right about hue, I fixed it in my answer. –  snake5 Oct 8 '12 at 14:15
7  
What you first propose is not even the most contrasting color, RGB is not perceptually uniform nor split along what are thought to be the opponent colors in our visual processing. Change your recommendation from HSV to HSL and your third option is an okay answer, though. But Martin's is waaaay better. –  user744 Oct 8 '12 at 14:15
    
@JoeWreschnig: Given that I use way more calculations and don't derive many more benefits in terms of results out of them (aside from correct luminance values and thus better B&W/colour threshold), I'm not sure "better" is the right word. More extensible and more easily tweakable, maybe. :) –  Martin Sojka Oct 8 '12 at 14:29
1  
@snake5 I really like this answer the best, it is very simple and concise. I learned a lot from Martin Sojka's answer about converting bases too. I will pick this as the accepted answer cause it is well thought out and a quick fix :) –  Zehelvion Oct 8 '12 at 14:54

It depends how you invert the color, the standard way of doing so is something like:

newColor = new Color(255-oldColor.r, 255-oldColor.g, 255-oldColor.b);

Which means that something grey like (128,128,128) will turn into something very grey (127, 127, 127). You may want to try inverting the hue instead. Convert your color to HSV mode and invert the hue color.hue = 360-color.hue.

You could additionally invert the saturation.

Ultimately, it sounds like you're the one choosing the original colors here. So just choose colors that invert nicely.

Another option would be to check for colors that won't change much. Something like:

if((255-oldColor.r)-oldColor.r < 10)
    newColor.r = 0;
share|improve this answer
1  
If I convert the hue of a gray color, it would stay gray. –  Zehelvion Oct 8 '12 at 13:49

Not the answer you're looking for? Browse other questions tagged or ask your own question.