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 constructorColor ( (constructor CSSOMString or ColorSpace ),colorSpace sequence <double >,coords optional double = 1 ); // Parse CSS coloralpha (constructor CSSOMString ); // Clone color instance or create from JSON-style objectcssText ((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 spacevalues Color (to CSSOMString ); // Check whether a color is in gamut of a given color spacecolorspace boolean inGamut (optional CSSOMString ); // Bring a color into gamut of a given colorspacecolorspace Color toGamut (optional (CSSOMString or ColorSpace ),colorSpace optional ToGamutOptions = {} );options stringifier ;ColorJSON (); // Color differencetoJSON 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
ColorSpaceobject in the registry using thecolorspaceparameter.-
If the result is
null, throw aTypeError -
Otherwise, set the color’s color space to it
-
-
If
coordsis not provided, create an array of zeroes with length equal to the number of coordinates incolorspace. -
If
coordsis 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
coordsto the cloned array.
-
-
If
alphais not a number, coerce it to a number, then set the color’salphato 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
colorSpacespecifier.
inGamut(colorSpace) method,
when called,
must perform the following steps:
-
Let
colorSpacebe the color space object from the current object’scolorSpacespecifier. -
While the color space does not have an
inGamutmethod, setcolorSpace = ColorSpace.get(colorSpace.base) -
If
colorSpacehas aninGamutmethod, returncolorSpace.inGamut(this.coords) -
Otherwise, return
true.
toGamut(colorSpace) method,
when called,
must perform the following steps:
-
If
colorSpaceis not specified, it is set tothis.colorSpace. -
Set
colorSpaceto the result ofColorSpace.get(colorSpace). -
If
this.inGamut(colorSpace) === true, clone the current color and return it. -
Lookup the coordinate reference in
options.methodand -
Let
color = this.to(colorSpace) -
While the color space does not have an
inGamutmethod, setcolorSpace = ColorSpace.get(colorSpace.base) -
If
colorSpacehas aninGamutmethod, 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
thisandcolorusing 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 objectoptions static undefined (register ColorSpace ); // Creates a new ColorSpace object and registers itcolorspace static ColorSpace (create CSSOMString ,name ColorSpaceOptions ); // Array of names for all registered color spacesoptions static readonly attribute FrozenArray <CSSOMString >; // Lookup ColorSpace object by namenames static ColorSpace ((get CSSOMString or ColorSpace )); // Load ICC profile and create a ColorSpace object from itname static Promise <ColorSpace >((fromICCProfile Response or CSSOMString ),resource ReducedColorSpaceOptions ); }; // White point x and y chromaticitiesoptions 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 metadataReducedColorSpaceOptions 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 limitstype double ?;min double ?; // Reference rangemax 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" -
minandmax -
refMinandrefMax
The ColorSpaceWhitePoint interface represents the xy chromaticity coordinates of
a white point. For convenience, D50 and D65 are predefined as follows:
-
ColorSpaceWhitePoint.D50is set tonew ColorSpaceWhitePoint(0.3457, 0.3585) -
ColorSpaceWhitePoint.D65is set tonew ColorSpaceWhitePoint(0.3127, 0.3290)
get(name) function:
-
If the argument is a
ColorSpaceobject, 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
needleis aColorSpaceobject, letneedle = needle.name -
If
needleis aUSVString, look up if there is an entry with that key in the internal Map of color names toColorSpaceobjects. -
Return the
ColorSpaceobject, 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.