hsl(210 60% 45%)
CSS Color 4 · CSS Color 5 · MoonBit
Turn the wheel.
Read the colour.
moonrgba is a colour library for MoonBit. It reads the colours CSS writes, converts between seventeen spaces, brings a colour into a gamut, mixes and measures it, and finds its nearest terminal slot. Every number on this board comes from the compiled library, running in this page.
One colour, every way
Paste another value in the field above, or drag the dial, and both columns follow.
A cell that reads none is a component the colour does not have — a grey
has no hue, and the library says so rather than inventing a number.
as CSS
components
What it reads
The parser takes one value — what would sit on the right of color: —
and reads it the way a browser would, including the argument list it belongs to.
Two grammars, not one
The comma list and the space list are different grammars with different rules. The first separator decides which one is being read, and that one's rules apply to everything after it.
Seventeen spaces
sRGB and its linear twin, HSL, HSV, HWB, Lab, LCh, Oklab, OkLCh, Display P3, Rec. 2020, Adobe RGB, ProPhoto, both XYZ, CMYK and YCbCr — two hubs, no drift.
Into a gamut, by chroma
A colour a screen cannot show loses chroma in OkLCh until it fits. Clipping is the cheaper answer and the library keeps it, but it is not the default.
Mixing and measuring
color-mix with its four hue directions and premultiplied alpha, WCAG 2 and APCA contrast, and four colour differences that do not pretend to be each other.
A terminal's palette
The nearest named colour, the nearest slot of the 256, and the three bytes of true colour — chosen by OkLab distance, because the palette is uneven.
No I/O, four backends
Every function is a pure conversion. Nothing here reads a file, opens a socket or asks a display anything; the same code runs on all four targets.
In MoonBit
Seven calls, and the values they return. The trailing comments are the output of these exact lines, not an illustration.
let brand = @moonrgba.Color::decode("#0891b2")
brand.to_oklch().encode() // oklch(0.60891 0.11094 221.72312)
brand.contrast(@moonrgba.Color::white()) // 3.6820433648230324
brand.to_ansi16() // cyan
brand.to_ansi256().code() // 31
@moonrgba.Color::decode("rebeccapurple").encode() // #639
brand.mix(@moonrgba.Color::white(),
space=@moonrgba.Space::Oklch).encode() // oklch(0.80446 0.05547 221.72312)
Reading and writing are one pair. decode takes one value and
raises Refused, which says where it stopped and what it wanted;
decode_opt is the same reader with the refusal turned into
None.
The writer takes the shortest spelling that reads back unchanged. That is
why rebeccapurple comes out as #639: four characters,
and the same colour. Where no short form is exact — a channel of 127.5 has no
hexadecimal spelling — it writes the longer one instead of rounding the colour
away.
A missing component stays missing. none is carried as a NaN,
written back as none, and filled in from the other side when two
colours are mixed.