Color API

A Collection of Interesting Ideas,

This version:
https://wicg.github.io/color-api
Issue Tracking:
GitHub
Inline In Spec
Editors:
Chris Lilley (W3C)
Lea Verou (Invited Expert)
Tab Atkins Jr. (Google)

Abstract

A color space agnostic class for color specification, manipulation, and conversion.

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.

wicg/color-api/1Expand introduction

2. API objects

2.1. Color objects

Color objects represent ...TBD

[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
interface Color {
  // Default constructor
  constructor(
    (CSSOMString or ColorSpace) colorSpace,
    sequence<double> coords,
    optional double alpha = 1
  );

  // Parse CSS color
  constructor(CSSOMString cssText);

  // Clone color instance or create from JSON-style object
  constructor((Color or CSSColorValue or ColorJSON) color);

  attribute CSSOMString colorSpace;
  attribute ObservableArray<double> coords;
  attribute double alpha;

  // Get/set coordinates (in this or other color spaces)
  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 values);

  // Convert to another color space
  Color to(CSSOMString colorspace);

  // Check whether a color is in gamut of a given color space
  boolean inGamut(optional CSSOMString colorspace);

  // Bring a color into gamut of a given colorspace
  Color toGamut(optional CSSOMString colorSpace, optional ToGamutOptions options);

  stringifier;

  ColorJSON toJSON();

  // Color difference
  double deltaE(Color color, optional DeltaEMethod method);
};

dictionary ToGamutOptions {
  coordReference? method = "lch.c";
};

dictionary coordReference {
  (CSSOMString or ColorSpace) colorSpace;
  CSSOMString name;
};

// 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.
enum DeltaEMethod {
  "76",    // fast, but limited accuracy
  "2000",  // slower, but accurate
};

dictionary ColorJSON {
  CSSOMString colorSpace;
  sequence<double> coords;
  double alpha;
};

callback relativeCoordCallback = double (double coord);
The new Color(colorspace, coords, alpha) constructor steps are:
  1. Look up the ColorSpace object in the registry using the colorspace parameter.

    1. If the result is null, throw a TypeError

    2. Otherwise, set the color’s color space to it

  2. If coords is not provided, create an array of zeroes with length equal to the number of coordinates in colorspace.

  3. If coords is provided:

    1. If it’s not an array, throw a TypeError.

    2. Create a clone of the array.

    3. If its length is greater than the number of coordinates in the color space, trim the excess numbers from the end.

    4. If its length is smaller than the number of coordinates in the color space, pad it with zeroes

    5. Set the color’s coords to the cloned array.

  4. If alpha is not a number, coerce it to a number, then set the color’s alpha to this number.

The get(coord) method of Color objects must, when called on this:
  1. If there are two arguments, set refSpace to ColorSpace.get(colorSpace).

  2. If there is only one argument, set refSpace to ColorSpace.get(this.colorSpace)

  3. Let color = this.to(refSpace)

  4. Return color.coords[refSpace.coord].

The to(colorSpace) method of Color objects must, when called on this:
  1. Look up the color space object from the current object’s colorSpace specifier.

The inGamut(colorSpace) method, when called, must perform the following steps:
  1. Let colorSpace be the color space object from the current object’s colorSpace specifier.

  2. While the color space does not have an inGamut method, set colorSpace = ColorSpace.get(colorSpace.base)

  3. If colorSpace has an inGamut method, return colorSpace.inGamut(this.coords)

  4. Otherwise, return true.

The toGamut(colorSpace) method, when called, must perform the following steps:
  1. Let colorSpace be the color space object from the current object’s colorSpace specifier.

  2. If this.inGamut(colorSpace) === true, clone the current color and return it.

  3. Lookup the coordinate reference in options.method and

  4. Let color = this.to()

  5. While the color space does not have an inGamut method, set colorSpace = ColorSpace.get(colorSpace.base)

  6. If colorSpace has an inGamut method, return colorSpace.inGamut(this.coords)

  7. Otherwise, return true.

2.2. ColorSpace objects

[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
interface ColorSpace {
  readonly attribute CSSOMString name;
  readonly attribute CSSOMString? iccProfile;
  readonly attribute ColorSpaceWhitePoint white;
  readonly attribute ColorSpace? base;
  readonly attribute sequence<CSSOMString> coords;

  constructor(CSSOMString name, ColorSpaceOptions options);

  // Register a ColorSpace object
  static undefined register(ColorSpace colorspace);

  // Creates a new ColorSpace object and registers it
  static ColorSpace create(CSSOMString name, ColorSpaceOptions options);

  // Array of names for all registered color spaces
  static readonly attribute FrozenArray<CSSOMString> names;

  // Lookup ColorSpace object by name
  static ColorSpace get((CSSOMString or ColorSpace) name);

  // Load ICC profile and create a ColorSpace object from it
  static Promise<ColorSpace> load(CSSOMString url);
};

// White point x and y chromaticities
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 ColorSpaceOptions {
  (ColorSpaceWhitePoint or object) white;

  inGamutCallback inGamut;

  // Base color space, if this is a transformation
  (CSSOMString or ColorSpace)? base;
  toBaseCallback toBase;
  fromBaseCallback fromBase;

  sequence<CSSOMString> coords; // coord names
};

callback inGamutCallback = boolean (sequence<double> coords);
callback toBaseCallback = sequence<double> (sequence<double> coords);
callback fromBaseCallback = sequence<double> (sequence<double> coords);

wicg/color-api/4Coord ranges?
wicg/color-api/6How to declare which color space coordinates are angles?
wicg/color-api/7Matrix-based extensibility
wicg/color-api/20What to do with coordinate names when loading a ColorSpace from an ICC profile?

The ColorSpaceWhitePoint interface represents the xy chromaticity coordinates of a white point. For convenience, D50 and D65 are predefined as follows:

The get(name) function:
  1. If the argument is a ColorSpace object, return it

  2. If the argument is a string, look up that string in the internal registry of ColorSpace objects.

    1. If a ColorSpace object is found, return it.

    2. Otherwise, throw a ReferenceError (Color space does not exist)

3. Algorithms

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

wicg/color-api/8Promises

3.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:

  1. If needle is a ColorSpace object, let needle = needle.name

  2. If needle is a USVString, look up if there is an entry with that key in the internal Map of color names to ColorSpace objects.

  3. Return the ColorSpace object, or null, if none is found

3.3. Converting between color spaces

To convert a color from color space A to color space B, perform the following steps:

  1. If A.name === B.name, clone the color and return it

  2. Let coords = A.toBase(color.coords). If A.base === B.name, return new Color(B, coords, color.alpha)

  3. While A.base !== "xyz":

    1. Let coords = A.toBase(color.coords).

    2. If A.base === B.name, return new Color(B, coords, color.alpha)

    3. Otherwise, let A = ColorSpace.get(A.base)

  4. Follow B’s base chain until "xyz" as well, and store the result in an array.

  5. Starting from the end, let coords = B.fromBase(coords) on each of these colorspaces

  6. Return new Color(B, coords, color.alpha)

wicg/color-api/11Clarify later steps of colorspace conversion

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.

3.4. Registering a color space

TBD. Should throw if base chain doesn’t resolve to "xyz" eventually. Should throw if name exists.

4. Security Considerations

There are no known security issues introduced by these features.

5. Privacy Considerations

There are no known privacy issues introduced by these features.

Conformance

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSS-TYPED-OM-1]
Shane Stephens; Tab Atkins Jr.; Naina Raisinghani. CSS Typed OM Level 1. 10 April 2018. WD. URL: https://www.w3.org/TR/css-typed-om-1/
[CSSOM-1]
Simon Pieters; Glenn Adams. CSS Object Model (CSSOM). 17 March 2016. WD. URL: https://www.w3.org/TR/cssom-1/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[WebIDL]
Boris Zbarsky. Web IDL. 15 December 2016. ED. URL: https://heycam.github.io/webidl/

IDL Index

[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
interface Color {
  // Default constructor
  constructor(
    (CSSOMString or ColorSpace) colorSpace,
    sequence<double> coords,
    optional double alpha = 1
  );

  // Parse CSS color
  constructor(CSSOMString cssText);

  // Clone color instance or create from JSON-style object
  constructor((Color or CSSColorValue or ColorJSON) color);

  attribute CSSOMString colorSpace;
  attribute ObservableArray<double> coords;
  attribute double alpha;

  // Get/set coordinates (in this or other color spaces)
  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 values);

  // Convert to another color space
  Color to(CSSOMString colorspace);

  // Check whether a color is in gamut of a given color space
  boolean inGamut(optional CSSOMString colorspace);

  // Bring a color into gamut of a given colorspace
  Color toGamut(optional CSSOMString colorSpace, optional ToGamutOptions options);

  stringifier;

  ColorJSON toJSON();

  // Color difference
  double deltaE(Color color, optional DeltaEMethod method);
};

dictionary ToGamutOptions {
  coordReference? method = "lch.c";
};

dictionary coordReference {
  (CSSOMString or ColorSpace) colorSpace;
  CSSOMString name;
};

// 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.
enum DeltaEMethod {
  "76",    // fast, but limited accuracy
  "2000",  // slower, but accurate
};

dictionary ColorJSON {
  CSSOMString colorSpace;
  sequence<double> coords;
  double alpha;
};

callback relativeCoordCallback = double (double coord);

[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
interface ColorSpace {
  readonly attribute CSSOMString name;
  readonly attribute CSSOMString? iccProfile;
  readonly attribute ColorSpaceWhitePoint white;
  readonly attribute ColorSpace? base;
  readonly attribute sequence<CSSOMString> coords;

  constructor(CSSOMString name, ColorSpaceOptions options);

  // Register a ColorSpace object
  static undefined register(ColorSpace colorspace);

  // Creates a new ColorSpace object and registers it
  static ColorSpace create(CSSOMString name, ColorSpaceOptions options);

  // Array of names for all registered color spaces
  static readonly attribute FrozenArray<CSSOMString> names;

  // Lookup ColorSpace object by name
  static ColorSpace get((CSSOMString or ColorSpace) name);

  // Load ICC profile and create a ColorSpace object from it
  static Promise<ColorSpace> load(CSSOMString url);
};

// White point x and y chromaticities
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 ColorSpaceOptions {
  (ColorSpaceWhitePoint or object) white;

  inGamutCallback inGamut;

  // Base color space, if this is a transformation
  (CSSOMString or ColorSpace)? base;
  toBaseCallback toBase;
  fromBaseCallback fromBase;

  sequence<CSSOMString> coords; // coord names
};

callback inGamutCallback = boolean (sequence<double> coords);
callback toBaseCallback = sequence<double> (sequence<double> coords);
callback fromBaseCallback = sequence<double> (sequence<double> coords);


Issues Index

wicg/color-api/1Expand introduction
wicg/color-api/3Describe the other constructor signatures
wicg/color-api/4Coord ranges?
wicg/color-api/5`toGamut()` in place?
wicg/color-api/4Coord ranges?
wicg/color-api/6How to declare which color space coordinates are angles?
wicg/color-api/7Matrix-based extensibility
wicg/color-api/20What to do with coordinate names when loading a ColorSpace from an ICC profile?
wicg/color-api/8Promises
wicg/color-api/11Clarify later steps of colorspace conversion