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.