Click here to Skip to main content
15,914,160 members
Articles / Programming Languages / C++
Article

Contrasting Colors

Rate me:
Please Sign up or sign in to vote.
4.08/5 (16 votes)
4 May 20042 min read 165K   2.1K   30   37
New method of calculating a contrasting color for a given color
Image 1

Introduction

The conventional way of calculating a contrasting color is to XOR the given color by 0xFFFFFF (or &HFFFFFF). However, the contrasting color obtained in this way may sometimes be similar to the original color, and our eyes can't really differentiate between the two distinctively. Thus this conventional way is not good enough. For example, grey, having the hex value of 0x808080, when XORed with 0xFFFFFF, gives 0x7F7F7F, which is very close to the original color.

I have come up with a new and simple solution that is based on this conventional method. This solution calculates a better contrasting color, but note that it is not always perfect. (Note: I'm not sure whether there already exists such a method).

The solution

This method works by testing whether each of the components (namely red, green, and blue) of the given color is close to 0x80 (128 in decimal). The closeness is defined by the compile time macro TOLERANCE, which is simply a value. If all the components are close to 0x80, then the contrasting color calculated using the old method will not be a good contrast to the original color. The new method overcomes this problem by adding 0x7F7F7F to the given color, and then to prevent overflow (because color values range only from 0x000000 to 0xFFFFFF), the result is ANDed with 0xFFFFFF.

Using the code

The main thing about this project is the function CalcContrastingColor, which is presented below.

At run time, click the "Background..." button to select a background color. Then the program will display a text using the calculated contrasting color. You can also change the font of the text.

// crBG is in 0xRRGGBB or 0xBBGGRR format.
INT CalcContrastColor (INT crBg){

    if (
        abs(((crBg ) & 0xFF) - 0x80) <= TOLERANCE &&
        abs(((crBg >> 8) & 0xFF) - 0x80) <= TOLERANCE &&
        abs(((crBg >> 16) & 0xFF) - 0x80) <= TOLERANCE

    ) return (0x7F7F7F + crBg) & 0xFFFFFF;

    else return crBg ^ 0xFFFFFF;
}

History

Improvements in version 2:
  • Display the outputs as shown in the above image.
  • Some text included in order to let user decide better whether it is a good contrasting color.
  • Windows XP theme compatible.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here



Comments and Discussions

 
GeneralRe: Simpler? Pin
Kippesoep6-May-04 21:02
Kippesoep6-May-04 21:02 
GeneralSome hints about RGB->HSL Pin
Kochise4-May-04 22:39
Kochise4-May-04 22:39 
GeneralRe: Some hints about RGB-&gt;HSL Pin
Paolo Messina5-May-04 3:13
professionalPaolo Messina5-May-04 3:13 
GeneralYop, true ;) Pin
Kochise5-May-04 4:15
Kochise5-May-04 4:15 
GeneralRe: Some hints about RGB-&gt;HSL Pin
User 10455755-May-04 5:41
User 10455755-May-04 5:41 
GeneralThe difference of grey... Pin
Kochise5-May-04 6:59
Kochise5-May-04 6:59 
GeneralRe: The difference of grey... Pin
User 10455756-May-04 4:25
User 10455756-May-04 4:25 
GeneralOK, here is the solution explained Pin
Kochise7-May-04 10:45
Kochise7-May-04 10:45 
Some links first :

http://astronomy.swin.edu.au/~pbourke/colour/ycc/
http://www.fourcc.org/fccyvrgb.php
http://elm-chan.org/works/yuv2rgb/report.html
http://people.via.ecp.fr/~remi/ecp/tpi/rapport/yuv.html (french)

Now you know your lesson, here the calculation :

You are wanting to always get the BEST contrast color, that's to say a color that definitly distinguish from the source color. But what do you expect from differences ?

Your math almost only rely on chrominance, which was several times spotted out has being not reliable in case of B/W (greyscale) transformation.

We are expecting a heavy duty algorithm to match both chrominance and luminance criterias. Saturation is already linked to luminance, so it's no use to take care of...

Let's first remember some math :
H =  0.5000 R - 0.4183 G - 0.0816 B [0.0, 1.0]
S = -0.1687 R - 0.3312 G + 0.5000 B [-0.5, 0.5]
L =  0.2989 R + 0.5866 G + 0.1145 B [-0.5, 0.5]

R = L            + 1.4022 H [0.0, 1.0]
G = L - 0.3456 S - 0.7145 H [0.0, 1.0]
B = L + 1.7710 S            [0.0, 1.0]

Now first the chrominance difference :

You have first to understand what secondary color is. Red is complementary to cyan as red is RGB(255, 0, 0) and cyan is RGB(0, 255, 255). From the Hue point of view, red is 0 and cyan 0.5 (127 on the field of 0-255). To get the best difference, just add/sub 0.5 (127) to the chrominance.

Now on a B/W TV, where all is grey, a uniform grey screen may shows several different colors having the same luminance value. As the luminance is coded from -0.5 to 0.5, the solution to get the best luminance difference is the same : add/sub 0.5 (127) to the luminance.

Now it is to write the complete math to get RcGcBc (contrast) from RoGoBo (original) :
H =  0.5000 Ro - 0.4183 Go - 0.0816 Bo + 0.5000
S = -0.1687 Ro - 0.3312 Go + 0.5000 Bo
L =  0.2989 Ro + 0.5866 Go + 0.1145 Bo + 0.5000

Here the new corrected hue and luminance. let just turn things into RGB :
Rc = (L =  0.2989 Ro + 0.5866 Go + 0.1145 Bo + 0.5000)                                                   + 1.4022 (H =  0.5000 Ro - 0.4183 Go - 0.0816 Bo + 0.5000)
Gc = (L =  0.2989 Ro + 0.5866 Go + 0.1145 Bo + 0.5000) - 0.3456 (S = -0.1687 Ro - 0.3312 Go + 0.5000 Bo) - 0.7145 (H =  0.5000 Ro - 0.4183 Go - 0.0816 Bo + 0.5000)
Bc = (L =  0.2989 Ro + 0.5866 Go + 0.1145 Bo + 0.5000) + 1.7710 (S = -0.1687 Ro - 0.3312 Go + 0.5000 Bo)

I think you get the idea :
Rc = (0.2989 + (1.4022 * 0.5000)) Ro + (0.5866 + (1.4022 * - 0.4183)) Go + (0.5866 + (1.4022 * - 0.0816)) Bo + (1.4022 * 0.5000)
Gc = (0.2989 + (- 0.3456 * -0.1687) + (- 0.7145 * 0.5000)) Ro + (0.5866 + (- 0.3456 * - 0.3312) + (- 0.7145 * - 0.4183)) Go + (0.5866 + (- 0.3456 * 0.5000) + (- 0.7145 * 0.0816)) Bo + (0.5000 + (- 0.7145 * 0.5000))
Bc = (0.2989 + (1.4022 * -0.1687)) Ro + (0.5866 + (1.7710 * - 0.3312)) Go + (0.1145 + (1.7710 * 0.5000)) Bo + 0.5000

He he, very close to the end :
Rc = 1.0000 Ro + 1.0000 Go + 0.4722 Bo + 0.7011
Gc = 0.0000 Ro + 1.0000 Go + 0.3555 Bo + 0.8572
Bc = 0.5354 Ro + 0.0000 Go + 1.4721 Bo + 0.5000

We don't have to get frighten about the values that seems to get beyon the range, it's normal and will be cleared once the value will be down-sized from int to char ! Here the final stage, let's kill the boss :
COLORREF l_nColorContrast 
= ( // Rc
  (( (((int) (l_nColor & 0x000000FF) >>  0) * 0x00010000) // Ro * 1.0000
   + (((int) (l_nColor & 0x0000FF00) >>  8) * 0x00010000) // Go * 1.0000
   + (((int) (l_nColor & 0x00FF0000) >> 16) * 0x000078E2) // Bo * 0.4722
   + 0x0000B37B                                           //    + 0.7011
  ) >> 16) & 0x000000FF)
| ( // Gc
  (( (((int) (l_nColor & 0x000000FF) >>  0) * 0x00000000) // Ro * 0.0000
   + (((int) (l_nColor & 0x0000FF00) >>  8) * 0x00010000) // Go * 1.0000
   + (((int) (l_nColor & 0x00FF0000) >> 16) * 0x00005B02) // Bo * 0.3555
   + 0x0000DB71                                           //    + 0.8572
  ) >>  8) & 0x0000FF00)
| ( // Bc
  (( (((int) (l_nColor & 0x000000FF) >>  0) * 0x0000890F) // Ro * 0.5354
   + (((int) (l_nColor & 0x0000FF00) >>  8) * 0x00000000) // Go * 0.0000
   + (((int) (l_nColor & 0x00FF0000) >> 16) * 0x000178DB) // Bo * 1.4721
   + 0x00008000                                           //    + 0.5000
  ) >>  0) & 0x00FF0000)
;

Perfect, now let's optimize things a bit :
COLORREF l_nColorContrast 
= ( // Rc
  (( (((int) (l_nColor & 0x000000FF) << 16)             ) // Ro * 1.0000
   + (((int) (l_nColor & 0x0000FF00) <<  8)             ) // Go * 1.0000
   + (((int) (l_nColor & 0x00FF0000) >> 16) * 0x000078E2) // Bo * 0.4722
   + 0x0000B37B                                           //    + 0.7011
  ) >> 16) & 0x000000FF)
| ( // Gc
  (( (((int) (l_nColor & 0x0000FF00) <<  8)             ) // Go * 1.0000 // No Ro
   + (((int) (l_nColor & 0x00FF0000) >> 16) * 0x00005B02) // Bo * 0.3555
   + 0x0000DB71                                           //    + 0.8572
  ) >>  8) & 0x0000FF00)
| ( // Bc
  (( (((int) (l_nColor & 0x000000FF) >>  0) * 0x0000890F) // Ro * 0.5354
   + (((int) (l_nColor & 0x00FF0000) >> 16) * 0x000178DB) // Bo * 1.4721 // No Go
   + 0x00008000                                           //    + 0.5000
  )      ) & 0x00FF0000)
;

OK, I not yet tested things as it's very late and I wanted to show you that coding is an art that cannot only be tricked with neat XOR ! It's 23:00 there, I'm gonna take a shower then go to bed Wink | ;) There might have a casting problem in the result as I just copied and pasted my previous code. Will check that tomorrow Wink | ;) Good nite to all !

Kochise

In Code we trust !
GeneralSorry, I must have been drunk yesterday night :/ (VERY LONG POST) Pin
Kochise8-May-04 9:17
Kochise8-May-04 9:17 
QuestionI only want the returned color to be black or white Pin
pscholl11-Jun-09 4:37
pscholl11-Jun-09 4:37 
AnswerRe: I only want the returned color to be black or white Pin
User 104557514-Jun-09 15:50
User 104557514-Jun-09 15:50 
GeneralSuper! Pin
Elcrombo4-May-04 21:18
Elcrombo4-May-04 21:18 
GeneralLooks Good Pin
kcjt3-May-04 12:27
kcjt3-May-04 12:27 
Generalcontrasting colors Pin
dbdduane30-Apr-04 6:24
dbdduane30-Apr-04 6:24 
GeneralGood Job Pin
mkg27-Apr-04 5:07
mkg27-Apr-04 5:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.