Contrast preserving color inversion

Jan 28, 2024

A new approach to color inversion that preserves contrast ratios between colors.

Introduction

Color inversion is a common accessibility feature that inverts the colors of a display, often making it easier to read in a dark environment, for example, by turning white text on a black background into black text on a white background.

In this post, I explore conventional approaches to color inversion and propose a new approach that preserves both hue and contrast ratios between colors.

Existing approaches

Two common approaches to color inversion are RGB inversion and lightness inversion.

RGB inversion

The simplest approach to color inversion is to simply invert each channel of an RGB color.

In this post, we’re working in sRGB color, which is the standard space for representing colors on the web. Note that as a convention, I will refer to all color components as ranging from 0 to 1, even though they are typically represented as integers from 0 to 255—this is called “8-bit color” because each component is represented with 8 bits.

RGB inversion is straightforward: each channel is simply inverted independently.

Rinverted=1RGinverted=1GBinverted=1B\begin{align*} R_{\text{inverted}} &= 1 - R \\ G_{\text{inverted}} &= 1 - G \\ B_{\text{inverted}} &= 1 - B \end{align*}

While this type of inversion is easy to implement and very efficient, it comes with a large usability drawback: hues are rotated by 180, which makes it difficult to recognize colors.

Use the widget below to see how RGB inversion affects hues. You can click the color swatches on the left (or above, on smaller screens) to change the colors.

Text

Contrast: 6.44

Foreground: #591a9f (0.054)

Background: #c4cfe0 (0.617)

Text

Contrast: 8.6

Foreground: #a6e560 (0.65)

Background: #3b301f (0.031)

Lightness inversion in HSL

To fix this hue rotation problem, we can instead invert the lightness of the color, while preserving the hue and saturation. To do this, we convert the color to HSL color space (a cylindrical representation of RGB), invert the lightness, and then convert back to RGB. In HSL, the inversion is simply:

Hinverted=HSinverted=SLinverted=1L\begin{align*} H_{\text{inverted}} &= H \\ S_{\text{inverted}} &= S \\ L_{\text{inverted}} &= 1 - L \end{align*}

This approach is still fairly simple and works well to enable color recognition. In fact, this appears to be the strategy used by Android when the color inversion accessibility feature is turned on.

iOS and macOS have an accessibility feature called “Smart” color inversion mode, but it is not lightness inversion—instead, it is standard RGB inversion that avoids inverting images and videos so that their colors are not distorted.

However, lightness inversion still has one issue: it distorts the contrast ratios between colors. This is because contrast ratio is not simply the difference in lightness between two colors, but rather a more complex calculation that will be explored in the next section.

Use the widget below to see how lightness inversion affects colors and their contrast ratios.

Text

Contrast: 6.44

Foreground: #591a9f (0.054)

Background: #c4cfe0 (0.617)

Text

Contrast: 3.63

Foreground: #9f60e5 (0.214)

Background: #1f2a3b (0.023)

Lightness inversion in Lab

An alternative to inverting lightness in HSL color space is to instead invert the L* component of the CIELAB color space (or another similar space based on a perceptual color model). This approach has a few advantages over HSL inversion:

  • The L* component is designed to be perceptually uniform, so the contrast ratios between colors are more likely to be close to their original values.
  • Since inversion occurs in a perceptual color space, this strategy may produce more visually appealing results.

On the other hand, it also comes with some tradeoffs:

  • The math involved is more complex, so the color space conversion is more expensive.
  • Notably, CIELAB has a much larger gamut than sRGB, so adjusting lightness in this space can produce colors that are out of gamut for sRGB, which will then need to be clamped or otherwise adjusted, potentially distorting the results.

Text

Contrast: 6.44

Foreground: #591a9f (0.054)

Background: #c4cfe0 (0.617)

Text

Contrast: 6.54

Foreground: #dc94ff (0.436)

Background: #222c39 (0.024)

Novel approach

The new approach proposed in this article is to invert colors in a way that preserves both hue and the contrast ratios between color pairs.

The math involved in this approach is a bit more complex, and details about the derivation are available in a separate post.

Deriving a method for contrast preserving color inversion

Seeking an approach to color inversion that preserves contrast ratios between colors.

Jan 2024

Demo

As before, this widget demonstrates the effect of the new inversion algorithm on colors and their contrast ratios.

Text

Contrast: 6.44

Foreground: #591a9f (0.054)

Background: #c4cfe0 (0.617)

Text

Contrast: 6.43

Foreground: #c9a5f0 (0.456)

Background: #233043 (0.029)

It’s not perfect—the contrast ratios are often off by a small amount, but this is due to the limited precision of the 8-bit sRGB color that we need to convert back to in the end, before calculating the results. Note, for example, that shifting even one color component by 1 bit (1/256th) often changes the contrast ratio by several hundredths. In theory, the contrast ratios could be identical in a higher precision color space.

The code used to implement the algorithm in this demo is available here.

Image inversion

Applications

One use case for this new inversion approach is inverting colors to facilitate “dark mode” content themes. In particular, contrast preserving color inversion can be used to invert documents or other user-generated content.

For example, a WYSIWYG word processing document could provide an inverted mode to facilitate comfortable reading in a dark environment, with background and text colors inverted, but with images and other content left unaltered, and without reducing accessibility by changing the contrast ratios between colors as other inversion approaches do.

Or, a browser extension could use this algorithm to invert colors on web pages in a way that preserves the contrast ratios between colors.

I’m not sure whether this approach would be too slow to be used for real-time display inversion (such as the accessibility features found in most operating systems). For 8-bit color, it seems that it should be possible to precompute the inverted colors and store them in a lookup table, so this could be a practical solution. I plan to do further investigations to understand the feasibility of such a tool and whether the output is acceptable.

This approach is specifically tuned to preserve contrast ratios between colors in the sRGB color space based on the relative luminance and contrast ratio calculations defined in the WCAG 2.2 specification. These calculations have changed slightly from past versions, but the result is unaffected. It is likely unsuitable for wider color spaces or where different calculations are required.

As always, you should consult with accessibility experts as well as users when designing accessibility features or where compliance with specific standards is required.

Contents