Ξ

Debunking claims about contrast formulas

Published on 2022-09-10 a11y math color

I want to comment on the color.js documentation on contrast. It lists a bunch of contrast formulas and explains some of their properties. I believe these explanations contain some misleading statements.

I don’t want to pick on the color.js authors specifically. They are generally doing an awesome job. This documentation just happens to aggregate many of the claims that can be found throughout the internet.

The many faces of Weber contrast

Let’s start with some fractions.

Admittedly, these formulas are not exactly the same. But I cannot see how you can say that simple contrast is unfit for real-world use and not say something similar about the others.

Equivalence of contrast formulas

I want to try to formalize this notion of “similarity” between different formulas.

Usually, a contrast formula is used to decide whether the contrast is above a certain threshold value. So if we have two contrast formulas f() and g() together with two thresholds, we can call them equivalent if they give the same results.

Mathematically speaking, we want the following to hold true for all color pairs (a, b) and (c, d):

f(a, b) < f(c, d) => g(a, b) < g(c, d)

In other words, two contrast formulas are equivalent if there is a strictly monotonic map between them. I call such a map a “scaling”.

For example, simple contrast and Weber contrast are equivalent according to this definition because scale(x) = x - 1 is strictly monotonic.

You could also have a stricter definition and require contrast formulas to be perceptually uniform in the sense that a doubling in the perceived contrast also results in a doubling of the contrast value. In that case, the scaling function would have to be linear and Weber would not be equivalent to Simple contrast. I do think this stricter definition has merit in some cases, but I will work with the weaker definition in this post.

Lightness difference

The authors also mention lightness difference Lmax - Lmin as a way to calculate contrast:

Instead of being based on luminance, which is not perceptually uniform (and thus, the visual difference corresponding to a given luminance difference is greater for a dark pair of colors than a light pair), lightness difference uses the CIE Lightness L*, which is (approximately) perceptually uniform.

Using the CIELab definition of lightness is one option, but there are others. For example, there is the Weber-Fechner law:

L = a * log(Y) + b

So what happens if we build a lightness difference based on that definition?

(a * log(Ymax) + b) - (a * log(Ymin) + b)
= a * (log(Ymax) - log(Ymin))
= a * log(Ymax / Ymin)

This turns out to be a scaled version of simple contrast. Even if you consider Weber-Fechner to be outdated, this is still a perceptual model. So both the claims that the other contrast algorithms are not based on perceptually uniform lightness difference and that simple contrast does not consider the logarithmic response characteristics of the human eye are bogus.

APCA

The final contrast formula mentioned in the color.js documentation is APCA. If you are interested in that I can refer you to my detailed analysis.

Conclusion

The main takeaway is that simple contrast (and therefore WCAG 2.1) is not as bad as some people make it sound. It is based on a difference of perceptual lightness. Yes, there are better formulas available and the choice of parameters can be criticized, but claiming that it is the “flat earth of accessibility” is an absurd exaggeration.

Update: I proposed some changes to the color.js documentation, but they were rejected.