Click here to Skip to main content
15,892,809 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 164.5K   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

 
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 
GeneralSorry, I must have been drunk yesterday night :/ (VERY LONG POST) Pin
Kochise8-May-04 9:17
Kochise8-May-04 9:17 
I even had trouble to calculate with my pocket calculator !

Never mind, slightly forget the previous post, even if the idea is interresting, here the REAL contrast color :
INT CalcContrastColor (INT crBg)
{ 
  CRGB l_nColorContrast(crBg);

  CRGB::sHSL  l_sHSL;
  
  l_nColorContrast.GetHSL(l_sHSL);

  // Change Hue and Lum
  l_sHSL.H += 180.0; // Turn around and select the additive color
  l_sHSL.S = 1.0; // Make sure Sat is to the max
  l_sHSL.L = 1.0 - l_sHSL.L; // Invert Lum

  l_nColorContrast.SetHSL(l_sHSL, FALSE);

  return l_nColorContrast.GetCOLORREF();
}

Just change the function in your code. As explained, I select the additive color, force saturation to 1, and invert light. It then even works fine in very low saturation ! To get the code to work, add the following class above it :
class CRGB
{
  public :
    // Structs
    struct sARGB
    { // 24 octets
      double A;
	    double R;
      double G;
      double B;
    };

    struct sHSL
    { // 24 octets
	    double H;
      double S;
      double L;
    };

    // Ctors
    inline
    CRGB
    (
    )
    {
      mp_sARGB.A = 0.0;
      mp_sARGB.R = 0.0;
      mp_sARGB.G = 0.0;
      mp_sARGB.B = 0.0;
    }

    inline
    CRGB
    ( double i_nRed
    , double i_nGreen
    , double i_nBlue
    )
    {
      SetRGB
      ( i_nRed
      , i_nGreen
      , i_nBlue
      );
    }

    inline
    CRGB
    ( unsigned int i_nRed
    , unsigned int i_nGreen
    , unsigned int i_nBlue
    )
    {
      SetRGB
      ( i_nRed
      , i_nGreen
      , i_nBlue
      );
    }

    inline
    CRGB
    ( COLORREF i_sColor
    )
    {
      SetRGB
      ( i_sColor
      );
    }

    // Functions
    inline
    void SetRGB
    ( sARGB& i_rsARGB
    )
    {
      mp_sARGB.A = i_rsARGB.A;
      mp_sARGB.R = i_rsARGB.R;
      mp_sARGB.G = i_rsARGB.G;
      mp_sARGB.B = i_rsARGB.B;
    };

    inline
    void SetRGB
    ( double i_nRed
    , double i_nGreen
    , double i_nBlue
    )
    {
      mp_sARGB.A = 0.0;
      mp_sARGB.R = i_nRed;
      mp_sARGB.G = i_nGreen;
      mp_sARGB.B = i_nBlue;
    };

    inline
    void SetRGB
    ( unsigned int i_nRed
    , unsigned int i_nGreen
    , unsigned int i_nBlue
    )
    {
      SetRGB
      ( i_nRed   / 255.0
      , i_nGreen / 255.0
      , i_nBlue  / 255.0
      );
    };

    inline
    void SetRGB
    ( COLORREF i_sColor
    )
    {
      SetRGB
      ( (unsigned int) ((i_sColor & 0x000000FF) >>  0)
      , (unsigned int) ((i_sColor & 0x0000FF00) >>  8)
      , (unsigned int) ((i_sColor & 0x00FF0000) >> 16)
      );

      mp_sARGB.A = ((i_sColor & 0xFF000000) >> 24) / 255.0;
    };

    inline
    sARGB GetARGB
    ( void
    )
    {
      return mp_sARGB;
    };

    inline
    void SetHSL
    ( sHSL& i_rsHSL
    , BOOL  i_bRefit = TRUE
    )
    {
      sARGB l_sARGB;

      HslToRgb(l_sARGB, i_rsHSL, i_bRefit);

      SetRGB(l_sARGB);
    };

    inline
    void GetHSL
    ( sHSL& o_rsHSL
    )
    {
      RgbToHsl(o_rsHSL, mp_sARGB);
    };

    inline
    COLORREF GetCOLORREF
    ( void
    )
    {
      return
      ( ((((COLORREF) (mp_sARGB.A * 255.0)) & 0x000000FF) << 24)
      | ((((COLORREF) (mp_sARGB.R * 255.0)) & 0x000000FF) <<  0)
      | ((((COLORREF) (mp_sARGB.G * 255.0)) & 0x000000FF) <<  8)
      | ((((COLORREF) (mp_sARGB.B * 255.0)) & 0x000000FF) << 16)
      )
      ;
    };

    inline
    void RgbToHsl
    ( sHSL&  o_rsHSL
    , sARGB& i_sARGB
    )
    {
      double l_nValMin;
      double l_nValMax;
      double l_nValDif;
      double l_nValSum;

      if(i_sARGB.R > i_sARGB.G)
      {
        if(i_sARGB.G > i_sARGB.B)
        {
          l_nValMax = i_sARGB.R;
          l_nValMin = i_sARGB.B;
        }
        else
        {
          if(i_sARGB.R > i_sARGB.B)
          {
            l_nValMax = i_sARGB.R;
          }
          else
          {
            l_nValMax = i_sARGB.B;
          }

          l_nValMin = i_sARGB.G;
        }
      }
      else
      {
        if(i_sARGB.G < i_sARGB.B)
        {
          l_nValMax = i_sARGB.B;
          l_nValMin = i_sARGB.R;
        }
        else
        {
          l_nValMax = i_sARGB.G;

          if(i_sARGB.R < i_sARGB.B)
          {
            l_nValMin = i_sARGB.R;
          }
          else
          {
            l_nValMin = i_sARGB.B;
          }
        }
      }

      l_nValDif = l_nValMax - l_nValMin;
      l_nValSum = l_nValMax + l_nValMin;

      if(l_nValDif == 0)
      {
        o_rsHSL.H = 0.0;
        o_rsHSL.S = 0.0;
      }
      else
      {
        if(l_nValMax == i_sARGB.R)
        {
          o_rsHSL.H = 60.0 * (6.0 + i_sARGB.G - i_sARGB.B);
        }
        else
        {
          if(l_nValMax == i_sARGB.G)
          {
            o_rsHSL.H = 60.0 * (2.0 + i_sARGB.B - i_sARGB.R);
          }
          else
          {
            o_rsHSL.H = 60.0 * (4.0 + i_sARGB.R - i_sARGB.G);
          }
        }

        while(o_rsHSL.H > 360.0)
        {
          o_rsHSL.H -= 360.0;
        }

        if(l_nValSum <= 1.0)
        {
          o_rsHSL.S = l_nValDif / l_nValSum;
        }
        else
        {
          o_rsHSL.S = l_nValDif / (2.0 - l_nValSum);
        }
      }

      o_rsHSL.L  = l_nValSum / 2.0;
    }

    inline
    void HslToRgb
    ( sARGB& o_rsARGB
    , sHSL&  i_rsHSL
    , BOOL   i_bRefit = TRUE
    )
    {
      o_rsARGB.A = 0.0;

      if(i_bRefit == TRUE)
      {
        if(i_rsHSL.S > 1.0)
        {
          i_rsHSL.S = 1.0;
        }
        else if(i_rsHSL.S < 0.0)
        {
          i_rsHSL.S = 0.0;
        }else{}

        if(i_rsHSL.L > 1.0)
        {
          i_rsHSL.L = 1.0;
        }
        else if(i_rsHSL.L < 0.0)
        {
          i_rsHSL.L = 0.0;
        }else{}
      }
      else
      {
        while(i_rsHSL.S > 1.0)
        {
          i_rsHSL.S -= 1.0;
        }

        while(i_rsHSL.S < 0.0)
        {
          i_rsHSL.S += 1.0;
        }

        while(i_rsHSL.L > 1.0)
        {
          i_rsHSL.L -= 1.0;
        }

        while(i_rsHSL.L < 0.0)
        {
          i_rsHSL.L += 1.0;
        }
      }

      if(i_rsHSL.S == 0.0)
      {
        o_rsARGB.R = i_rsHSL.L;
        o_rsARGB.G = i_rsHSL.L;
        o_rsARGB.B = i_rsHSL.L;
      }
      else
      {
        double l_nTempo1;
        double l_nTempo2;

        if(i_rsHSL.L <= 0.5)
        {
          l_nTempo1
          = i_rsHSL.L
          + ( i_rsHSL.S
            * i_rsHSL.L
            )
          ;
        }
        else
        {
          l_nTempo1
          = i_rsHSL.L
          + i_rsHSL.S
          - ( i_rsHSL.L
            * i_rsHSL.S
            )
          ;
        }

        l_nTempo2
        = (2.0 * i_rsHSL.L)
        - l_nTempo1
        ;

        o_rsARGB.R
        = ColorAngle
          ( l_nTempo1
          , l_nTempo2
          , i_rsHSL.H + 120.0
          )
        ;

        o_rsARGB.G
        = ColorAngle
          ( l_nTempo1
          , l_nTempo2
          , i_rsHSL.H
          )
        ;

        o_rsARGB.B
        = ColorAngle
          ( l_nTempo1
          , l_nTempo2
          , i_rsHSL.H - 120.0
          )
        ;

      }
    }

  protected :
    // Function
    inline
    double ColorAngle
    ( double i_nTempo1
    , double i_nTempo2
    , double i_nHue
    )
    {
      while(i_nHue > 360.0)
      {
        i_nHue -= 360.0;
      }

      while(i_nHue < 0.0)
      {
        i_nHue += 360.0;
      }

      if(i_nHue < 60.0)
      {
        i_nTempo2
        += ( ( i_nTempo1
             - i_nTempo2
             )
           * i_nHue
           )
         / 60.0
        ;
      }
      else if(i_nHue < 180.0)
      {
        i_nTempo2 = i_nTempo1;
      }
      else if(i_nHue < 240.0)
      {
        i_nTempo2
        += ( ( i_nTempo1
             - i_nTempo2
             )
           * ( 240.0
             - i_nHue
             )
           )
         / 60.0
        ;
      }else{}

      return i_nTempo2;
    }

    // Members
    sARGB mp_sARGB;
};

Kochise


In Code we trust !
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.