Introduction
Lately, I keep running into situations where I wanted to find out the contrast ratio between two colors. But I didn't have a utility to do it quickly, so I kept ending up at the WebAIM color contrast checker. Which is a nice service, but I wanted to go to my terminal and type in something like color-contrast --foreground #123abc --background #ffffff
and get the same (or similar) details.
One way to do this would be to query the WebAIM color contrast API. But, I wanted to learn about how contrasts were calculated. So I decided to do a little math. By the end, I was able to come up with this command line utility to help out.
Math
I'm not very good at math. I'd like to think I'm good at logic and problem solving though! So first things first, I needed to find the color contrast formula that WebAIM uses. This formula can be found in the W3C documentation. Sort of.
The formula they give is
(L1 + 0.05) / (L2 + 0.05)
L1
and L2
are relative luminance values. And this is where I got lost for a while.
Relative Luminance
It took me a while, but I finally figured out that if you read the documentation from bottom to top it makes more sense. So let's do that!
What we need to find is the standard RGB (sRGB) values for each of the red, green, and blue channels. In the docs, that's represented by RsRGB = R8bit / 255
for the red channel. So let's say, that you have an rgb color of rgb(123, 4, 255)
. The sRGB values would be
RsRGB = 123 / 255 ~= 0.4823
GsRGB = 4 / 255 ~= 0.0156
BsRGB = 255 / 255 = 1
Each of those values then needs to be evaluated in the following way. If the value is less than or equal to 0.03928 (I'm not making that up), then it should be value / 12.92
otherwise, it will be ((value + 0.055) / 1.055)^2.4
.
in javsacripty pseudo-code we could do something like
if (value <= 0.03928) {
result = value / 12.92
} else {
base = (value + 0.055) / 1.055
result = Math.pow(base, 2.4)
}
if we put our rgb values through this we get
R ~= 0.1980
G~= 0.0012
B = 1
You might think we're done now, but nope! We still need to calculate the actual luminance from this according to the formula given in the documentation
L = 0.2126 * R + 0.7152 * G + 0.0722 * B
So in our case, we end up with an L for this rgb color of about 0.1151.
Next, we need to do that same thing for another color.
Lighter and Darker
Let's say we calculated the value for rgb(255,255,255)
aka white using the same technique and arrived at the conclusion that L = 1
for that one.
Now we have our two colors. The lighter color will always be L1
and the darker color will always be L2
Again in pseudo-code
let color1
let color2
if (L1 >= L2) {
color1 = L1
color2 = L2
} else {
color1 = L2
color2 = L1
}
We then add 0.05 to each color and from here you can calculate the contrast as
contrast = (color1 + 0.05) / (color2 + 0.05)
Which in this particular case is equal to about 6.3957!
Conclusion
This was a fun project to work on. I might do another post about exactly how I built it. For now though I wanted to write up the steps I took to solve the math behind the project.