Towards a Color API for the Web Platform
Presenter: Lea Verou (MIT)
Duration: 13 min
Slides & video
Hi, I'm Lea Verou.
And I'm going to talk about the efforts and current status of standardizing a color API for the web platform.
But first off, why should you possibly listen to me?
I've been involved in web standards for about a decade as a CSS working group member and more recently as an elected W3C TAG member.
I'm a co-editor of CSS Color 4 and CSS Color 5, which are directly related to this.
And my day job is researching human computer interaction at MIT CSAIL, and more specifically usable programming, which is directly relevant to API design.
So, what are the platform and user needs that dictate how this API is going to be designed?
First off, one of the biggest needs is input and output for existing web APIs.
Currently we're just passing strings around, which is suboptimal for everyone.
And there's a bunch of APIs that require this.
There's the Canvas API, obviously the CSSOM, there's WebGPU, even newer proposals like the EyeDropper API and of course, <input type="color"> in HTML and whatever follows it.
Also, there are the authors needs for their own applications, they often need to parse CSS colors that their users provide as input to convert between different color spaces and manipulate different coordinates sometimes with, sometimes without gamut mapping, depending on the use case; or compute color difference and contrast and interpolate.
So, what are the requirements for such an API?
One requirement is that it needs to be color space agnostic.
It cannot be centered around sRGB or use RGB coordinates internally or anything like that.
And similarly, it needs to be compatible with both HDR and SDR.
It needs to be extensible so that authors can add more color spaces, because we cannot possibly add all of them to be built in and it should follow, it's API should follow a layered design, it should be usable by non-experts, it shouldn't require a ton of knowledge to use it, but it should still be useful to experts if possible.
Some earlier efforts centered around reusing CSSColorValue for this, CSSColorValue is part of the Typed OM and it's, the API consists of an abstract CSSColorValue class that inherits from CSS color, from CSSStyleValue and multiple subclasses for the different color types In CSS.
These subclasses may have different API shapes.
For example, CSSRGB has R, G and B properties, while CSSColor has a channels property with an array of all the coordinates.
And similarly, their constructors have different signatures as well.
CSSRGB accepts red, green and blue coordinates as positional arguments, CSSColor accepts a color space argument and an array of coordinates.
It supports a basic color space conversion and it's generally an early stage spec, no implementations yet.
So, what are the advantages of using CSSColorValue across the platform to represent any color value?
There are two obvious advantages.
That there's just one object across the entire web platform.
Authors don't need to have the overhead of converting from one object to another.
Everything accepts this one object, and it has good integration with CSS right out of the box.
These are the obvious advantages, however, there's a bunch of disadvantages as well.
The primary thing is that CSSColorValue is designed to represent CSS <color> values.
It's designed to represent syntax, not colors, not points in a color space.
So this causes a lot of warts, which are actually perfectly fine design decisions for what it was designed to do, they only become problems when you try to repurpose it to do something else.
So because it's designed to represent syntax, CSS has two ways to represent these RGB colors.
There's the RGB function and hex colors as well, like the sRGB specific formats and that corresponds to the CSS RGB function.
And there's also the color() function when the color spaces is sRGB, which is represented by the CSS color class.
So basically, you have two different ways to represent an sRGB color and if you convert any color to an sRGB color, depending on how you write the argument, if you're converting to RGB, you get a CSSRGB object, if you're converting to sRGB, you get a CSSColor object, even though both are sRGB objects.
Also, all its properties are objects, not primitives, so the keywords are not actual strings, they're CSSKeywordValue.
Strings are accepted for input, but the output is objects.
And if you need to read the actual primitive value, you have to do .value on them, which is kind of clumsy.
Similarly, the coordinates are not numbers, they are CSSNumberish objects, so you also have to do .value to read the red coordinate, to read any coordinate. (stammers) It gets quite clunky.
And coordinates may not even be actual numbers since this is designed to represent CSS syntax, since the syntax can have calc() expressions instead of numbers and this object can also represent this.
And these issues can not really be fixed by API design iteration, because they are not problems with the API design, they are great design decisions for representing CSS syntax.
If you try to change it so that it's better for representing colors, then it becomes worse for representing CSS syntax.
It can't really do both.
So for that reason, we started efforts to design a separate color API as a completely new thing.
And this started, so Chris, Lilly and I started this work in 2020.
It started by this library that we created to experiment with API design ideas and algorithms.
And even though it hasn't been officially released yet, it has received a fair bit of community input and usage and feedback and even derivative work.
So the current state of the Color API, you can find it in these URLs here.
There's a draft explainer and a draft spec.
And this API has been designed from scratch, even though it has been influenced by our Color JS work, a native API has different needs than a library.
And we also got useful feedback from Tab Atkins, who is the spec editor for CSSColorValue and we iterated on the API even more after this feedback.
So what's the current API draft?
There is a single color class, no subclasses, and it's constructors basically accept the color space coordinates and optional alpha, or a color that could be a string or a CSSColorValue or even another color instance to clone it. coords is an observable array, which means, it's mutable, it can be tweaked.
The color space property is either a string or a color space object, most likely it will be a color space object.
And alpha is just a number.
Currently they're all mutable, although there's an open issue on that.
So color spaces are represented by color space objects and color space objects can be created by authors as well, and there are predefined ones for the predefined color spaces.
They can be registered via colorspace.register and then they can be referenced by an ID, but anonymous color spaces can also be referenced by just passing objects around, and that can be useful for encapsulation, for web components to use color spaces without polluting the global namespace.
And to declare a color space, you need to declare it's white point, it's coordinates, a function for its gamut and conversion code to and from any existing color space.
Or you can load a color space from an ICC profile, which resolves to a color space object.
And color spaces cannot become unregistered once they're registered.
This is by design.
There are just a bunch of convenience methods for conversion and manipulation in any color space without having to convert the color itself.
For example, it's common to want to change the lightness, to make a color lighter without actually changing it's color space.
And all of these can be done.
There's also a conversion method to a different color space and similarly, any coordinates in any color spaces can be both read, and written.
And there's also relative manipulation supported by passing in functions.
And there's an aggregate syntax as well for performing multiple manipulations in the same color space.
Gamut mapping is explicit, all color conventions are lossless in this by default, this is important for round-tripping.
So gamut mapping is opt-in, there is an in-gamut function to check if the current color is in gamut, either of its own or another color space, and there's a toGamut function that performs gamut mapping to the gamut of any color space.
By default, this works by LCH chroma reduction, although authors can pass any coordinate in that method to use that one instead.
This is important for it to be truly color space agnostic.
Like, you might define another color space, like, OK LCH for example, and you might want to do your gamut mapping based on OK LCH, because that's better for that purpose.
It's an open problem, how to avoid people passing nonsensical coordinates for gamut mapping, like hues for example.
We had a breakout in the CSS working group on July 21st and we discussed these options to resolve on future direction.
You can read the minutes, they're published, and we resolved to add Color API for representing color points that is separate from CSSColorValue.
And that it should, as a minimum, handle all the color spaces currently specified for the web platform.
And we moved the Color API repo from a personal repo to WICG for incubation.
There's a bunch of open issues.
This is a very, very, very early stage work.
Some of them, some of the most thorny ones are how to declare polar color spaces, like, how do declare that a coordinate is an angle.
Also, what really is a color space.
Right now color models and color spaces are mixed.
Like, HSL is declared as a separate color space that is just using sRGB as its base.
It's using the sRGB gamut and it's converted to and from sRGB, but it's still syntactically a separate color space.
Is that a good idea?
Do we need to separate color models somehow?
Also, mutable or immutable?
Right now it's kind of a mix.
It's mostly mutable, but there are functions that return new instances instead.
Also, how to do HDR tone mapping.
And what should be the integration between this Color API and CSS?
What happens with registered color spaces?
Are they available in CSS as well?
And similarly, do color spaces declared in CSS by the @color-profile rule, do they become registered color space objects once the profile loads?
And how does parsing and serialization of author-defined color spaces work?
And these are only a few of the open issues, there's a lot of work to be done.
So, are you interested?
Come and help us design this!
Here's the repo.
Thank you very much.