Color computations for numberscope

To facilitate color manipulations in visualizers or in formula parameters to visualizers, the frontscope provides a Chroma class based on the npm package chroma-js. You can use this class for color mixing and creating palettes and so on, and then convert the resulting color objects (for example) to hex strings to use as arguments to the p5 sketch color method.

The Chroma color class supports all of the methods documented for the chroma-js api. Additional functions and facilities for manipulating Chroma colors are documented below.

All of the chroma-js api and operations documented here are also available in mathjs formulas. For example, you can darken a color x an amount controlled by a number x by writing c.darken(x), or desaturate it by writing c.desaturate(x), etc.

In addition, all of the named colors (like red or chartreuse, including all CSS (Cascading Style Sheets) named colors) are available as pre-defined constant symbols, as are the color brewer palettes, like RdBu or Set1. Note the palettes are arrays of colors, so to get a specific color from them in a formula you need to index them with a 1-based index, e.g., Set1[5].

chroma(...)

Rather than directly using a Chroma constructor, the recommended basic way to create a new Chroma object is via the chroma() function, which can understand a wide assortment of different argument types for flexible creation of colors. In addition to the possibilities detailed in the chroma-js api, the numberscope chroma() also supports:

  • chroma() returns opaque black
  • chroma([r: number, g: number, b: number, a: number]) gives a color with the four channels specified as numbers between 0 and 1 inclusive.
  • chroma(l: number) when l is between 0 and 1 produces an opaque greyscale color from black to white, respectively. (Note when l is an integer larger than one, this reverts to the usual chroma-js api meaning, in which the number is converted to a hex string and then interpreted as a color code.)
  • chroma(name: string, a: number) produces the same color as chroma(name) but with its alpha channel set to a.
  • If all of the arguments to the usual chroma(r: number, g: number, b: number, a?:number) signature are numbers between 0 and 1, they are interpreted on that scale, rather than the default [0...255] scale.

All of the other functions below also return Chroma objects unless otherwise noted.

rainbow(hue: bigint | number, opacity?: number)

This function conveniently allows creation of an opaque color from just a "hue angle" in color space, periodically interpreted with the main period from 0 to 360. Optionally, you can also specify an opacity for the resulting color, from 1 to 0. It uses the "oklch" color space provided by chromajs to ensure that all of the colors it returns will have approximately the same apparent lightness and hue saturation (and hopefully these levels have been selected to produce attractive colors).

isChroma(x: unknown): x is Chroma

A TypeScript type guard to discern Chroma objects.

overlay(bottom: Chroma, top: Chroma)

Returns the result of alpha-compositing top on top of bottom. For example, if top is opaque (has alpha channel equal to one), this will just give top back. This operation is available in mathjs formulas using the ordinary addition operator +, but note that it is not commutative.

ply(color: Chroma, plies: number)

Returns a newly-created Chroma object the same as color except it represents overlaying plies many times with that color. Equivalently, the chromatic components of color are left alone, and the new opacity value is given by 1 - (1 - alpha)^plies, where alpha is the opacity of color. This operation is available in mathjs formulas using the ordinary multiplication operator *, and in such formulas, the color and factor may appear in either order. With these definitions and conventions, we have such expected identities as c + c == 2*c and so on.

Combining the previous two operations in a linear combination in a mathjs formula such as red + x*chroma(blue, 0.01) provides one reasonable way to morph geometrically from red to blue as x goes from 1 to infinity, but note that when the colors used as endpoints are on opposite sides of the color wheel, the colors in the "middle" of this trajectory will be rather greyish/muddy. See the chroma-js api for other ways of creating color scales if direct alpha-compositing produces undesirable results.

dilute(color: Chroma, factor: number)

Returns a newly-created Chroma object the same as color except that its alpha value has been multiplied by factor.

Combining this with addition as in red + dilute(blue, x) provides one reasonable way to morph linearly from red to blue as x goes from 0 to 1, with the same caveats about greyish/muddy colors in the middle of the trajectory.