=encoding utf8
=head1 NAME
colour/contrast - Colour contrast helpers.
=head1 SYNOPSIS
from colour/contrast import
contrast_ratio,
contrast_passes,
readable_text_colour;
say( contrast_ratio( "black", "white" ) );
say( readable_text_colour( "gold" ) );
=head1 DESCRIPTION
This pure-Zuzu module provides WCAG-style relative luminance and contrast
ratio helpers. All colour arguments are parsed through C<std/colour> via
C<colour/palette>.
=head1 EXPORTED FUNCTIONS
=over
=item * C<< relative_luminance(String colour) >>
Return the sRGB relative luminance for C<colour>.
=item * C<< contrast_ratio(String foreground, String background) >>
Return the contrast ratio between two colours.
=item * C<< contrast_passes(String foreground, String background,
String level := "AA", Boolean large_text := false) >>
Return whether the pair passes C<AA> or C<AAA>. Large text uses the
corresponding reduced thresholds.
=item * C<< contrast_grade(String foreground, String background) >>
Return C<AAA>, C<AA>, C<AA Large>, or C<Fail>.
=item * C<< best_contrast(String background, Array colours) >>
Return the candidate colour with the best contrast against C<background>.
=item * C<< readable_text_colour(String background, String dark :=
"#000000", String light := "#ffffff") >>
Return whichever of C<dark> or C<light> has better contrast against
C<background>.
=back
=head1 COPYRIGHT AND LICENCE
B<< colour/contrast >> is copyright Toby Inkster.
It is free software; you may redistribute it and/or modify it under
the terms of either the Artistic License 1.0 or the GNU General Public
License version 2.
=cut
from std/colour import parse_colour;
from std/math import Math;
from colour/palette import rgb;
function _linear_component ( Number channel ) {
let value := channel / 255;
if ( value <= 0.03928 ) {
return value / 12.92;
}
return Math.pow( ( value + 0.055 ) / 1.055, 2.4 );
}
function relative_luminance ( String colour ) {
let parts := rgb(colour);
return 0.2126 * _linear_component( parts{r} )
+ 0.7152 * _linear_component( parts{g} )
+ 0.0722 * _linear_component( parts{b} );
}
function contrast_ratio ( String foreground, String background ) {
let left := relative_luminance(foreground);
let right := relative_luminance(background);
let lighter := Math.max( left, right );
let darker := Math.min( left, right );
return ( lighter + 0.05 ) / ( darker + 0.05 );
}
function _threshold ( String level, Boolean large_text ) {
let normalized := uc(level);
if ( normalized eq "AA" ) {
return large_text ? 3 : 4.5;
}
if ( normalized eq "AAA" ) {
return large_text ? 4.5 : 7;
}
die "colour/contrast: level must be AA or AAA";
}
function contrast_passes (
String foreground,
String background,
String level := "AA",
Boolean large_text := false
) {
return contrast_ratio( foreground, background )
>= _threshold( level, large_text );
}
function contrast_grade ( String foreground, String background ) {
let ratio := contrast_ratio( foreground, background );
if ( ratio >= 7 ) {
return "AAA";
}
if ( ratio >= 4.5 ) {
return "AA";
}
if ( ratio >= 3 ) {
return "AA Large";
}
return "Fail";
}
function best_contrast ( String background, Array colours ) {
if ( colours.length() == 0 ) {
die "colour/contrast: colours must not be empty";
}
let best := parse_colour( "" _ colours[0] );
let best_ratio := contrast_ratio( best, background );
let i := 1;
while ( i < colours.length() ) {
let candidate := parse_colour( "" _ colours[i] );
let ratio := contrast_ratio( candidate, background );
if ( ratio > best_ratio ) {
best := candidate;
best_ratio := ratio;
}
i++;
}
return best;
}
function readable_text_colour (
String background,
String dark := "#000000",
String light := "#ffffff"
) {
return best_contrast( background, [ dark, light ] );
}
modules/colour/contrast.zzm
colour-palette-0.0.1 source code
Package
- Name
- colour-palette
- Version
- 0.0.1
- Uploaded
- 2026-05-13 17:33:17
- Dependencies
-
-
std/colour>= 0 -
std/math>= 0 -
std/string>= 0
-
- Metadata
- zuzu-distribution.json
- Archive
- Download .tar.gz