1. Introduction
Many APIs on the Web platform need to be able to accept color input
and provide color output
in a format more structured than CSS <color>
strings.
Furthermore, authors often need to perform computations on color values,
such as manipulating components in a variety of color spaces,
computing color differences,
evaluating whether color pairs have sufficient contrast,
or interpolating two colors (regardless of what color space they are specified in).
The API presented in this document aims to address all of the common use cases, and provide extensibility points to enable more complex use cases to be expressed.
2. Color
class
Color
objects represent ...TBD
[Exposed=*]interface { // Default constructor
Color ( (
constructor CSSOMString or ColorSpace ),
colorSpace sequence <double >,
coords optional double = 1 ); // Parse CSS color
alpha (
constructor CSSOMString ); // Clone color instance or create from JSON-style object
cssText ((
constructor Color or CSSColorValue or ColorJSON ));
color attribute ColorSpace ;
colorSpace attribute ObservableArray <double >;
coords attribute double ; // Get/set coordinates (in this or other color spaces)
alpha double ((
get CSSOMString or ColorSpace ), (
colorSpace CSSOMString or unsigned short ));
coord double get ((CSSOMString or unsigned short ));
coord Color ( (
set CSSOMString or ColorSpace ), (
colorSpace CSSOMString or unsigned short ), (
coord double or relativeCoordCallback ));
value Color ( (
set CSSOMString or unsigned short ), (
coord double or relativeCoordCallback ));
value Color ((
set CSSOMString or ColorSpace ),
colorSpace object );
values Color (
set object ); // Convert to another color space
values Color (
to CSSOMString ); // Check whether a color is in gamut of a given color space
colorspace boolean inGamut (optional CSSOMString ); // Bring a color into gamut of a given colorspace
colorspace Color toGamut (optional (CSSOMString or ColorSpace ),
colorSpace optional ToGamutOptions = {} );
options stringifier ;ColorJSON (); // Color difference
toJSON double (
deltaE Color ,
color optional DeltaEMethod ); };
method dictionary {
ToGamutOptions coordReference ?= "lch.c"; };
method dictionary { (
coordReference CSSOMString or ColorSpace );
colorSpace CSSOMString ; }; // TODO: we want authors to be able to extend this // If we keep it an enum, the only way to add custom deltaE methods // is a separate method.
name enum {
DeltaEMethod , // fast, but limited accuracy
"76" , // slower, but accurate };
"2000" dictionary { (
ColorJSON CSSOMString or ColorSpace );
colorSpace sequence <double >;
coords double ; };
alpha callback =
relativeCoordCallback double (double );
coord
new Color(colorspace, coords, alpha)
constructor steps are:
-
Look up the
ColorSpace
object in the registry using thecolorspace
parameter.-
If the result is
null
, throw aTypeError
-
Otherwise, set the color’s color space to it
-
-
If
coords
is not provided, create an array of zeroes with length equal to the number of coordinates incolorspace
. -
If
coords
is provided:-
If it’s not an array, throw a
TypeError
. -
Create a clone of the array.
-
If its length is greater than the number of coordinates in the color space, trim the excess numbers from the end.
-
If its length is smaller than the number of coordinates in the color space, pad it with zeroes
-
Set the color’s
coords
to the cloned array.
-
-
If
alpha
is not a number, coerce it to a number, then set the color’salpha
to this number.
get(coord)
method
of Color
objects must,
when called on this:
-
If there are two arguments, set refSpace to
ColorSpace.get(colorSpace)
. -
If there is only one argument, set refSpace to
ColorSpace.get(this.colorSpace)
-
Let
color = this.to(refSpace)
-
Return
color.coords[refSpace.coord]
.
to(colorSpace)
method
of Color
objects must,
when called on this:
-
Look up the color space object from the current object’s
colorSpace
specifier.
inGamut(colorSpace)
method,
when called,
must perform the following steps:
-
Let
colorSpace
be the color space object from the current object’scolorSpace
specifier. -
While the color space does not have an
inGamut
method, setcolorSpace = ColorSpace.get(colorSpace.base)
-
If
colorSpace
has aninGamut
method, returncolorSpace.inGamut(this.coords)
-
Otherwise, return
true
.
toGamut(colorSpace)
method,
when called,
must perform the following steps:
-
If
colorSpace
is not specified, it is set tothis.colorSpace
. -
Set
colorSpace
to the result ofColorSpace.get(colorSpace)
. -
If
this.inGamut(colorSpace) === true
, clone the current color and return it. -
Lookup the coordinate reference in
options.method
and -
Let
color = this.to(colorSpace)
-
While the color space does not have an
inGamut
method, setcolorSpace = ColorSpace.get(colorSpace.base)
-
If
colorSpace
has aninGamut
method, returncolorSpace.inGamut(this.coords)
-
Otherwise, return
true
.
Color space objects should not have a method that is only present sometimes. We need to do this some other way.
deltaE(color, deltaEMethod)
method,
when called,
must perform the following steps:
-
Calculate the color difference between
this
andcolor
using the method specified bydeltaEMethod
(see prose below) -
Return the result.
The value "76"
corresponds to the deltaE 76 method,
which is a fast but inaccurate method.
The value "2000"
corresponds to the deltaE 2000 method,
which is a slower but more accurate method.
Future versions of this specification may add additional methods,
or allow authors to define their own methods.
3. ColorSpace
class
[Exposed=*]interface {
ColorSpace readonly attribute CSSOMString ;
name readonly attribute ColorSpaceWhitePoint ;
white readonly attribute ColorSpace ?;
base readonly attribute sequence <CSSOMString >;
coords (
constructor CSSOMString ,
name ColorSpaceOptions ); // Register a ColorSpace object
options static undefined (
register ColorSpace ); // Creates a new ColorSpace object and registers it
colorspace static ColorSpace (
create CSSOMString ,
name ColorSpaceOptions ); // Array of names for all registered color spaces
options static readonly attribute FrozenArray <CSSOMString >; // Lookup ColorSpace object by name
names static ColorSpace ((
get CSSOMString or ColorSpace )); // Load ICC profile and create a ColorSpace object from it
name static Promise <ColorSpace >((
fromICCProfile Response or CSSOMString ),
resource ReducedColorSpaceOptions ); }; // White point x and y chromaticities
options interface {
ColorSpaceWhitePoint (
constructor double ,
x double );
y readonly attribute double ;
x readonly attribute double ;
y static readonly attribute ColorSpaceWhitePoint ;
D65 static readonly attribute ColorSpaceWhitePoint ; };
D50 dictionary { // Coordinate names and optional metadata
ReducedColorSpaceOptions record <DOMString ,ColorSpaceCoordinate >; };
coords dictionary :
ColorSpaceOptions ReducedColorSpaceOptions { (ColorSpaceWhitePoint or object );
white inGamutCallback ; // Base color space, if this is a transformation (
inGamut CSSOMString or ColorSpace )?;
base toBaseCallback ;
toBase fromBaseCallback ; };
fromBase dictionary {
ColorSpaceCoordinate ColorSpaceCoordinateType ?= "number"; // Gamut limits
type double ?;
min double ?; // Reference range
max double ?;
refMin double ?; };
refMax enum {
ColorSpaceCoordinateType ,
"angle" };
"number" callback =
inGamutCallback boolean (sequence <double >);
coords callback =
toBaseCallback sequence <double > (sequence <double >);
coords callback =
fromBaseCallback sequence <double > (sequence <double >);
coords
Color space coordinates must specify at least one of:
-
type: "angle"
-
min
andmax
-
refMin
andrefMax
The ColorSpaceWhitePoint
interface represents the xy chromaticity coordinates of
a white point. For convenience, D50 and D65 are predefined as follows:
-
ColorSpaceWhitePoint.D50
is set tonew ColorSpaceWhitePoint(0.3457, 0.3585)
-
ColorSpaceWhitePoint.D65
is set tonew ColorSpaceWhitePoint(0.3127, 0.3290)
get(name)
function:
-
If the argument is a
ColorSpace
object, return it -
If the argument is a string, look up that string in the internal registry of ColorSpace objects.
-
If a ColorSpace object is found, return it.
-
Otherwise, throw a
ReferenceError
(Color space does not exist)
-
4. Algorithms
4.1. Getting and setting coordinates
The color.get()
and color.set()
methods allow authors to read/write coordinates
in the current color space or even other color spaces.
Color spaces can be provided either as a string (color space id) or a CoorSpace object.
Coordinates can be specified either as a name, or as a numerical index.
color.set(coord, value)
also accepts a value.
If the value is a function, it is invoked immediately,
with the result of color.get(coord)
being passed as the first argument.
If the result is a number, the corresponding coordinate is set to it.
4.2. Color space lookup
Color spaces can be looked up either by ColorSpace
object, or by name
.
Implementations are expected to maintain an internal Map
registry of color space names to objects, for fast lookups.
To look up a color space, follow the following steps:
-
If
needle
is aColorSpace
object, letneedle = needle.name
-
If
needle
is aUSVString
, look up if there is an entry with that key in the internal Map of color names toColorSpace
objects. -
Return the
ColorSpace
object, ornull
, if none is found
4.3. Converting between color spaces
To convert a color from color space A to color space B, perform the following steps:
-
If
A.name === B.name
, clone the color and return it -
Let coords =
A.toBase(color.coords)
. IfA.base === B.name
, returnnew Color(B, coords, color.alpha)
-
While
A.base !== "xyz"
:-
Let coords =
A.toBase(color.coords)
. -
If
A.base === B.name
, returnnew Color(B, coords, color.alpha)
-
Otherwise, let
A = ColorSpace.get(A.base)
-
-
Follow B’s base chain until
"xyz"
as well, and store the result in an array. -
Starting from the end, let
coords = B.fromBase(coords)
on each of these colorspaces -
Return
new Color(B, coords, color.alpha)
Note: While this seems complicated in the general case, in virtually every real case the base chain has a length of max 3, so the algorithm would end very quickly.
4.4. Registering a color space
TBD.
Should throw if base
chain doesn’t resolve to "xyz"
eventually.
Should throw if name
exists.
5. Security Considerations
There are no known security issues introduced by these features.
6. Privacy Considerations
There are no known privacy issues introduced by these features.