--- title: Debunking claims about contrast formulas date: 2022-09-10 tags: [a11y, math, color] description: 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 want to comment on the [color.js documentation][1] 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. - **simple contrast**: `Ymax / Ymin` The authors do not have anything nice to say about this one: > This definition is not useful for real-world luminances, because of their > much higher dynamic range, and the logarithmic response characteristics > of the human eye. - **Weber Contrast**: `(Ymax - Ymin) / Ymin` The authors provide a lot of background information on this one, but fail to mention one crucial point: This again is simple contrast, just with an offset of 1: (Ymax - Ymin) / Ymin = Ymax / Ymin - Ymin / Ymin = Ymax / Ymin - 1 - **Michelson Contrast**: `(Ymax - Ymin) / (Ymax + Ymin)` Again they fail to mention a crucial detail: This is a Weber Contrast that compares `Ymax` and the average of `Ymax` and `Ymin`: (Ymax - Ymin) / (Ymax + Ymin) = (Ymax + Ymax - Ymax - Ymin) / (Ymax + Ymin) = (2 * Ymax - (Ymax + Ymin)) / (Ymax + Ymin) = (Ymax - (Ymax + Ymin) / 2) / ((Ymax + Ymin) / 2) = (Ymax - Yavg) / Yavg - **WCAG 2.1**: `(Ymax + 0.05) / (Ymin + 0.05)` The authors correctly observe: > corresponds to Simple Contrast with a fixed 0.05 relative luminance > increase to both colors to account for viewing flare In other words: This *is* simple contrast. But instead of only considering the light that is produced by the color itself, it also factors in the light that is contributed by the viewing conditions. 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][2]: 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][3]. ## 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"][4] is an absurd exaggeration. **Update**: I proposed some [changes to the color.js documentation][5], but they were rejected. [1]: https://github.com/LeaVerou/color.js/blob/bdf68c7a1242f497bfae7e1179363eefaaaf9b70/docs/contrast.md [2]: https://en.wikipedia.org/wiki/Weber-fechner [3]: https://github.com/xi/apca-introduction/blob/main/analysis.md [4]: https://twitter.com/MyndexResearch/status/1527313470575628289 [5]: https://github.com/LeaVerou/color.js/pull/218