39
votes

Something like calculating the average value of rgb components and then decide whether to use black or white?

Do I have to convert RGB to HSV in first step 'cause RGB is not always what the human eyes see?

I'm using C#

5
This question contains answers which will assist you: stackoverflow.com/questions/946544/…Michael Greene

5 Answers

75
votes

It just so happens I needed this function for a project not long ago.

private int PerceivedBrightness(Color c)
{
    return (int)Math.Sqrt(
    c.R * c.R * .241 +
    c.G * c.G * .691 +
    c.B * c.B * .068);
}

This formula I found on the web at Nbd Tech that dealt with perceived colors and color conversion formula. The site gives a lot of information that is helpful.

Here's how to use this to select black or white:

var foreColor = (PerceivedBrightness(backColor) > 130 ? Color.Black : Color.White);

You can use a value other than 130 as the cutoff; it is preference.


Update: According to Darel Rex Finley at his site:

The values I came up with by playing with Photoshop were actually .241, .691, and .068, but I have since been informed that the values .299, .587, and .114 are more accurate.

This specification follows ITU-R Recommendation BT.601 (or Rec. 601 for short). The site I mentioned above, Nbd Tech, hasn't yet been updated to reflect this.

Based on this, here is the updated method (thanks to DTI-Matt for the comment):

private int PerceivedBrightness(Color c)
{
    return (int)Math.Sqrt(
    c.R * c.R * .299 +
    c.G * c.G * .587 +
    c.B * c.B * .114);
}

Note on threshold preference:

Colors with a perceived brightness near the middle (e.g. 120-140) will be more subjective. For example, it's debatable whether red (FF0000), which evaluates to 139, is clearer with a black or white overlay.

White and Black on Red

7
votes

what about that?

private static Color GetReadableForeColor(Color c)
{
    return (((c.R + c.B + c.G) / 3) > 128) ? Color.Black : Color.White;
}
6
votes

The Color struct supports conversion to HSB natively.

if (Color.GetBrightness() > 0.5f) {
  // win
}

You may want to add a component of saturation as well, considering saturation also contributes to apparent 'lightness'.

0
votes

You could do a simple calculation depending on color depth, if say you had a #FFFFFF color format, you could just add the RGB values and calculate if they're under half way.

The max in that case is 255 (F x F = 256) per, so just check if it's under that threshold:

var colorCode = ((R + B + G) < 255*3) ? "#FFFFFF" : "#000000";

If the color's below, it's darker...use a white background. If it's above, it's lighter...use a black background. It's not perfect, but an idea to get started.

-1
votes

If I'm understanding correctly, one approach might be to get hold of the desktop wallpaper image, check in some manor what colour it is and then change your application colour based on that.

There's an article on geekpedia about getting hold of the current desktop wallpaper (and lots of hits on google on that), but the basic premise is to grab the registry value of the current wallpaper:

RegistryKey rkWallPaper = Registry.CurrentUser.OpenSubKey("Control Panel\\Desktop", false);
string WallpaperPath = rkWallPaper.GetValue("WallPaper").ToString();

You could then use that path to open the image. You can then get hold of lots of properties, such as the dimensions and individual pixel colours in RGB.

To work out whether it's "more white" or "more black" you have many options.

One idea would be to get each pixel colour in RGB, average the values (to get the greyscale value) and then average the greyscale value of each pixel across the image. If this comes out > 128 then it could be condidered to be "white". If < 128 then "black". Basically you are deciding which side of the mid-grey dividing line the images pixel intensities average out to.

// Psudo code - can't check the C# spec atm.
foreach(Pixel p in image)
{
    // get average of colour components.
    double greyScale = (p.Red + p.Green + p.Blue) / 3;
    totalIntensity += greyScale;
}

double averageImageIntensity = totalIntensity / image.NumPixels;

if(totalIntensity > 128) // image could be considered to be "white"
else // image could be considered "black".

Problems: could be a slow procedure, you might want to sample only some of the pixels (say, every 10th one etc.) to save time. More generally, it seems like a fairly hacky thing to be doing at all. Pulling user files at run-time and messing with them is hardly clean, and it provides potential security and stability concerns. What if the image is duff or corrupt etc.

Personally I'd suggest simply giving the user the choice of colour / theme / skin themselves, or better yet let them customise it!