tituli.color

Colours, WCAG contrast, and the ink-for-this-background decision.

Everything here is pure arithmetic on (r, g, b[, a]) tuples in 0–255, so it costs nothing to import and is easy to test.

>>> contrast_ratio((255, 255, 255), (0, 0, 0))
21.0
>>> ink_for(luminance=0.9)   # light background -> dark ink
(17, 17, 17)
>>> ink_for(luminance=0.1)   # dark background -> light ink
(255, 255, 255)

Functions

contrast_from_luminance(ink, luminance)

Contrast of ink over a background of known relative luminance.

contrast_ratio(a, b)

WCAG contrast ratio between two colours (1..21).

ink_for(*, luminance[, light, dark])

Pick the ink (light or dark) with more contrast against luminance.

mean_luminance(pixels)

Mean relative luminance of a pixel sample.

mix(a, b, t)

Linear blend, t=0 -> a, t=1 -> b.

needs_scrim(ink, luminance, *[, minimum])

Whether ink over that background falls short of minimum contrast.

parse_color(color)

Accept "#rgb", "#rrggbb", "#rrggbbaa", a Pillow name or a tuple.

relative_luminance(color)

WCAG relative luminance in 0..1.

with_alpha(color, alpha)

Same colour with opacity alpha in 0..1.

tituli.color.contrast_from_luminance(ink, luminance)[source]

Contrast of ink over a background of known relative luminance.

Return type:

float

tituli.color.contrast_ratio(a, b)[source]

WCAG contrast ratio between two colours (1..21).

Return type:

float

>>> round(contrast_ratio("#777", "#fff"), 2)
4.48
tituli.color.ink_for(*, luminance, light=(255, 255, 255), dark=(17, 17, 17))[source]

Pick the ink (light or dark) with more contrast against luminance.

Return type:

tuple[int, int, int]

tituli.color.mean_luminance(pixels)[source]

Mean relative luminance of a pixel sample.

Return type:

float

tituli.color.mix(a, b, t)[source]

Linear blend, t=0 -> a, t=1 -> b.

Return type:

tuple[int, int, int, int]

tituli.color.needs_scrim(ink, luminance, *, minimum=4.5)[source]

Whether ink over that background falls short of minimum contrast.

Return type:

bool

>>> needs_scrim((255, 255, 255), luminance=0.5)
True
>>> needs_scrim((255, 255, 255), luminance=0.02)
False
tituli.color.parse_color(color)[source]

Accept "#rgb", "#rrggbb", "#rrggbbaa", a Pillow name or a tuple.

Return type:

tuple[int, int, int, int]

>>> parse_color("#fff")
(255, 255, 255, 255)
>>> parse_color((10, 20, 30))
(10, 20, 30, 255)
tituli.color.relative_luminance(color)[source]

WCAG relative luminance in 0..1.

Return type:

float

>>> round(relative_luminance((255, 255, 255)), 3)
1.0
>>> relative_luminance((0, 0, 0))
0.0
tituli.color.with_alpha(color, alpha)[source]

Same colour with opacity alpha in 0..1.

Return type:

tuple[int, int, int, int]

>>> with_alpha("#000", 0.5)
(0, 0, 0, 128)