Types
10
The values a colour is made of, and the other types the functions return.
structColor
pub(all) struct Color
A colour: three components in a named space, and an alpha.
Alpha is kept apart from the components because it is not a colour: it says how much of what is behind shows through, and it survives a change of space untouched.
A component may be none, which CSS writes as the none keyword and which this library carries as a NaN — "this space did not say", which interpolation then fills in from the other side. Ordinary arithmetic on such a component gives NaN back, which is the honest answer.
- space : Space
- x : Double
- y : Double
- z : Double
- alpha : Double
enumSpace
pub(all) enum Space
The space a colour's three components are read in.
space | x | y | z |
|---|
Srgb, SrgbLinear, DisplayP3, Rec2020, A98Rgb, ProPhoto | red | green | blue |
Hsl | hue, degrees | saturation | lightness |
Hsv | hue, degrees | saturation | value |
Hwb | hue, degrees | whiteness | blackness |
Lab | lightness, 0–100 | a | b |
Lch | lightness, 0–100 | chroma | hue, degrees |
Oklab | lightness, 0–1 | a | b |
Oklch | lightness, 0–1 | chroma | hue, degrees |
XyzD50, XyzD65 | X | Y | Z |
YCbCr | luma | blue difference | red difference |
Current | — | — | — |
Component ranges are the ones the format itself uses, not a common scale: Lab lightness is 0–100 because that is what lab() writes, while Oklab lightness is 0–1 because that is what oklab() writes. A colour out of its space's range is still a valid value — CSS Color 4 lets a colour be outside its gamut and leaves clipping to the moment it is painted.
- SrgbGamma-encoded sRGB, components 0–1. The one space every screen has.
- SrgbLinearLinear-light sRGB, components 0–1: sRGB without the transfer curve.
- HslHue in degrees, saturation and lightness 0–1.
- HsvHue in degrees, saturation and value 0–1. No CSS syntax of its own.
- HwbHue in degrees, whiteness and blackness 0–1.
- LabCIE Lab, D50. Lightness 0–100, a and b roughly ±125.
- LchCIE LCh, D50: Lab in polar form, hue in degrees.
- OklabOklab. Lightness 0–1, a and b roughly ±0.4.
- OklchOkLCh: Oklab in polar form, hue in degrees.
- XyzD50CIE XYZ relative to D50, Y = 1 for white.
- XyzD65CIE XYZ relative to D65, Y = 1 for white. What
color(xyz ...) writes. - DisplayP3Display P3, components 0–1, sRGB's transfer curve with wider primaries.
- DisplayP3LinearLinear-light Display P3: P3 without the transfer curve.
- Rec2020Rec. 2020, components 0–1.
- A98RgbAdobe RGB (1998), components 0–1.
- ProPhotoProPhoto RGB, components 0–1, D50 white.
- YCbCr(Luma)A luma/chroma pair as BT.601, BT.709 or BT.2020 define it. Luma 0–1, chroma −0.5–0.5. No CSS syntax.
- CurrentNot a space: the
currentcolor keyword, which stands for whatever colour the element it is painted on already has. Carried in a Color so that a declaration can be read and written back unchanged; resolve it before converting, with Color::resolve.
Space::code
pub fn Space::code(self : Space) -> String
The identifier this space is written with in CSS, or the one this library uses where CSS has no syntax for it.
Space::has_gamut
pub fn Space::has_gamut(self : Space) -> Bool
Whether a space has a bounded gamut at all. XYZ and the polar spaces do not: every triple is a colour, and only the RGB spaces say which ones a display can show.
Space::is_polar
pub fn Space::is_polar(self : Space) -> Bool
Whether the space writes a hue in its third component (Hsl, Hsv, Hwb) or in its third of a polar pair (Lch, Oklch).
Space::is_predefined
pub fn Space::is_predefined(self : Space) -> Bool
Whether this space can be written inside CSS's color().
Space::of
pub fn Space::of(code : StringView) -> Space?
The space an identifier names, if any.
enumFormat
pub(all) enum Format
How to write a colour out.
- ShortestThe shortest spelling the colour's own space allows, falling back to sRGB for the spaces CSS has no syntax for.
- Hex
#rgb, #rgba, #rrggbb or #rrggbbaa, whichever is shortest and exact. Always sRGB, so a colour in another space is converted and may be clipped. - Rgb
rgb(0 0 0) or rgb(0 0 0 / 0.5), always sRGB. - Hsl
hsl(0 0% 0%). - Hwb
hwb(0 0% 0%). - Lab
lab(0 0 0). - Lch
lch(0 0 0). - Oklab
oklab(0 0 0). - Oklch
oklch(0 0 0). - Color
color(srgb 0 0 0) and the rest of the spaces color() takes. A space it cannot name is written in its own syntax instead. - NamedThe name the colour has, or the same string
Hex would give.
enumDelta
pub(all) enum Delta
Which colour difference to measure. The name is the question, not the space: three of these are computed in Lab and one in Oklab.
- E76CIE76: the Euclidean distance in Lab, which is 1976's answer.
- E94CIE94, with the graphic-arts weights: 1 for lightness, 1 for chroma, 1 for hue. The textile weights (2, 1, 1) are a different question.
- E2000CIEDE2000, the current CIE recommendation, with all three weights at 1.
- OkThe Euclidean distance in Oklab, which needs no weights to be even.
enumHue
pub(all) enum Hue
Which way round the hue circle an interpolation travels.
- ShorterThe way that is at most half a turn: 350° to 10° goes through 0°.
- LongerThe way that is at least half a turn.
- IncreasingAlways increasing, so 350° to 10° goes through 360°.
- DecreasingAlways decreasing.
enumLuma
pub(all) enum Luma
Which luma coefficients a YCbCr colour uses.
The three differ only in how much of each primary counts as brightness, and a value converted with the wrong one is visibly off, so the choice is part of the value rather than a global setting.
- Bt601BT.601: the coefficients of SD television.
- Bt709BT.709: the coefficients of HD television, and of sRGB in practice.
- Bt2020BT.2020: the coefficients of UHD television, on wider primaries.
structCmyk
pub(all) struct Cmyk
CMYK for print, as four inks in 0–1.
The conversion is the arithmetic one — full black is all key, and the other three inks spread what is left — not the one an ICC profile for a particular press and paper would give.
Four inks do not fit in three channels, so the arithmetic has to choose: for one colour there are many ink combinations, and to_cmyk returns the one with as much key as possible, which is the usual choice because key ink is cheap and the three colour inks are not. That means a colour survives the round trip but its inks may not: cmyk(0.2 0.4 0.6 0.1) prints the same colour as cmyk(0 0.25 0.5 0.28), and only the second comes back.
- c : Double
- m : Double
- y : Double
- k : Double
Cmyk::of
pub fn Cmyk::of(c : Double, m : Double, y : Double, k : Double) -> Cmyk
Four inks. Nothing is clamped here: an ink of 1.2 is a number no press can lay down, and deciding that is the printer's business rather than the type's. to_color clamps on the way back out, so the arithmetic stays total.
Cmyk::to_color
pub fn Cmyk::to_color(self : Cmyk, alpha? : Double) -> Color
A colour from CMYK inks, in sRGB.
enumAnsi16
pub(all) enum Ansi16
The sixteen colours a terminal names, in the order their escape sequences number them.
- Black
- Red
- Green
- Yellow
- Blue
- Magenta
- Cyan
- White
- BrightBlack
- BrightRed
- BrightGreen
- BrightYellow
- BrightBlue
- BrightMagenta
- BrightCyan
- BrightWhite
Ansi16::code
pub fn Ansi16::code(self : Ansi16) -> Int32
Where a named colour sits in the palette, which is also the low nibble of the escape sequence that selects it.
Ansi16::color
pub fn Ansi16::color(self : Ansi16) -> Color
The sRGB colour of a named slot: xterm's defaults.
Ansi16::of
pub fn Ansi16::of(code : Int32) -> Ansi16?
The colour a palette slot stands for, if the number names one.
structAnsi256
pub(all) struct Ansi256
A slot in the 256-colour palette: 0–15 are the named colours, 16–231 a 6×6×6 cube, and 232–255 a grey ramp.
- code : Int320–255. Kept in range by
Ansi256::of; written by hand it is a number a terminal would mask, so nothing here trusts it to be inside.
Ansi256::code
pub fn Ansi256::code(self : Ansi256) -> Int32
The number to put in an escape sequence.
Ansi256::color
pub fn Ansi256::color(self : Ansi256) -> Color
The colour a slot stands for.
Ansi256::of
pub fn Ansi256::of(code : Int32) -> Ansi256
The palette slot with this number. Numbers outside 0–255 are clamped rather than wrapped, so that an out-of-range number lands on the nearest end rather than on a colour that is nothing like it.
suberrorRefused
pub(all) suberror Refused
Why a string is not a CSS colour.
- Unexpected(at~ : Int32, got~ : String, want~ : String)The text did not continue the way the syntax required, at this offset.
- Unknown(at~ : Int32, got~ : String)A function or keyword that is valid CSS but not a colour this library knows, at this offset.
Making a colour
22
One constructor per space, each taking that space's own units. The colour stays in the space it was made in, so it is written back in that space.
methodColor::black
pub fn Color::black() -> Color
Black in sRGB.
methodColor::white
pub fn Color::white() -> Color
White in sRGB.
methodColor::transparent
pub fn Color::transparent() -> Color
transparent: black with no alpha at all. Not a space of its own, because what shows through is decided by the backdrop, not by the colour.
methodColor::current
pub fn Color::current() -> Color
The currentcolor keyword.
methodColor::rgb
pub fn Color::rgb( red : Int32, green : Int32, blue : Int32, alpha? : Double, ) -> Color
sRGB from bytes, 0–255, which is how a colour usually arrives from a file, a theme or a terminal.
methodColor::srgb
pub fn Color::srgb( r : Double, g : Double, b : Double, alpha? : Double, ) -> Color
sRGB from components in 0–1.
methodColor::srgb_linear
pub fn Color::srgb_linear( r : Double, g : Double, b : Double, alpha? : Double, ) -> Color
Linear-light sRGB from components in 0–1.
methodColor::hsl
pub fn Color::hsl(h : Double, s : Double, l : Double, alpha? : Double) -> Color
HSL: hue in degrees, saturation and lightness in 0–1.
methodColor::hsv
pub fn Color::hsv(h : Double, s : Double, v : Double, alpha? : Double) -> Color
HSV: hue in degrees, saturation and value in 0–1.
methodColor::hwb
pub fn Color::hwb(h : Double, w : Double, b : Double, alpha? : Double) -> Color
HWB: hue in degrees, whiteness and blackness in 0–1.
methodColor::lab
pub fn Color::lab(l : Double, a : Double, b : Double, alpha? : Double) -> Color
CIE Lab with a D50 white: lightness 0–100.
methodColor::lch
pub fn Color::lch(l : Double, c : Double, h : Double, alpha? : Double) -> Color
CIE LCh with a D50 white: lightness 0–100, hue in degrees.
methodColor::oklab
pub fn Color::oklab( l : Double, a : Double, b : Double, alpha? : Double, ) -> Color
Oklab: lightness 0–1.
methodColor::oklch
pub fn Color::oklch( l : Double, c : Double, h : Double, alpha? : Double, ) -> Color
OkLCh: lightness 0–1, hue in degrees.
methodColor::display_p3
pub fn Color::display_p3( r : Double, g : Double, b : Double, alpha? : Double, ) -> Color
Display P3, components in 0–1.
methodColor::rec2020
pub fn Color::rec2020( r : Double, g : Double, b : Double, alpha? : Double, ) -> Color
Rec. 2020, components in 0–1.
methodColor::a98_rgb
pub fn Color::a98_rgb( r : Double, g : Double, b : Double, alpha? : Double, ) -> Color
Adobe RGB (1998), components in 0–1.
methodColor::prophoto
pub fn Color::prophoto( r : Double, g : Double, b : Double, alpha? : Double, ) -> Color
ProPhoto RGB, components in 0–1, D50 white.
methodColor::xyz_d50
pub fn Color::xyz_d50( x : Double, y : Double, z : Double, alpha? : Double, ) -> Color
CIE XYZ relative to D50, Y = 1 for white.
methodColor::xyz_d65
pub fn Color::xyz_d65( x : Double, y : Double, z : Double, alpha? : Double, ) -> Color
CIE XYZ relative to D65, Y = 1 for white.
methodColor::ycbcr
pub fn Color::ycbcr( y : Double, cb : Double, cr : Double, luma? : Luma, alpha? : Double, ) -> Color
YCbCr: luma in 0–1, chroma in −0.5–0.5.
methodColor::of
pub fn Color::of( space : Space, x : Double, y : Double, z : Double, alpha? : Double, ) -> Color
A colour in any space, from components already in that space's own units.
The named constructors below cover the common spaces; this one is for the rest, and for code that picks a space at run time.
Reading and writing
5
CSS in, CSS out, and the name table in both directions.
methodColor::decode
pub fn Color::decode(text : StringView) -> Color raise Refused
A <color>: everything from the first non-blank to the closing bracket.
methodColor::decode_opt
pub fn Color::decode_opt(text : StringView) -> Color?
Read a colour, or None where the text is not one.
methodColor::encode
pub fn Color::encode(self : Color, format? : Format) -> String
A colour as CSS, in the shortest spelling that reads back as the same colour.
Without a format the colour stays in the space it is in, which matters: an OkLCh colour written through sRGB would be clipped to a gamut it never asked for. The spaces CSS cannot spell — HSV, YCbCr — go out as sRGB, and the unresolved currentcolor goes out as itself.
methodColor::of_name
pub fn Color::of_name(name : StringView) -> Color?
The colour a CSS keyword names, or None if it names nothing.
transparent and currentcolor are not here: one is a colour with no alpha and the other is not a colour yet, and both are handled where a declaration is read rather than in the table of names.
methodColor::name
pub fn Color::name(self : Color) -> String?
The name this colour has, if it has one, and the shortest such name where several do: aqua and cyan are the same colour, and four letters beat four letters only by coming first in the table.
A colour that is not one of the 148 opaque names has no name, however close it looks: a name is a spelling, not an approximation.
Converting
18
One method per destination space, and to_space for a space known only at run time. A conversion is exact in both directions.
methodColor::to_srgb
pub fn Color::to_srgb(self : Color) -> Color
sRGB, components 0–1.
methodColor::to_srgb_linear
pub fn Color::to_srgb_linear(self : Color) -> Color
Linear-light sRGB, components 0–1.
methodColor::to_hsl
pub fn Color::to_hsl(self : Color) -> Color
HSL: hue in degrees, saturation and lightness 0–1.
methodColor::to_hsv
pub fn Color::to_hsv(self : Color) -> Color
HSV: hue in degrees, saturation and value 0–1.
methodColor::to_hwb
pub fn Color::to_hwb(self : Color) -> Color
HWB: hue in degrees, whiteness and blackness 0–1.
methodColor::to_oklab
pub fn Color::to_oklab(self : Color) -> Color
Oklab, lightness 0–1.
methodColor::to_oklch
pub fn Color::to_oklch(self : Color) -> Color
OkLCh, lightness 0–1.
methodColor::to_lab
pub fn Color::to_lab(self : Color) -> Color
CIE Lab relative to D50, lightness 0–100.
methodColor::to_lch
pub fn Color::to_lch(self : Color) -> Color
CIE LCh relative to D50, lightness 0–100.
methodColor::to_xyz_d65
pub fn Color::to_xyz_d65(self : Color) -> Color
CIE XYZ relative to D65, Y = 1 for white.
methodColor::to_xyz_d50
pub fn Color::to_xyz_d50(self : Color) -> Color
CIE XYZ relative to D50, Y = 1 for white.
methodColor::to_display_p3
pub fn Color::to_display_p3(self : Color) -> Color
Display P3, components 0–1.
methodColor::to_rec2020
pub fn Color::to_rec2020(self : Color) -> Color
Rec. 2020, components 0–1.
methodColor::to_a98_rgb
pub fn Color::to_a98_rgb(self : Color) -> Color
Adobe RGB (1998), components 0–1.
methodColor::to_prophoto
pub fn Color::to_prophoto(self : Color) -> Color
ProPhoto RGB, components 0–1.
methodColor::to_cmyk
pub fn Color::to_cmyk(self : Color) -> Cmyk
The colour as four inks: the arithmetic conversion, through sRGB, taking as much key as the colour allows. Black is all key, and a white has none.
This is the inverse of Cmyk::to_color for the colour but not for the inks — the note on Cmyk says why, and the platform test that reads it back says it in numbers.
methodColor::to_ycbcr
pub fn Color::to_ycbcr(self : Color, luma~ : Luma) -> Color
YCbCr with the given luma coefficients: luma 0–1, chroma −0.5–0.5.
methodColor::to_space
pub fn Color::to_space(self : Color, space : Space) -> Color
The same colour in another space.
This is the one conversion every to_* goes through, so that a pair of spaces is only ever defined once. Converting to the space a colour is already in returns it unchanged rather than round-tripping it through a hub, which would otherwise cost a matrix inversion for nothing.
Components
9
The three numbers, read and replaced. A colour keeps its space, so reading a component gives the number that space uses.
methodColor::components
pub fn Color::components(self : Color) -> (Double, Double, Double)
The three components, in the order the space writes them.
methodColor::with_components
pub fn Color::with_components( self : Color, x : Double, y : Double, z : Double, ) -> Color
The same colour with different components, in the same space.
methodColor::with_alpha
pub fn Color::with_alpha(self : Color, alpha : Double) -> Color
The same colour at a different alpha.
methodColor::hue
pub fn Color::hue(self : Color) -> Double
The hue in degrees, or NaN where the space has none.
methodColor::lightness
pub fn Color::lightness(self : Color) -> Double
The lightness, or NaN where the space has none.
methodColor::saturation
pub fn Color::saturation(self : Color) -> Double
The saturation, or NaN where the space has none.
methodColor::chroma
pub fn Color::chroma(self : Color) -> Double
The chroma, or NaN where the space has none.
methodColor::is_current
pub fn Color::is_current(self : Color) -> Bool
Whether this is the unresolved currentcolor keyword.
methodColor::resolve
pub fn Color::resolve(self : Color, current : Color) -> Color
Substitute the colour that currentcolor stands for. Anything else is returned unchanged, so this is safe to call on every colour.
Gamut
3
Whether a colour fits, and what to do about it when it does not.
methodColor::in_gamut
pub fn Color::in_gamut(self : Color, space? : Space) -> Bool
Whether the colour is inside the gamut of its space, or of space if one is given.
A space with no gamut answers true: there is nothing for a colour to be outside of.
methodColor::to_gamut
pub fn Color::to_gamut(self : Color, space? : Space) -> Color
The colour brought into the gamut of its space, or of space if one is given, by CSS Color 4's chroma reduction.
A colour already inside comes back unchanged, in the space asked for. One outside loses chroma at constant lightness and hue until it fits, and only in the cases where that cannot work — a colour too light or too dark to exist at all — falls back to clipping.
methodColor::clip
pub fn Color::clip(self : Color, space? : Space) -> Color
The colour with every component clamped into 0–1 of its space, which is what a display does with a colour it cannot show.
Clipping is a blunt instrument: it moves a colour along whichever axis overflows and leaves hue and lightness to follow. to_gamut is the answer that keeps them; this is the answer that costs nothing, and the one to use when the colour is already close to the edge.
Mixing and adjusting
6
color-mix() and the four verbs that move one quantity.
methodColor::mix
pub fn Color::mix( self : Color, other : Color, space? : Space, hue? : Hue, weight? : Double, ) -> Color
A mix of two colours, as color-mix() defines one.
weight is how much of this colour the result has: 0.3 means three parts of this and seven of other, which is what color-mix(in oklab, this 30%, other) writes. It defaults to half.
The result is in the interpolation space, not in the space either colour came from: mixing in Oklab and then converting back to sRGB would lose the out-of-gamut values the mix produced, and to_gamut is the caller's decision to make.
A missing component — none — takes the other colour's value before the mix, so that oklch(none 0.1 200) mixed with a colour that has a lightness interpolates that lightness rather than treating the gap as zero.
currentcolor is not mixed: it stands for a colour nobody has resolved yet, and the result would be a colour that pretends to know.
methodColor::lighten
pub fn Color::lighten(self : Color, amount : Double) -> Color
Move the colour towards white by amount, a fraction of its space's lightness scale: 0.1 in HSL is a tenth of the way to white, 10 in Lab is ten units of lightness.
Hue and chroma are kept, so this is not the same as mixing with white: a mix with white washes the chroma out as well.
methodColor::darken
pub fn Color::darken(self : Color, amount : Double) -> Color
Move the colour towards black by amount, a fraction of its space's lightness scale.
methodColor::saturate
pub fn Color::saturate(self : Color, amount : Double) -> Color
Add chroma, by amount of the space's chroma scale: 0.1 is a tenth of the chroma a colour can have before it leaves what people can see.
A colour already at the edge of its gamut gains nothing visible from this; to_gamut afterwards is what says so.
methodColor::desaturate
pub fn Color::desaturate(self : Color, amount : Double) -> Color
Take chroma away, by amount of the space's chroma scale. At 1 the colour is grey.
methodColor::fade
pub fn Color::fade(self : Color, amount : Double) -> Color
The same colour with less alpha, by amount of what it has: 0.25 leaves three quarters.
Measuring
4
Two contrast measures and four colour differences, each on its own scale.
methodColor::contrast
pub fn Color::contrast(self : Color, other : Color) -> Double
The WCAG 2 contrast ratio between two colours: 1 for the same colour, 21 for black on white.
The order does not matter — this is the measure that is symmetric, and that is one of the reasons APCA exists.
methodColor::apca
pub fn Color::apca(self : Color, background : Color) -> Double
The APCA lightness contrast Lc of this colour as text on background: positive for dark text on a light background, negative the other way round, and zero when the pair is too close to read at all.
The constants are APCA 0.98G-4g, the version the SAPC project publishes for web use. A sign-flipped result is not a mistake: it says which way round the pair is, and the thresholds for readable text are different in each direction, so a caller that wants "is this readable" has to ask about a specific way round.
methodColor::luminance
pub fn Color::luminance(self : Color) -> Double
Relative luminance per WCAG 2: sRGB made linear, weighted by the eye's sensitivity to each primary, with the components clamped to what a screen can show.
methodColor::delta_e
pub fn Color::delta_e(self : Color, other : Color, delta? : Delta) -> Double
The distance between two colours, by the chosen measure.
Colours in any space are converted to the one the measure is defined on, so delta_e answers about the colours, not about where they are written. Opaque alpha plays no part: this is a distance between colours, and a colour at 10% opacity is the same colour as one at 100%.