moonrgba

    Colour for MoonBit: CSS syntax, colour spaces, gamut mapping, terminal colours and colour arithmetic.

    color
    colour
    css
    css-color
    oklch
    oklab
    lab
    lch
    hsl
    hwb
    hsv
    srgb
    display-p3
    rec2020
    a98-rgb
    prophoto-rgb
    xyz
    cmyk
    gamut
    gamut-mapping
    contrast
    wcag
    apca
    terminal
    ansi
    ansi256
    color-mixing
    palette
    theme
    moonbit
    Download zip
    Version
    0.1.1
    License
    Apache-2.0
    Last updated
    2 days ago
    Downloads
    22

    No dependencies

    #moonrgba


    Colour for MoonBit: the syntax CSS writes a colour in, the spaces it can be measured in, and the arithmetic a theme, a chart or a terminal needs to get from one to the other.

    No I/O, no dependencies, all four backends.

    let brand = @moonrgba.Color::decode("#0891b2")
    brand.to_oklch().encode() // "oklch(0.60891 0.11094 221.72312)"
    brand.mix(@moonrgba.Color::white(), space=@moonrgba.Space::Oklch).encode()
    // "oklch(0.80446 0.05547 221.72312)"
    brand.contrast(@moonrgba.Color::white()) // 3.6820433648230324
    brand.to_ansi256().code() // 31

    moon run src/examples/tour walks one colour through every space and every measure; moon run src/examples/theme builds a theme from one seed colour.

    #Reading and writing CSS

    Color::decode reads one value — what would sit on the right of color: — and Color::encode writes one back. Every syntax CSS Color 4 defines is read, including both the comma and the space argument lists, none for a component the author left out, and currentcolor.

    @moonrgba.Color::decode("#123").encode() // "#123"
    @moonrgba.Color::decode("rgb(10 20 30 / 50%)").encode() // "rgb(10 20 30 / 0.5)"
    @moonrgba.Color::decode("lab(50% 50% -20%)").encode() // "lab(50 62.5 -25)"
    @moonrgba.Color::decode("color(display-p3 .5 .2 .1)").encode()
    // "color(display-p3 0.5 0.2 0.1)"
    @moonrgba.Color::decode("currentcolor").encode() // "currentcolor"

    Writing is as short as it can be and still read back as the same colour: #f00 rather than #ff0000, no alpha when it is one, no deg on a hue, five decimals where a channel is a fraction. When no spelling is both short and exact — a channel of 127.5 has no hexadecimal form — the writer takes the longer one instead of rounding the colour away.

    A colour that cannot be read raises Refused, which says where it stopped and what it wanted:

    let refused : @moonrgba.Refused = try {
    ignore(@moonrgba.Color::decode("rgb(1 2)"))
    abort("unreachable")
    } catch {
    error => error
    }
    println("\{refused}") // at 7: got ), want a number

    Unexpected(at~, got~, want~) is a syntax error and Unknown(at~, got~) a name or function that is not a colour, so a caller reading a theme file can tell them apart without reading the message. decode_opt is the same reader with the refusal turned into None.

    SyntaxReadWriteWritten by encode()
    #rgb, #rgba, #rrggbb, #rrggbbaayesyessRGB that is a whole byte per channel
    rgb() rgba(), comma argumentsyes—the form belongs to an earlier grammar
    rgb() rgba(), space argumentsyesyessRGB that is not a whole byte per channel
    hsl() hsla()yesyesa colour written in HSL
    hwb()yesyesa colour written in HWB
    lab(), lch()yesyesa colour written in Lab or LCh
    oklab(), oklch()yesyesa colour written in Oklab or OkLCh
    color(...)yesyesa colour in one of the eight spaces it names
    the 148 named coloursyeswith Format::Named
    transparentyesas #0000
    currentcoloryesyes

    encode takes a Format when the caller wants a particular one — Hex, Rgb, Hsl, Hwb, Lab, Lch, Oklab, Oklch, Color, Named — and converts first if the colour is written in another space.

    Not read: calc(), var(), env(), attr(), the relative colour syntax (rgb(from ...)) and the system colours (Canvas, ButtonText). The first four need a cascade before they are a colour, and the last takes its value from the user's settings.

    #Colour spaces

    A colour is three components and an alpha, and the space says what the three mean. to_* converts, and comes back total: a colour outside its space's gamut keeps its value, and nothing is clipped behind the caller's back.

    let hsl = @moonrgba.Color::hsl(210.0, 0.5, 0.4)
    hsl.to_oklch().encode() // "oklch(0.49931 0.09866 250.43306)"
    hsl.to_srgb().components() // (0.2, 0.4, 0.6)

    SpaceComponentsReached through
    Srgbred, green, blue 0–1the hub for the HSL family
    SrgbLinearred, green, blue 0–1D65 XYZ
    Hsl, Hsv, Hwbhue in degrees, then two fractionssRGB
    DisplayP3, DisplayP3Linear, Rec2020, A98Rgbred, green, blue 0–1D65 XYZ, own matrix and curve
    ProPhotored, green, blue 0–1D50 XYZ, then Bradford to D65
    Lab, Lchlightness 0–100, then a and b or chroma and hueD50 XYZ
    Oklab, Oklchlightness 0–1, then a and b or chroma and hueD65 XYZ through LMS
    XyzD50, XyzD65X, Y, Z, Y = 1 for whiteBradford between the two
    YCbCrluma 0–1, chroma −0.5–0.5sRGB, with BT.601, BT.709 or BT.2020 coefficients
    Current—the unresolved currentcolor keyword

    YCbCr keeps the coefficients it was written with, so to_ycbcr(luma=Bt601) and to_ycbcr(luma=Bt2020) are different colours and not the same numbers.

    CMYK is four inks in a type of its own, because four do not fit in three components: Color::to_cmyk reduces to the ink combination with the most key, and Cmyk::to_color brings it back.

    #Bringing a colour into a gamut

    to_gamut reduces chroma in OkLCh until the colour fits, which is what CSS Color 4 asks for; clip moves each component to the nearest edge, which is cheaper and changes the colour more.

    let wide = @moonrgba.Color::oklch(0.7, 0.4, 30.0)
    wide.in_gamut(space=@moonrgba.Space::Srgb) // false
    wide.to_srgb().encode() // "color(srgb 1.31965 -0.44982 -0.31009)"
    wide.clip(space=@moonrgba.Space::Srgb).encode() // "#f00"
    wide.to_gamut(space=@moonrgba.Space::Srgb).encode() // "rgb(255 88.00945 67.46665)"
    wide.to_gamut(space=@moonrgba.Space::Srgb).to_oklch().encode()
    // "oklch(0.6831 0.2062 30.18163)"

    A colour already inside comes back untouched, so mapping twice is mapping once. A colour that no amount of chroma reduction can reach — a saturated blue in sRGB — falls back to clipping, which is the last step of the same algorithm.

    #Mixing and adjusting

    mix is color-mix(): the interpolation space decides where the average is taken, the weight decides how much of each colour it has, and alpha takes part premultiplied so that a nearly transparent colour does not drag the result towards its own hue. A missing component takes the other colour's value first.

    let red = @moonrgba.Color::rgb(255, 0, 0)
    let blue = @moonrgba.Color::rgb(0, 0, 255)
    red.mix(blue, space=@moonrgba.Space::Srgb).encode() // "rgb(127.5 0 127.5)"
    red.mix(blue, space=@moonrgba.Space::Oklab, weight=0.3).encode()
    // "oklab(0.5048 0.04474 -0.18032)"
    @moonrgba.Color::hsl(350.0, 1.0, 0.5)
    .mix(@moonrgba.Color::hsl(10.0, 1.0, 0.5), space=@moonrgba.Space::Hsl, hue=Longer)
    .hue() // 180

    Hue is Shorter, Longer, Increasing or Decreasing, and decides which way round the circle a mix travels.

    lighten, darken, saturate and desaturate nudge one component of the colour's own space — a tenth of that space's scale — and fade takes alpha away.

    #Contrast

    Two measures, because the field has two. contrast is the WCAG 2 ratio, from 1 to 21 and symmetric. apca is the signed lightness contrast of APCA 0.98G-4g, from about −108 to 108, with the text first:

    let ink = @moonrgba.Color::black()
    let paper = @moonrgba.Color::white()
    ink.contrast(paper) // 21
    ink.apca(paper) // 106.04067321268862
    paper.apca(ink) // -107.88473318309848

    #Colour difference

    delta_e measures how far apart two colours are, by one of four accepted answers. They are not comparable with each other: 2 is barely visible under E2000 and obvious under E76.

    MeasureSpaceNote
    E76Labthe straight-line distance
    E94Labgraphic-arts weights; the first colour is the reference
    E2000Labthe current CIE recommendation
    OkOklabno weights needed; a just-noticeable difference is 0.02

    #Terminal colours

    Three tiers, and a nearest match by ΔE OK rather than by arithmetic on the channels, because the palette is uneven: the cube is coarse near black and fine near white, and a distance in sRGB would not know that.

    let brand = @moonrgba.Color::decode("#0891b2")
    brand.to_ansi16() // cyan
    brand.to_ansi256().code() // 31
    brand.rgb8() // (8, 145, 178)
    @moonrgba.Ansi256::of(196).color().encode() // "#f00"

    The sixteen colours are the ones xterm uses, which is what terminals show.

    #The value

    pub(all) struct Color {
    space : Space
    x : Double
    y : Double
    z : Double
    alpha : Double
    }

    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, carried as a NaN, which is what CSS means by the keyword and what interpolation then fills in from the other side.

    Show writes the shortest CSS, so a colour interpolated into a log line or an error message is something a browser would accept:

    println("\{@moonrgba.Color::decode("#0891b2")}") // #0891b2

    #Interface

    CallWhat it does
    Color::decode(text)read a CSS colour; raises Refused
    Color::decode_opt(text)the same, None instead of raising
    Color::encode(format?)write CSS, in the shortest exact spelling
    Color::of(space, x, y, z, alpha?)a colour from components already in a space
    Color::srgb, rgb, hsl, hwb, lab, lch, oklab, oklch, xyz_d65, xyz_d50, display_p3, rec2020, a98_rgb, prophoto, ycbcra colour in that space
    Color::of_name, Color::namethe 148 names, both ways
    Color::to_srgb, to_hsl, to_lab, to_oklch and the rest, to_spacethe same colour in another space
    Color::in_gamut, clip, to_gamutdecide what to do with a colour a display cannot show
    Color::mix, lighten, darken, saturate, desaturate, fadecombining and nudging
    Color::contrast, apca, luminancehow readable a pair is
    Color::delta_ehow far apart two colours are
    Color::to_ansi16, to_ansi256, rgb8into a terminal's palette
    Color::to_cmyk, Cmyk::to_colorinto print, and back
    Color::components, hue, chroma, lightness, saturation, with_components, with_alpha, resolvelooking inside, and putting it back together
    Space::code, Space::of, is_polar, is_predefined, has_gamutwhat a space is

    #Licence

    Apache-2.0. See LICENSE.

    Refused

    pub(all) suberror Refused {
    Unexpected(at~ : Int, got~ : String, want~ : String)
    Unknown(at~ : Int, got~ : String)
    } derive(Eq,
    Debug
    )

    Why a string is not a CSS colour.
    impl Show for Refused

    Refused::equal

    fn Refused::equal(Refused, Refused) -> Bool

    Refused::not_equal

    fn Refused::not_equal(x : Refused, y : Refused) -> Bool

    Refused::output

    fn Refused::output(self : Refused, logger : &Logger) -> Unit

    Refused::to_repr

    Refused::to_string

    fn Refused::to_string(self : Refused) -> String

    Ansi16

    pub(all) enum Ansi16 {
    Black
    Red
    Green
    Yellow
    Blue
    Magenta
    Cyan
    White
    BrightBlack
    BrightRed
    BrightGreen
    BrightYellow
    BrightBlue
    BrightMagenta
    BrightCyan
    BrightWhite
    } derive(Eq,
    Debug
    )

    The sixteen colours a terminal names, in the order their escape sequences number them.
    impl Show for Ansi16

    Ansi16::code

    fn Ansi16::code(self : Ansi16) -> Int

    Where a named colour sits in the palette, which is also the low nibble of the escape sequence that selects it.

    Ansi16::color

    fn Ansi16::color(self : Ansi16) -> Color

    The sRGB colour of a named slot: xterm's defaults.

    Ansi16::equal

    fn Ansi16::equal(Ansi16, Ansi16) -> Bool

    Ansi16::not_equal

    fn Ansi16::not_equal(x : Ansi16, y : Ansi16) -> Bool

    Ansi16::of

    fn Ansi16::of(code : Int) -> Ansi16?

    The colour a palette slot stands for, if the number names one.

    Ansi16::output

    fn Ansi16::output(self : Ansi16, logger : &Logger) -> Unit

    Ansi16::to_repr

    Ansi16::to_string

    fn Ansi16::to_string(self : Ansi16) -> String

    Ansi256

    pub(all) struct Ansi256 {
    code : Int
    } derive(Eq,
    Debug
    )

    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.
    impl Show for Ansi256

    Ansi256::code

    fn Ansi256::code(self : Ansi256) -> Int

    The number to put in an escape sequence.

    Ansi256::color

    fn Ansi256::color(self : Ansi256) -> Color

    The colour a slot stands for.

    Ansi256::equal

    fn Ansi256::equal(Ansi256, Ansi256) -> Bool

    Ansi256::not_equal

    fn Ansi256::not_equal(x : Ansi256, y : Ansi256) -> Bool

    Ansi256::of

    fn Ansi256::of(code : Int) -> 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.

    Ansi256::output

    fn Ansi256::output(self : Ansi256, logger : &Logger) -> Unit

    Ansi256::to_repr

    Ansi256::to_string

    fn Ansi256::to_string(self : Ansi256) -> String

    Cmyk

    pub(all) struct Cmyk {
    c : Double
    m : Double
    y : Double
    k : Double
    } derive(Eq,
    Debug
    )

    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.

    Cmyk::equal

    fn Cmyk::equal(Cmyk, Cmyk) -> Bool

    Cmyk::not_equal

    fn Cmyk::not_equal(x : Cmyk, y : Cmyk) -> Bool

    Cmyk::of

    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

    fn Cmyk::to_color(self : Cmyk, alpha? : Double) -> Color

    A colour from CMYK inks, in sRGB.

    Cmyk::to_repr

    Color

    pub(all) struct Color {
    space : Space
    x : Double
    y : Double
    z : Double
    alpha : Double
    } derive(
    Debug
    )

    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.
    impl Show for Color

    Color::a98_rgb

    fn Color::a98_rgb(r : Double, g : Double, b : Double, alpha? : Double) -> Color

    Adobe RGB (1998), components in 0–1.

    Color::apca

    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.

    Color::black

    fn Color::black() -> Color

    Black in sRGB.

    Color::chroma

    fn Color::chroma(self : Color) -> Double

    The chroma, or NaN where the space has none.

    Color::clip

    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.

    Color::components

    fn Color::components(self : Color) -> (Double, Double, Double)

    The three components, in the order the space writes them.

    Color::contrast

    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.

    Color::current

    fn Color::current() -> Color

    The currentcolor keyword.

    Color::darken

    fn Color::darken(self : Color, amount : Double) -> Color

    Move the colour towards black by amount, a fraction of its space's lightness scale.

    Color::decode

    fn Color::decode(text : StringView) -> Color raise Refused

    A <color>: everything from the first non-blank to the closing bracket.

    Color::decode_opt

    fn Color::decode_opt(text : StringView) -> Color?

    Read a colour, or None where the text is not one.

    Color::delta_e

    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%.

    Color::desaturate

    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.

    Color::display_p3

    fn Color::display_p3(r : Double, g : Double, b : Double, alpha? : Double) -> Color

    Display P3, components in 0–1.

    Color::encode

    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.

    Color::fade

    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.

    Color::hsl

    fn Color::hsl(h : Double, s : Double, l : Double, alpha? : Double) -> Color

    HSL: hue in degrees, saturation and lightness in 0–1.

    Color::hsv

    fn Color::hsv(h : Double, s : Double, v : Double, alpha? : Double) -> Color

    HSV: hue in degrees, saturation and value in 0–1.

    Color::hue

    fn Color::hue(self : Color) -> Double

    The hue in degrees, or NaN where the space has none.

    Color::hwb

    fn Color::hwb(h : Double, w : Double, b : Double, alpha? : Double) -> Color

    HWB: hue in degrees, whiteness and blackness in 0–1.

    Color::in_gamut

    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.

    Color::is_current

    fn Color::is_current(self : Color) -> Bool

    Whether this is the unresolved currentcolor keyword.

    Color::lab

    fn Color::lab(l : Double, a : Double, b : Double, alpha? : Double) -> Color

    CIE Lab with a D50 white: lightness 0–100.

    Color::lch

    fn Color::lch(l : Double, c : Double, h : Double, alpha? : Double) -> Color

    CIE LCh with a D50 white: lightness 0–100, hue in degrees.

    Color::lighten

    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.

    Color::lightness

    fn Color::lightness(self : Color) -> Double

    The lightness, or NaN where the space has none.

    Color::luminance

    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.

    Color::mix

    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.

    Color::name

    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.

    Color::of

    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.

    Color::of_name

    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.

    Color::oklab

    fn Color::oklab(l : Double, a : Double, b : Double, alpha? : Double) -> Color

    Oklab: lightness 0–1.

    Color::oklch

    fn Color::oklch(l : Double, c : Double, h : Double, alpha? : Double) -> Color

    OkLCh: lightness 0–1, hue in degrees.

    Color::output

    fn Color::output(self : Color, logger : &Logger) -> Unit

    Color::prophoto

    fn Color::prophoto(r : Double, g : Double, b : Double, alpha? : Double) -> Color

    ProPhoto RGB, components in 0–1, D50 white.

    Color::rec2020

    fn Color::rec2020(r : Double, g : Double, b : Double, alpha? : Double) -> Color

    Rec. 2020, components in 0–1.

    Color::resolve

    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.

    Color::rgb

    fn Color::rgb(red : Int, green : Int, blue : Int, alpha? : Double) -> Color

    sRGB from bytes, 0–255, which is how a colour usually arrives from a file, a theme or a terminal.

    Color::rgb8

    fn Color::rgb8(self : Color) -> (Int, Int, Int)

    The colour as the three bytes a true-colour escape sequence carries, 0–255 each, clipped the way a terminal clips.

    Color::saturate

    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.

    Color::saturation

    fn Color::saturation(self : Color) -> Double

    The saturation, or NaN where the space has none.

    Color::srgb

    fn Color::srgb(r : Double, g : Double, b : Double, alpha? : Double) -> Color

    sRGB from components in 0–1.

    Color::srgb_linear

    fn Color::srgb_linear(r : Double, g : Double, b : Double, alpha? : Double) -> Color

    Linear-light sRGB from components in 0–1.

    Color::to_a98_rgb

    fn Color::to_a98_rgb(self : Color) -> Color

    Adobe RGB (1998), components 0–1.

    Color::to_ansi16

    fn Color::to_ansi16(self : Color) -> Ansi16

    The named slot closest to this colour by ΔE OK.

    Color::to_ansi256

    fn Color::to_ansi256(self : Color) -> Ansi256

    The slot of the 256-colour palette closest to this colour by ΔE OK.

    Color::to_cmyk

    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.

    Color::to_display_p3

    fn Color::to_display_p3(self : Color) -> Color

    Display P3, components 0–1.

    Color::to_gamut

    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.

    Color::to_hsl

    fn Color::to_hsl(self : Color) -> Color

    HSL: hue in degrees, saturation and lightness 0–1.

    Color::to_hsv

    fn Color::to_hsv(self : Color) -> Color

    HSV: hue in degrees, saturation and value 0–1.

    Color::to_hwb

    fn Color::to_hwb(self : Color) -> Color

    HWB: hue in degrees, whiteness and blackness 0–1.

    Color::to_lab

    fn Color::to_lab(self : Color) -> Color

    CIE Lab relative to D50, lightness 0–100.

    Color::to_lch

    fn Color::to_lch(self : Color) -> Color

    CIE LCh relative to D50, lightness 0–100.

    Color::to_oklab

    fn Color::to_oklab(self : Color) -> Color

    Oklab, lightness 0–1.

    Color::to_oklch

    fn Color::to_oklch(self : Color) -> Color

    OkLCh, lightness 0–1.

    Color::to_prophoto

    fn Color::to_prophoto(self : Color) -> Color

    ProPhoto RGB, components 0–1.

    Color::to_rec2020

    fn Color::to_rec2020(self : Color) -> Color

    Rec. 2020, components 0–1.

    Color::to_repr

    Color::to_space

    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.

    Color::to_srgb

    fn Color::to_srgb(self : Color) -> Color

    sRGB, components 0–1.

    Color::to_srgb_linear

    fn Color::to_srgb_linear(self : Color) -> Color

    Linear-light sRGB, components 0–1.

    Color::to_string

    fn Color::to_string(self : Color) -> String

    Color::to_xyz_d50

    fn Color::to_xyz_d50(self : Color) -> Color

    CIE XYZ relative to D50, Y = 1 for white.

    Color::to_xyz_d65

    fn Color::to_xyz_d65(self : Color) -> Color

    CIE XYZ relative to D65, Y = 1 for white.

    Color::to_ycbcr

    fn Color::to_ycbcr(self : Color, luma~ : Luma) -> Color

    YCbCr with the given luma coefficients: luma 0–1, chroma −0.5–0.5.

    Color::transparent

    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.

    Color::white

    fn Color::white() -> Color

    White in sRGB.

    Color::with_alpha

    fn Color::with_alpha(self : Color, alpha : Double) -> Color

    The same colour at a different alpha.

    Color::with_components

    fn Color::with_components(self : Color, x : Double, y : Double, z : Double) -> Color

    The same colour with different components, in the same space.

    Color::xyz_d50

    fn Color::xyz_d50(x : Double, y : Double, z : Double, alpha? : Double) -> Color

    CIE XYZ relative to D50, Y = 1 for white.

    Color::xyz_d65

    fn Color::xyz_d65(x : Double, y : Double, z : Double, alpha? : Double) -> Color

    CIE XYZ relative to D65, Y = 1 for white.

    Color::ycbcr

    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.

    Delta

    pub(all) enum Delta {
    E76
    E94
    E2000
    Ok
    } derive(Eq,
    Debug
    )

    Which colour difference to measure. The name is the question, not the space: three of these are computed in Lab and one in Oklab.
    impl Show for Delta

    Delta::equal

    fn Delta::equal(Delta, Delta) -> Bool

    Delta::not_equal

    fn Delta::not_equal(x : Delta, y : Delta) -> Bool

    Delta::output

    fn Delta::output(self : Delta, logger : &Logger) -> Unit

    Delta::to_repr

    Delta::to_string

    fn Delta::to_string(self : Delta) -> String

    Format

    pub(all) enum Format {
    Shortest
    Hex
    Rgb
    Hsl
    Hwb
    Lab
    Lch
    Oklab
    Oklch
    Color
    Named
    } derive(Eq,
    Debug
    )

    How to write a colour out.
    impl Show for Format

    Format::equal

    fn Format::equal(Format, Format) -> Bool

    Format::not_equal

    fn Format::not_equal(x : Format, y : Format) -> Bool

    Format::output

    fn Format::output(self : Format, logger : &Logger) -> Unit

    Format::to_repr

    Format::to_string

    fn Format::to_string(self : Format) -> String

    Hue

    pub(all) enum Hue {
    Shorter
    Longer
    Increasing
    Decreasing
    } derive(Eq,
    Debug
    )

    Which way round the hue circle an interpolation travels.
    impl Show for Hue

    Hue::equal

    fn Hue::equal(Hue, Hue) -> Bool

    Hue::not_equal

    fn Hue::not_equal(x : Hue, y : Hue) -> Bool

    Hue::output

    fn Hue::output(self : Hue, logger : &Logger) -> Unit

    Hue::to_repr

    Hue::to_string

    fn Hue::to_string(self : Hue) -> String

    Luma

    pub(all) enum Luma {
    Bt601
    Bt709
    Bt2020
    } derive(Eq,
    Debug
    )

    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.

    Luma::equal

    fn Luma::equal(Luma, Luma) -> Bool

    Luma::not_equal

    fn Luma::not_equal(x : Luma, y : Luma) -> Bool

    Luma::to_repr

    Space

    pub(all) enum Space {
    Srgb
    SrgbLinear
    Hsl
    Hsv
    Hwb
    Lab
    Lch
    Oklab
    Oklch
    XyzD50
    XyzD65
    DisplayP3
    DisplayP3Linear
    Rec2020
    A98Rgb
    ProPhoto
    YCbCr(Luma)
    Current
    } derive(Eq,
    Debug
    )

    The space a colour's three components are read in.

    spacexyz
    Srgb, SrgbLinear, DisplayP3, Rec2020, A98Rgb, ProPhotoredgreenblue
    Hslhue, degreessaturationlightness
    Hsvhue, degreessaturationvalue
    Hwbhue, degreeswhitenessblackness
    Lablightness, 0–100ab
    Lchlightness, 0–100chromahue, degrees
    Oklablightness, 0–1ab
    Oklchlightness, 0–1chromahue, degrees
    XyzD50, XyzD65XYZ
    YCbCrlumablue differencered 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.
    impl Show for Space

    Space::code

    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::equal

    fn Space::equal(Space, Space) -> Bool

    Space::has_gamut

    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

    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

    fn Space::is_predefined(self : Space) -> Bool

    Whether this space can be written inside CSS's color().

    Space::not_equal

    fn Space::not_equal(x : Space, y : Space) -> Bool

    Space::of

    fn Space::of(code : StringView) -> Space?

    The space an identifier names, if any.

    Space::output

    fn Space::output(self : Space, logger : &Logger) -> Unit

    Space::to_repr

    Space::to_string

    fn Space::to_string(self : Space) -> String

    names_all

    fn names_all() -> Array[StringView]

    All the names, in the order the table keeps them.

    names_count

    fn names_count() -> Int

    The number of names, which CSS Color 4 fixes at 148.