# Re: Color contrast between semi-transparent colors

From: Lea Verou <lea@w3.org>
Date: Sun, 14 Oct 2012 07:17:32 +0300
Cc: w3c-wai-ig@w3.org, Chris Lilley <chris@w3.org>
Message-Id: <7BEEEDDE-3BCA-4148-8C06-5A0F4AEA612F@w3.org>
```I realized that all of my previous assumptions were incorrect and would not accurately calculate an upper or lower bound, so I decided to give up on trying to calculate this intuitively, and used a bit of maths instead. The following is a write-up of my conclusions. I'd appreciate it if anyone could check it for logical leaps or mathematical mistakes, since I'm very rusty on this kind of thing. I can understand that typing formulas in plain text (even Unicode) can be confusing, so if it makes things easier, I can move them to a PDF or HTML with MathML.

== The Problem ==

Calculating the contrast ratio *range* that a semi-transparent background color can produce with an opaque text color.

== Definitions ==

Backdrop color: The color that the semi-transparent background color is going to be overlaid on. The problem assumes we know both the text color and the background color, so the only variable is the backdrop color. We also assume that the whole thing is going to be overlaid on a solid color, since variant color backgrounds have different contrast demands.

== Alpha blending ==

When a semi-transparent color that described by rgba(R₁, G₁, B₁, A₁) is overlaid on a solid color rgb(R₂, G₂, B₂), the resulting solid color of the alpha blending operation will be rgb(R₁A₁ + R₂(1-A₁), G₁A₁ + G₂(1-A₁), B₁A₁ + B₂(1-A₁)) 
We will use that in all the following sections.

== Future work ==

In the following, I will assume that the text color is opaque, for simplicity and because it's the most common case. If it isn't, we can use the alpha blending formula above to calculate the resulting opaque text color for the backdrop colors that produce the highest and lowest contrast ratios. This should produce a reasonable estimation of the range, although properly calculating the range when the text color is translucent seems a bit more complicated as it would change many of the following formulas. I can work on that in the future, if my reasoning here turns out to be correct.

== Upper contrast ratio bound ==

We want to maximize the ratio between the relative luminances. The luminance of the text color is known, so we are trying to maximize or minimize the luminance of the resulting background color (depends on whether it's going to be the numerator or the denominator of the fraction).

Observing the relative luminance formulas , it’s obvious that L(R, G, B) is strictly increasing for each of R, G, B, since they are linear with positive coefficients. Each of R, G, B is also strictly increasing for R_sRGB, G_sRGB, B_sRGB < .3928 for the same reason. It turns out it’s also strictly increasing for values > .3928, since each of these functions has a first derivative that is > 0 for the range we are interested in.  Moreover, the function is continuous, as both its legs give the same value for 0.03928, so it's continuous and strictly increasing in its entirety.

Consequently, to maximize relative luminance we need to maximize the values of R, G and B of the resulting background color. Similarly, to minimize it we need to use the minimum possible value for all three of them. Since the alpha blending formula also consists of strictly increasing functions (for R₂, G₂, B₂), the resulting color with the maximum luminosity is rgb(R₁A₁ + (1-A₁), G₁A₁ + (1-A₁), B₁A₁ + (1-A₁)) which we get for R₂ = G₂ = B₂ = 1 (white).
Similarly, the color with the lowest luminosity is rgb(R₁A₁, G₁A₁, B₁A₁) which we get for R₂ = G₂ = B₂ = 0 (black).

Since we don’t know whether the maximum contrast is produced by maximizing or minimizing the luminance of the background (it depends on the luminance of the text color), we can get the maximum contrast by calculating both contrast ratios and taking the maximum of the two. Therefore, the algorithm for calculating the maximum contrast ratio is:

- Calculate the results of alpha blending of the background color a) on white and b) on black.
- Calculate the contrast ratio of these two colors with the text color, using the algorithm described in WCAG 2.0, since all of these are opaque.
- The maximum of the two contrast ratios is the maximum contrast ratio.

== Lower contrast ratio bound ==

We want to minimize the ratio between the relative luminances of the text color and the blended background color. Therefore, we want to minimize the absolute value of the difference between the text color and the resulting background color. Obviously, if there is a backdrop color that equates the two, this is the one that results in the minimum contrast ratio:

R₀ = R₁A₁ + R₂(1-A₁)  => R₂ = (R₀ - R₁A₁) / (1-A₁)
G₀ = G₁A₁ + G₂(1-A₁)  => G₂ = (G₀ - G₁A₁) / (1-A₁)
B₀ = B₁A₁ + B₂(1-A₁)  => B₂ = (B₀ - B₁A₁) / (1-A₁)

if the resulting R₂, G₂, B₂ values are out of the [0, 255] range, this means there is no sRGB color that produces a contrast ratio of 1. In that case, to get the closest color, we apply the following, effectively clipping these values to the allowed [0, 255] range:

R₂ = max(0, min(R₂, 255))
G₂ = max(0, min(G₂, 255))
B₂ = max(0, min(B₂, 255))

Since these will have no effect when R₂, G₂, B₂ are within [0, 255], we can safely say that the backdrop color which produces the minimum contrast is:

R₂ = max(0, min((R₀ - R₁A₁) / (1-A₁), 255))
G₂ = max(0, min((G₀ - R₁A₁) / (1-A₁), 255))
B₂ = max(0, min((B₀ - R₁A₁) / (1-A₁), 255))

== Testing ==

I've made a contrast ratio tool which uses these algorithms . You can use it to verify them in practice. Only works in modern browsers (no IE8, minimal functionality in IE9). So far, everything I've tried seems to produce reasonable results (even when at first glance they didn’t seem correct, so please double-check if you think you found a mistake).

: http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
: http://www.w3.org/TR/WCAG/#relativeluminancedef
: 2.4 * (x + .055)^1.4 / 1.055^2.4, where x is each of R_sRGB, G_sRGB, B_sRGB respectively.
: http://leaverou.github.com/contrast-ratio

On Sep 6, 2012, at 13:33, Lea Verou wrote:

> Actually, the algorithm I described seems correct to determine a lower bound. However, when it comes to the upper bound, I found an error in my reasoning: I assumed the color with the maximum contrast for a given color A is the inverse of A. This seems correct for colors close to the edges of the RGB cube, but becomes more and more inaccurate as we move towards the center of the cube. It’s especially incorrect for gray, as the inverse of gray is …gray.
>
> Another possible way to calculate this (that I need to test more) is that the color with the maximum (linear) distance in the RGB cube could be the one that produces the maximum contrast. If that fails too, there are strictly defined mathematical algorithms to find the maximum(s) of any function, which could be applied to the contrast ratio function. I'm quite rusty on multivariable calculus, but I could give it a shot.
>
> I haven't given up on this, and am still looking into it. Any help would be immensely appreciated. I've tried googling for algorithms about maximizing color contrast for a given color, but can't seem to find anything. All I could find was tutorials for designers about when to use white or black text on certain backgrounds or research papers about monitor display contrast :( There must be prior research on this, I'm probably just too useless at searching to find it.
>
>
> Lea Verou
> W3C developer relations
> http://w3.org/people/all#lea ✿ http://lea.verou.me ✿ @leaverou
>
>
>
>
>
>
> On Sep 6, 2012, at 11:38, Shadi Abou-Zahra wrote:
>
>> Hi Lea,
>>
>> Sounds very conclusive. Interesting.
>>
>> You say you've tested this with color combinations other than white text on a semi-transparent black background and it seems to work?
>>
>> If so then it seems apt to submit to WCAG WG as a proposed technique:
>> - <http://www.w3.org/WAI/GL/WCAG20/TECHS-SUBMIT/>
>>
>> Best,
>>
>>
>> On 5.9.2012 16:24, Lea Verou wrote:
>>> I just had a brainwave and I think I've figured it out! The extremes, i.e. the backgrounds that produce the minimum and maximum contrast are not fixed. They depend on the foreground color (text color). The background* that produces the minimum contrast if combined with the semi transparent background color is the text color, since this is as close as you can get to it. Similarly, the background that produces the maximum contrast is the inverse of the text color. Therefore, the contrast ratio range, is the contrast ratios produced by these two colors.
>>>
>>> Allow me to illustrate with a (quite common in modern web design) example:
>>> Text color: white (#FFFFFF)
>>> Background color: rgba(0, 0, 0, .5) (50% semi-transparent black)
>>>
>>> The inverse of white is black.
>>> rgba(0, 0, 0, .5) on white produces gray (#808080). The contrast ratio of gray and white is 4.
>>> rgba(0, 0, 0, .5)  on black produces black (#000000). The contrast ratio of white and black is 21.
>>>
>>> Therefore, the contrast ratio range of white text on rgba(0, 0, 0, .5) is 4 - 21. In other words, the contrast ratio of these two colors is 12.5±8.5. This tells us that we can use this for large text (if AA is sufficient), but we should be wary of using it for body text, since it can go below the 4.5 threshold. Increasing the transparency to .55 passes AA for any size text.
>>>
>>> I've tested this algorithm with a number of different colors and it seems to produce accurate results. Please let me know if you see an error in my reasoning, or my math.
>>>
>>> If I'm correct, I think this should be added in WCAG. Authors use semi-transparent colors all the time in modern web design, and it would immensely help them to be able to determine whether their backgrounds might produce illegible text.
>>>
>>> * I'm using the word "background" for two separate things: The (semi-transparent) background color and what's going to be behind it. I hope the meaning is clear through the context.
>>>
>>> Lea Verou
>>> W3C developer relations
>>> http://w3.org/people/all#lea ✿ http://lea.verou.me ✿ @leaverou
>>>
>>>
>>>
>>>
>>>
>>>
>>> On Sep 4, 2012, at 23:48, Lea Verou wrote:
>>>
>>>> Hi all,
>>>>
>>>> I was studying the color contrast section of WCAG 2.0  and I observed that the algorithm presented assumes opaque colors. In the case of an opaque background, it's easy to calculate the color contrast using the same algorithm, even when the foreground is translucent. However, when the background is semi-transparent (for example, CSS rgba() or hsla() colors) and the colors it’s going to be on are unknown, there is no single contrast ratio, but a range of potential contrast ratios. How would somebody calculate this range? It’s obviously impractical to calculate the ratios for every possible color that might be underneath, these could be in the millions.
>>>>
>>>> I tried calculating the min and max ratios by using white and black, but it did not yield accurate results. Consider the example of gray text on hsla(0,0%,50%,0.5) background (gray with 50% transparency). On white, this is gives us gray on hsl(0,0%,75%) which has a ratio of 2.1:1. On black, it gives us gray on hsl(0,0%,25%) which has a ratio of 2.7:1. However, this doesn’t mean that every potential color will yield a ratio between 2.1 and 2.7: If we have gray underneath, the resulting ratio is 1:1.
>>>>
>>>> My question is: Which (and how many) colors should be sampled to get an accurate minimum and maximum contrast ratio?
>>>>
>>>> : http://www.w3.org/TR/WCAG/#visual-audio-contrast-contrast
>>>>
>>>> Lea Verou
>>>> W3C developer relations
>>>> http://w3.org/people/all#lea ✿ http://lea.verou.me ✿ @leaverou
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>
>>>
>>
>> --