=encoding utf8
=head1 NAME
colour/palette - Colour palette helpers.
=head1 SYNOPSIS
from colour/palette import
rgb,
hsl,
from_rgb,
from_hsl,
lighten,
complement,
triad;
say( rgb("purple") );
say( lighten( "#336699", 10 ) );
say( triad( "tomato" ) );
=head1 DESCRIPTION
This pure-Zuzu module builds on C<std/colour> to convert colours between
hexadecimal, RGB, and HSL forms, adjust lightness and saturation, mix two
colours, rotate hue, and generate small palettes.
All colour arguments are parsed with C<parse_colour>, so CSS colour
keywords and three- or six-digit hexadecimal colours are accepted.
=head1 EXPORTED FUNCTIONS
=over
=item * C<< rgb(String colour) >>
Return a dictionary with C<r>, C<g>, and C<b> channel values from 0 to
255.
=item * C<< hsl(String colour) >>
Return a dictionary with C<h> in degrees and C<s> and C<l> as
percentages.
=item * C<< from_rgb(Number r, Number g, Number b) >>
Return a normalized lowercase hexadecimal colour.
=item * C<< from_hsl(Number h, Number s, Number l) >>
Return a normalized lowercase hexadecimal colour.
=item * C<< mix(String left, String right, Number weight := 0.5) >>
Mix two colours. C<weight> is the fraction of C<right> in the result.
=item * C<< lighten(String colour, Number amount) >>
=item * C<< darken(String colour, Number amount) >>
=item * C<< saturate(String colour, Number amount) >>
=item * C<< desaturate(String colour, Number amount) >>
Adjust HSL percentage channels by C<amount> percentage points.
=item * C<< rotate_hue(String colour, Number degrees) >>
=item * C<< complement(String colour) >>
=item * C<< analogous(String colour, Number angle := 30) >>
=item * C<< triad(String colour) >>
=item * C<< tetrad(String colour) >>
=item * C<< monochrome(String colour, Number steps := 5,
Number spread := 30) >>
=back
=head1 COPYRIGHT AND LICENCE
B<< colour/palette >> 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 std/string import substr;
function _div_floor ( Number n, Number d ) {
return floor( n / d );
}
function _mod ( Number n, Number d ) {
return n - _div_floor( n, d ) * d;
}
function _hex_channel ( String hex, Number offset ) {
return Math.hex2dec( substr( hex, offset, 2 ) ) + 0;
}
function _clamp ( Number value, Number low, Number high ) {
if ( value < low ) {
return low;
}
if ( value > high ) {
return high;
}
return value;
}
function _channel ( Number value ) {
return int( _clamp( round(value), 0, 255 ) );
}
function _percent ( Number value ) {
return _clamp( value, 0, 100 );
}
function _hex_byte ( Number value ) {
let text := Math.dec2hex( "" _ _channel(value) );
if ( length text == 1 ) {
return "0" _ text;
}
return text;
}
function _rgb_to_hex ( Number r, Number g, Number b ) {
return "#" _ _hex_byte(r) _ _hex_byte(g) _ _hex_byte(b);
}
function _wrap_hue ( Number hue ) {
let wrapped := _mod( hue, 360 );
if ( wrapped < 0 ) {
wrapped += 360;
}
return wrapped;
}
function _hue_to_rgb ( Number c, Number x, Number hprime ) {
if ( hprime < 1 ) {
return [ c, x, 0 ];
}
if ( hprime < 2 ) {
return [ x, c, 0 ];
}
if ( hprime < 3 ) {
return [ 0, c, x ];
}
if ( hprime < 4 ) {
return [ 0, x, c ];
}
if ( hprime < 5 ) {
return [ x, 0, c ];
}
return [ c, 0, x ];
}
function rgb ( String colour ) {
let hex := parse_colour(colour);
return {
r: _hex_channel( hex, 1 ),
g: _hex_channel( hex, 3 ),
b: _hex_channel( hex, 5 ),
};
}
function hsl ( String colour ) {
let parts := rgb(colour);
let r := parts{r} / 255;
let g := parts{g} / 255;
let b := parts{b} / 255;
let max := Math.max( r, g, b );
let min := Math.min( r, g, b );
let delta := max - min;
let lightness := ( max + min ) / 2;
let hue := 0;
let saturation := 0;
if ( delta != 0 ) {
saturation := delta / ( 1 - abs( 2 * lightness - 1 ) );
if ( max == r ) {
hue := 60 * _mod( ( g - b ) / delta, 6 );
}
else if ( max == g ) {
hue := 60 * ( ( b - r ) / delta + 2 );
}
else {
hue := 60 * ( ( r - g ) / delta + 4 );
}
}
return {
h: _wrap_hue(hue),
s: saturation * 100,
l: lightness * 100,
};
}
function from_rgb ( Number r, Number g, Number b ) {
return _rgb_to_hex( r, g, b );
}
function from_hsl ( Number h, Number s, Number l ) {
let hue := _wrap_hue(h);
let saturation := _percent(s) / 100;
let lightness := _percent(l) / 100;
if ( saturation == 0 ) {
let grey := lightness * 255;
return _rgb_to_hex( grey, grey, grey );
}
let c := ( 1 - abs( 2 * lightness - 1 ) ) * saturation;
let hprime := hue / 60;
let x := c * ( 1 - abs( _mod( hprime, 2 ) - 1 ) );
let base := _hue_to_rgb( c, x, hprime );
let m := lightness - c / 2;
return _rgb_to_hex(
( base[0] + m ) * 255,
( base[1] + m ) * 255,
( base[2] + m ) * 255,
);
}
function mix ( String left, String right, Number weight := 0.5 ) {
let lw := 1 - _clamp( weight, 0, 1 );
let rw := 1 - lw;
let l := rgb(left);
let r := rgb(right);
return _rgb_to_hex(
l{r} * lw + r{r} * rw,
l{g} * lw + r{g} * rw,
l{b} * lw + r{b} * rw,
);
}
function lighten ( String colour, Number amount ) {
let value := hsl(colour);
return from_hsl( value{h}, value{s}, value{l} + amount );
}
function darken ( String colour, Number amount ) {
return lighten( colour, -amount );
}
function saturate ( String colour, Number amount ) {
let value := hsl(colour);
return from_hsl( value{h}, value{s} + amount, value{l} );
}
function desaturate ( String colour, Number amount ) {
return saturate( colour, -amount );
}
function rotate_hue ( String colour, Number degrees ) {
let value := hsl(colour);
return from_hsl( value{h} + degrees, value{s}, value{l} );
}
function complement ( String colour ) {
return rotate_hue( colour, 180 );
}
function analogous ( String colour, Number angle := 30 ) {
return [
rotate_hue( colour, -angle ),
parse_colour(colour),
rotate_hue( colour, angle ),
];
}
function triad ( String colour ) {
return [
parse_colour(colour),
rotate_hue( colour, 120 ),
rotate_hue( colour, 240 ),
];
}
function tetrad ( String colour ) {
return [
parse_colour(colour),
rotate_hue( colour, 90 ),
complement(colour),
rotate_hue( colour, 270 ),
];
}
function monochrome ( String colour, Number steps := 5, Number spread := 30 ) {
let count := int(steps);
if ( count != steps or count < 1 ) {
die "colour/palette: steps must be a positive integer";
}
let value := hsl(colour);
let start := value{l} - spread / 2;
let out := [];
let i := 0;
while ( i < count ) {
let offset := count == 1 ? 0 : spread * i / ( count - 1 );
out.push( from_hsl( value{h}, value{s}, start + offset ) );
i++;
}
return out;
}
modules/colour/palette.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