Expresso.Color (expresso v0.1.0)

Copy Markdown View Source

Calculates the contrast of two colors, and moves the lightness of a color

A color is a #rrggbb string of sRGB. contrast/2 returns the contrast ratio of WCAG 2.2, from 1 to 21. lightness_contrast/2 returns the lightness contrast Lc of APCA, from 0 to about 108. blend/3 returns the color of a foreground with an opacity on a background, as a browser paints it. adjust/4 makes a foreground lighter or darker until it has a contrast ratio and a lightness contrast with a background. It changes only the lightness of the color in OKLab, so the hue stays the same. Expresso.Palette uses these functions for the themes.

Summary

Types

t()

A color as #rrggbb

Functions

Make a foreground lighter or darker until its contrast with the background is at least minimum, and its lightness contrast is at least lc

Return the color of a foreground with an opacity from 0 to 1 on a background

Return the contrast ratio of two colors, as WCAG 2.2 defines it

Return the lightness contrast Lc of a text on a background, as APCA defines it

Return the relative luminance of a color, from 0 for black to 1 for white

Types

t()

@type t() :: String.t()

A color as #rrggbb

Functions

adjust(foreground, background, minimum, lc \\ 0)

@spec adjust(t(), t(), number(), number()) :: {:ok, t(), float()} | :error

Make a foreground lighter or darker until its contrast with the background is at least minimum, and its lightness contrast is at least lc

minimum is a contrast ratio of WCAG, and lc is an absolute value of APCA. The default lc of 0 asks only for the contrast ratio.

The function returns the color and the change of its lightness in OKLab, from 0 to 1. A color that already has the contrast comes back with the change 0. The foreground becomes lighter on a dark background and darker on a light background, in steps of 0.001. The function reduces the chroma only where a color with the new lightness is outside sRGB. It returns :error when no lightness gives the contrast.

iex> Expresso.Color.adjust("#ffffff", "#000000", 4.5)
{:ok, "#ffffff", 0.0}

blend(foreground, background, opacity)

@spec blend(t(), t(), float()) :: t()

Return the color of a foreground with an opacity from 0 to 1 on a background

Each channel is opacity * foreground + (1 - opacity) * background, on the encoded values of sRGB. A browser composites an element with a filter in this way.

iex> Expresso.Color.blend("#000000", "#ffffff", 0.5)
"#808080"

contrast(first, second)

@spec contrast(t(), t()) :: float()

Return the contrast ratio of two colors, as WCAG 2.2 defines it

The ratio is (L1 + 0.05) / (L2 + 0.05), where L1 is the relative luminance of the lighter color and L2 the luminance of the darker color. The order of the arguments is not important.

iex> Expresso.Color.contrast("#000000", "#ffffff")
21.0

iex> Float.round(Expresso.Color.contrast("#777777", "#ffffff"), 2)
4.48

lightness_contrast(text, background)

@spec lightness_contrast(t(), t()) :: float()

Return the lightness contrast Lc of a text on a background, as APCA defines it

The function uses the constants of APCA-W3 version 0.0.98G-4g. APCA gives a negative value for light text on a dark background. This function returns the absolute value, so a larger value is always more contrast. The order of the arguments is important: the first color is the text.

APCA raises each luminance to a different power for the text and for the background, and the powers depend on which color is lighter. Thus the same two colors give a different value when they change places, and a dark background gives less contrast than the contrast ratio of WCAG says. docs/architecture.md gives the formula.

iex> Float.round(Expresso.Color.lightness_contrast("#000000", "#ffffff"), 1)
106.0

iex> Float.round(Expresso.Color.lightness_contrast("#ffffff", "#000000"), 1)
107.9

iex> Float.round(Expresso.Color.lightness_contrast("#888888", "#ffffff"), 1)
63.1

luminance(color)

@spec luminance(t()) :: float()

Return the relative luminance of a color, from 0 for black to 1 for white