Steps on the Way. Getting There.
A formula for determining whether text of a given colour or shade is readable over a background of a different colour or shade.
I have been puzzling over this topic for some years. Only in January 2016 did I begin to see a pattern that fitted in with general theories about screen colours, with thanks to Elliot Mebane who pointed something out to me in January 2016, that led me to begin to put two and two together and make 3.99999.
I am not a mathematician, I am essentially an artist, and also a computer programer, hence much of what I have discovered about this has been through trial and error. Here are the results of my current researches.
First of all, a computer monitor (and any type of display screen) does not get brighter linearly, the distance between two brightnesses of a given hue will be relatively greater, the brighter the colours are. For a knowledgeable explanation of this, see Frequently Asked Questions about Gamma by Charles Poynton.
This means that any formula that gives constants as factors to multiply out by, where you multiply the red, green and blue elements of the RGB triad each by a number to get a brightness value, e.g. formulas such as those in the W3C Guidelines, won’t work when you are wanting to compare two brightnesses. They can’t, and they don’t.
Photoshop Seems to Get it Right at First Sight
Photoshop gets it right. Or does it? Those greyscales you see in the examples below look right. So what formula does Photoshop use?
Here is a block produced in Photoshop showing red, green and blue at values 255, 192, 128 and 64:
Now we convert that to greyscale and see what grey values Photoshop gives us.
That looks pretty good perceptually. It goes like this:
I don’t know for sure what formula Photoshop uses as I’m not privy to Photoshop’s internal code, but I do know of a formula that gives exactly the same greyscale results as Photoshop, and I know of another that is simpler and more rough-and-ready and which gives results that are pretty close to Photoshop’s. (Tested against Photoshop 5.5 thru CC, the greyscale conversion seems to have stayed consistent across the versions).
As with all the various formulas for obtaining a greyscale, the like-Photoshop formula uses percentage values to multiply the red (R), green (G) and blue (B) numeric levels by. (see Puzzling Greys). The like-Photoshop formula uses R=0.2235, G=0.7154, B=0.0611 to get a result equivalent to what you get with Photoshop.
I say in the title to this section that Photoshop seems to get it right at first sight. It does so far as the examples on the left show, but as I explain below Photoshop gives a linear result for greyscale, which is not perfect for comparing the brightness levels of two shades of grey. Read on . . .
So, to the formulas:
The like-Photoshop Formula
You get a brightness (i.e. greyscale) value by calling:
brightness_value = gamma_grey(R, G, B, 0.2235, 0.7154, 0.0611);
Where R, G and B are the colour values of red, green and blue, of course.
You’ll find that gives you a brightness value exactly equivalent to Photoshop’s (well I do).
What the Formula is Doing
The formulas use a classic linear to sRGB and sRGB to linear conversion. Mathematically that can be seen on the page A close look at the sRGB formula and in structural code on Optimizing conversion between sRGB and linear.
The gamma_grey function passes the colour value, R, G or B, though the inverse gamma sRGB inv_gam_sRGB and then multiplies the result by the R, G, or B constant factor. It then adds the R, G and B results from inv_gam_sRGB together and passes the result though the gamma sRGB gam_sRGB function to get a greyscale.
inv_gam_sRGB will always give a result from 0 to 1. This fraction is then multiplied by the factor for each of the R, G and B values.
Although these are gamma functions, the resulting greyscale will in effect be linear, that is because the colour values go through both gam_sRGB and inv_gam_sRGB, which has a cancelling-out outcome, see my page Colour Brightness Experiment which shows that, without further adjustment, a brightness range for a given colour triad is linear.
Values 0-255 calculated as sRGB give a curve, which is said to be very useful for adjusting colours on photographs, but it is a curve that it inverse to what we need when comparing two brightnesses. The sRGB curve with an exponent of 2.4 looks like this:
With this curve the brightnesses get less the higher the colour value, not more, so we need the inverse of this:
That is the chart for 0-255 through the inv_gam_sRGB formula.
Similar but Rougher
You can get almost identical results, slightly further out in the low values values, by forgetting all about complex gamma calculations and using something much simpler.
My Rough-and-Ready Formula
brightness_value = (0.22475*R^2.235 + 0.7154*G^2.235 + 0.05575*B^2.235)^(1/2.235)
Thanks to Roland Miyamoto for pointing out to me the simplicity of this formula.
BUT . . .
The gamma calculation that gives a result identical to what you get with Photoshop still gives a brightness R where R = G = B. Although the formula uses exponents, the brightness results come out linear as I explain above. To get a perceptual relative brightness we need a curved graph along the lines of the inverse gamma sRGB curve as shown above, see my page Colour Brightness Experiment where the formulas can be seen in practice and in graphic form and can be experimented with as desired.
The brightness weighted option on my page Colour Brightness Experiment takes the greyscale values as calculated, from whatever method, and runs them through the inv_gam_sRGB function to get a curved graph on output for the relative brightness of grey.
Now as I said above, I am not a mathematician, so there may be something I am missing here, but from where I am standing at the moment it seems that unless you get a curved graph of the inverse gamma type for relative levels of brightness, you will never be able to judge mathematically, whether a brightness of level A is going to be readable over a brightness of level B, for the reasons I touched on at the beginning of this long page.
brightness_value=R * 0.51278 + G * 0.86304 + B * 0.27481
Try it. It comes out nigh-on the same as the gamma!
Notice that those three constants add up not to 1 but to 1.6506, which will be why on my page Colour Brightness Experiment the gamma and rough-n-ready calculation give a curved graph rather than a straight line, I think – there must be some reason why they do and it is not because the distance between any two colour values for a single colour are unequal.
Note: I updated these formulas on 28 February 2016, before that date they were on their way but more complex. The comments to this page that you see below were all extremely helpful in getting to where I am now and I must thank all those who took the time to make them.
A Brightness-Weighted Formula
We need a formula which produces a result where the distance between two brighter pixels is greater than the distance between two darker ones. A formula based on exponents then. And seeing as how Photoshop seems to be about right with its values for 255 red, 255 green and 255 blue, at 130, 220 and 70, we’ll have a formula that gives those values for 255, and of course 0 for 0.
The formula I am currently working with is this:
brightness_value = (R * r)^e + (G * g)^e + (B * b)^e
where r=(130^1/e) / 255 g=(220^1/e) / 255 and b=(70^1/e) / 255
e can be any number, each number giving a different gap between the pixel brightness values, I’m currently using the same exponent as in the like-Photoshop gamma formula, of 2.4
I am working on getting this formula incorporated in all my demonstration pages, pages such as Colour Brightness Experiment, which with luck might already be updated by the time you come to look at it.
The code will produce a relative brightness level as a positive or negative integer. It does this by obtaining a brightness level for each of the foreground and background colours and subtracting one from the other, and then adjusting the result for type size. This SEEMS TO WORK in that it gives a brightness difference across the whole spectrum that is usable.
As a rule of thumb, I would say that for 10pt text results that are outside of a range of -80 to 80 are moderately readable. This range should be adjusted for different type sizes as explained on my page Readability of Type in Colour – Effect of Font Size Though to each his own wicked taste, naturally.
• the foreground and background colours expressed in web notation, ie a 6-digit hexadecimal number beginning with # (eg #000000 for black). The function assumes you have validated that these numbers are correctly structured.
You can experiment on Colour Text Legibility.