29

This should be a simple question, but I haven't been able to find a way to make it work.

Essentially, I have a silly localhost page that I use in my webdevelopment. When I am surfing between our development server and my local version of the C# code (redirected from the dev url via host file) I have been known to sometimes forget what 'dev.foo.com' points at - local or server.

So I created a page which will run locally as my default web page's default page, so I can easily identify my localhost from the server.

This page does a lot of things randomly (including generating a character's starting stats for D&D), including setting a random background color. I do this by generating 3 random numbers between 0 and 255, and setting them as the RGB value for the body background color in CSS.

Given the 3 ints R, G, and B, how do I determine R2, G2, and B2 such that the second color will have high contrast with the first? I like having the page have random background colors (it keeps me from getting used to the look of the landing page) but I also like to be able to read the text.

Andrew Eisenberg
  • 28,387
  • 9
  • 92
  • 148
Jeff
  • 2,835
  • 3
  • 41
  • 69

11 Answers11

28

You need a difference in brightness for text to be readable, as color vision itself has too low resolution.

So as an algorithm I'd suggest the following:

  • Pick a random background color.

  • Then decide whether it is a light or a dark color. For example you could check whether the average of the three primary colors is greater or equal 128.

  • For a light color use black text, for a dark one white text.

Update: Here is an example image I made while playing with the split_evenly example of the Rust crate plotters. It shows the colors in Palette99:

7x3 colored fields with their index in either black or white depending on the background's brightness

starblue
  • 55,348
  • 14
  • 97
  • 151
  • This is a fairly simple solution, and one that (now) is working fairly well. I'm going to try this for a while, as it IS always readable, if a bit plain. – Jeff Jan 02 '09 at 19:57
8

"Contrast" is a loaded word. If you just care about being able to read the text, then one easy way is to work in a luminance-based color space like HSL, and pick foreground and background colors with big differences in luminance.

The conversion between HSL and RGB is well-known--see Wikipedia for the details.

If you're talking about actual color contrast, it's not nearly as cut-and-dried (there are a lot of perceptual factors that, as far as I know, haven't been reduced to a single colors space), but I suspect you don't need that level of sophistication.

Tim Lesher
  • 6,341
  • 2
  • 28
  • 42
  • 1
    there is a class that deals with HSL conversions that can be found here http://richnewman.wordpress.com/2007/05/07/using-hsl-color-hue-saturation-luminosity-to-create-better-looking-guis-part-2/ – KevB Jan 02 '09 at 20:11
5

Check out this PHP solution: Calculating Color Contrast with PHP by Andreas Gohr. It can be ported to any language of course.

He also has a very nice demonstration of his contrast analyzer where you can find some minimal contrast levels to work with.

GEOCHET
  • 21,119
  • 15
  • 74
  • 98
markus
  • 40,136
  • 23
  • 97
  • 142
5

You can use method GetBrightness() on Color class. It returns a float value from 0.0 (brightness of black) to 1.0 (white). A simple solution would be:

var color1 = new Color.FromArgb(r1, g1, b1);
var brightness = color1.GetBrightness();

var color2 = brightness > 0.5 ? Color.Black : Color.White;
Marko Juvančič
  • 5,792
  • 1
  • 25
  • 41
3

I did something like this in a Palm OS application. This is what I came up with. It doesn't give you "high contrast" colors but it gives you a background color that's different enough from the text color to be quite readable:

  // Black background is a special case.  It's fairly likely to occur and 
  // the default color shift we do isn't very noticeable with very dark colors.
  if (backColor.r < 0x20 && backColor.g < 0x20 && backColor.b < 0x20)
  {
      textColor.r = backColor.r + 0x20;
      textColor.g = backColor.g + 0x20;
      textColor.b = backColor.b + 0x20;
  }
  else
  {
      textColor.r = backColor.r + ((backColor.r < 128) ? 0x10 : -0x10);
      textColor.g = backColor.g + ((backColor.g < 128) ? 0x10 : -0x10);
      textColor.b = backColor.b + ((backColor.b < 128) ? 0x10 : -0x10);
  }

You might not need to do black as a special case for your purposes - Palm's color handling is a bit funky (16-bit color).

mhenry1384
  • 7,538
  • 5
  • 55
  • 74
3

These answers are more or less suggesting to use one of the two or three color choices based on whether the color is bright or dark.

I use a bit different approach and it worked elegantly in my case. Here is the implementation.

 int color = your_color;
 contrastColor = Color.rgb(255-(color >> 16)&0xFF, 255-(color >> 8)&0xFF, 255- color&0xFF);

It's simple and wonderful.

Mayank Kumar Chaudhari
  • 16,027
  • 10
  • 55
  • 122
2

If you flip all the bits, you will get the "opposite" color which would be pretty good contrast.

I believe it's the ~ operator in C#:

R2 = ~R1;
G2 = ~G1;
B2 = ~B1;
bobwienholt
  • 17,420
  • 3
  • 40
  • 48
  • 6
    That doesn't work well for colors near the "middle" of the color histogram, like #808080. – Tim Lesher Jan 02 '09 at 19:35
  • That's true... but opposite color's have the added benefit of looking pleasing to the eye when put next to each other. – bobwienholt Jan 02 '09 at 19:42
  • 11
    Tim, you can go ahead and phrase it a bit stronger - it doesn't work at all. :-) – mhenry1384 Jan 02 '09 at 19:43
  • 1
    You could add some "if" statement for those "middle" cases where it doesn't work correctly. You could also have something like "if(light) return Black; else return White;" but that'd obviously reduce the ammount of output colors. – luiscubal Jan 02 '09 at 19:48
  • Maybe I should have pointed out that this is an ASP site, but your comment was still helpful. My only problem with it would be those 'edge' cases in the center, and the question that ensues of 'how close to the center before switching to black & white?' – Jeff Jan 02 '09 at 19:55
1

Thanks to @starblue !

Here is C# code that I use

 public static string GetContrastBlackOrWhiteColorAsHtmlColorCode(Color c)
        {
            System.Drawing.Color color = System.Drawing.ColorTranslator.FromHtml("transparent");

            try
            {
                if (c.R >= 128 && c.G >= 128 && c.B >= 128)
                {
                    return System.Drawing.ColorTranslator.ToHtml(Color.Black);
                }
                else
                {
                    return System.Drawing.ColorTranslator.ToHtml(Color.White);
                }
            }
            catch (Exception)
            {
            }

            return System.Drawing.ColorTranslator.ToHtml(color);
        }
NoWar
  • 36,338
  • 80
  • 323
  • 498
  • 2
    This isn't quite what StarBlue had said - he suggested averaging the R,G, and B values. You are setting minimum thresholds. Your code will use white even at [#FF7FFF](http://www.colorhexa.com/7fffff), [#7FFFFF](http://www.colorhexa.com/ff7fff), and [#FFFF7F](http://www.colorhexa.com/ffff7f) – Jeff Jul 16 '14 at 12:59
0

For best contrast use this code

function lumdiff($R1,$G1,$B1,$R2,$G2,$B2){

    $L1 = 0.2126 * pow($R1/255, 2.2) +
          0.7152 * pow($G1/255, 2.2) +
          0.0722 * pow($B1/255, 2.2);

    $L2 = 0.2126 * pow($R2/255, 2.2) +
          0.7152 * pow($G2/255, 2.2) +
          0.0722 * pow($B2/255, 2.2);

    if($L1 > $L2){
        return ($L1+0.05) / ($L2+0.05);
    }else{
        return ($L2+0.05) / ($L1+0.05);
    }
}

function get_the_contrast($c1, $c2) {
    return (lumdiff(hexdec(substr($c1,0,2)),
        hexdec(substr($c1,2,2)),hexdec(substr($c1,4,2)),
        hexdec(substr($c2,0,2)),hexdec(substr($c2,2,2)),
        hexdec(substr($c2,4,2))));
}

The method above ( AVG(red,green,blue) > 128 ) is not realy good.

0
private Color GetContrastingColor(Color color)
{
    int r = color.R > 0 ? 256 - color.R : 255;
    int g = color.G > 0 ? 256 - color.G : 255;
    int b = color.B > 0 ? 256 - color.B : 255;
    return System.Drawing.Color.FromArgb(r, g, b);
}
jco
  • 11
  • 1
0

Grayscale conversion is better done with special coefficients for calculation, for example Gray = (Red * 0.3 + Green * 0.59 + Blue * 0.11). More details can be found here. Then you need to compare the value with 128 and choose white or black, as shown in the first comment.

DYMATEJlb
  • 29
  • 2