stouputils.image.colors module#

Colour maths for deciding what to draw on top of a filled shape.

Charts, heatmaps and legends all face the same question: black text or white text on this patch? Answering it from the sRGB luminance keeps every annotation readable, whatever colormap produced the patch.

relative_luminance(
color: Iterable[float],
) float[source]#

Perceived brightness of a colour, as the sRGB relative luminance (https://en.wikipedia.org/wiki/Relative_luminance).

Channels are linearised before being weighted, since sRGB stores them gamma-encoded. Averaging the raw channels instead would rate a saturated blue as bright as a saturated green.

Parameters:

color (Iterable[float]) – RGB or RGBA channels in [0.0, 1.0]; anything past the third is ignored.

Returns:

Luminance in [0.0, 1.0], 0.0 being black and 1.0 white.

Return type:

float

Examples

>>> relative_luminance((0.0, 0.0, 0.0))
0.0
>>> relative_luminance((1.0, 1.0, 1.0, 1.0))
1.0
>>> round(relative_luminance((1.0, 0.0, 0.0)), 4)
0.2126
>>> round(relative_luminance((0.0, 0.0, 1.0)), 4)
0.0722
readable_text_color(
color: Iterable[float],
on_light: str = 'black',
on_dark: str = 'white',
threshold: float = 0.4,
) str[source]#

Pick the text colour that stays readable on top of a given background colour.

Parameters:
  • color (Iterable[float]) – Background RGB or RGBA channels in [0.0, 1.0].

  • on_light (str) – Returned when the background is light, ex: “black” or “#222222”.

  • on_dark (str) – Returned when the background is dark.

  • threshold (float) – Luminance above which the background counts as light.

Returns:

Either on_light or on_dark, untouched, so any colour syntax the caller uses goes through.

Return type:

str

Examples

>>> readable_text_color((1.0, 1.0, 1.0))
'black'
>>> readable_text_color((0.27, 0.0, 0.33, 1.0))
'white'
>>> readable_text_color((0.99, 0.91, 0.14), on_light=".1")
'.1'