p5
Version:
[](https://www.npmjs.com/package/p5)
1,888 lines (1,783 loc) • 64.9 kB
JavaScript
import { sRGB, P3, HSL as HSL$1, HWB as HWB$1, Lab, LCH as LCH$1, OKLab, OKLCH as OKLCH$1, ColorSpace, to, parse, range, serialize } from 'colorjs.io/fn';
import HSBSpace from './color/color_spaces/hsb.js';
/**
* @module Color
* @submodule Creating & Reading
* @for p5
* @requires core
* @requires color_conversion
*/
const map = (n, start1, stop1, start2, stop2, clamp) => {
let result = ((n - start1) / (stop1 - start1) * (stop2 - start2) + start2);
if (clamp) {
result = Math.max(result, Math.min(start2, stop2));
result = Math.min(result, Math.max(start2, stop2));
}
return result;
};
const serializationMap = {};
class Color {
// Reference to underlying color object depending on implementation
// Not meant to be used publicly unless the implementation is known for sure
_color;
// Color mode of the Color object, uses p5 color modes
mode;
static colorMap = {};
static #colorjsMaxes = {};
static #grayscaleMap = {};
// Used to add additional color modes to p5.js
// Uses underlying library's definition
static addColorMode(mode, definition){
ColorSpace.register(definition);
Color.colorMap[mode] = definition.id;
// Get colorjs maxes
Color.#colorjsMaxes[mode] = Object.values(definition.coords).reduce((acc, v) => {
acc.push(v.refRange || v.range);
return acc;
}, []);
Color.#colorjsMaxes[mode].push([0, 1]);
// Get grayscale mapping
Color.#grayscaleMap[mode] = definition.fromGray;
}
constructor(vals, colorMode, colorMaxes, { clamp = false } = {}) {
// This changes with the color object
this.mode = colorMode || RGB;
if(vals instanceof Color){
// Received Color object to be used for color mode conversion
const mode = colorMode ?
Color.colorMap[colorMode] :
Color.colorMap[vals.mode];
this._color = to(vals._color, mode);
this.mode = mode;
}else if (typeof vals === 'object' && !Array.isArray(vals) && vals !== null){
// Received color.js object to be used internally
const mode = colorMode ?
Color.colorMap[colorMode] :
vals.spaceId;
this._color = to(vals, mode);
this.mode = colorMode || Object.entries(Color.colorMap).find(([key, val]) => {
return val === this._color.spaceId;
});
} else if(typeof vals[0] === 'string') {
// Received string
try{
this._color = parse(vals[0]);
const [mode] = Object.entries(Color.colorMap).find(([key, val]) => {
return val === this._color.spaceId;
});
this.mode = mode;
this._color = to(this._color, this._color.spaceId);
}catch(err){
// TODO: Invalid color string
throw new Error('Invalid color string');
}
}else {
// Received individual channel values
let mappedVals;
if(colorMaxes){
// NOTE: need to consider different number of arguments (eg. CMYK)
if(vals.length === 4){
mappedVals = Color.mapColorRange(vals, this.mode, colorMaxes, clamp);
}else if(vals.length === 3){
mappedVals = Color.mapColorRange([vals[0], vals[1], vals[2]], this.mode, colorMaxes, clamp);
mappedVals.push(1);
}else if(vals.length === 2){
// Grayscale with alpha
if(Color.#grayscaleMap[this.mode]){
mappedVals = Color.#grayscaleMap[this.mode](vals[0], colorMaxes, clamp);
}else {
mappedVals = Color.mapColorRange([vals[0], vals[0], vals[0]], this.mode, colorMaxes, clamp);
}
const alphaMaxes = Array.isArray(colorMaxes[colorMaxes.length-1]) ?
colorMaxes[colorMaxes.length-1] :
[0, colorMaxes[colorMaxes.length-1]];
mappedVals.push(
map(
vals[1],
alphaMaxes[0],
alphaMaxes[1],
0,
1,
clamp
)
);
}else if(vals.length === 1){
// Grayscale only
if(Color.#grayscaleMap[this.mode]){
mappedVals = Color.#grayscaleMap[this.mode](vals[0], colorMaxes, clamp);
}else {
mappedVals = Color.mapColorRange([vals[0], vals[0], vals[0]], this.mode, colorMaxes, clamp);
}
mappedVals.push(1);
}else {
throw new Error('Invalid color');
}
}else {
mappedVals = vals;
}
const space = Color.colorMap[this.mode] || console.error('Invalid color mode');
const coords = mappedVals.slice(0, 3);
const color = {
space,
coords,
alpha: mappedVals[3]
};
this._color = to(color, space);
}
}
// Convert from p5 color range to color.js color range
static mapColorRange(origin, mode, maxes, clamp){
const p5Maxes = maxes.map((max) => {
if(!Array.isArray(max)){
return [0, max];
}else {
return max;
}
});
const colorjsMaxes = Color.#colorjsMaxes[mode];
return origin.map((channel, i) => {
const newval = map(channel, p5Maxes[i][0], p5Maxes[i][1], colorjsMaxes[i][0], colorjsMaxes[i][1], clamp);
return newval;
});
}
// Convert from color.js color range to p5 color range
static unmapColorRange(origin, mode, maxes){
const p5Maxes = maxes.map((max) => {
if(!Array.isArray(max)){
return [0, max];
}else {
return max;
}
});
const colorjsMaxes = Color.#colorjsMaxes[mode];
return origin.map((channel, i) => {
const newval = map(channel, colorjsMaxes[i][0], colorjsMaxes[i][1], p5Maxes[i][0], p5Maxes[i][1]);
return newval;
});
}
// Will do conversion in-Gamut as out of Gamut conversion is only really useful for futher conversions
#toColorMode(mode){
return new Color(this._color, mode);
}
// Get raw coordinates of underlying library, can differ between libraries
get _array() {
return this._getRGBA();
}
array(){
return this._array;
}
lerp(color, amt, mode){
// Find the closest common ancestor color space
let spaceIndex = -1;
while(
(
spaceIndex+1 < this._color.space.path.length ||
spaceIndex+1 < color._color.space.path.length
) &&
this._color.space.path[spaceIndex+1] === color._color.space.path[spaceIndex+1]
){
spaceIndex += 1;
}
if (spaceIndex === -1) {
// This probably will not occur in practice
throw new Error('Cannot lerp colors. No common color space found');
}
const obj = range(this._color, color._color, {
space: this._color.space.path[spaceIndex].id
})(amt);
return new Color(obj, mode || this.mode);
}
/**
* Returns the color formatted as a `String`.
*
* Calling `myColor.toString()` can be useful for debugging, as in
* `print(myColor.toString())`. It's also helpful for using p5.js with other
* libraries.
*
* The parameter, `format`, is optional. If a format string is passed, as in
* `myColor.toString('#rrggbb')`, it will determine how the color string is
* formatted. By default, color strings are formatted as `'rgba(r, g, b, a)'`.
*
* @param {String} [format] how the color string will be formatted.
* Leaving this empty formats the string as rgba(r, g, b, a).
* '#rgb' '#rgba' '#rrggbb' and '#rrggbbaa' format as hexadecimal color codes.
* 'rgb' 'hsb' and 'hsl' return the color formatted in the specified color mode.
* 'rgba' 'hsba' and 'hsla' are the same as above but with alpha channels.
* 'rgb%' 'hsb%' 'hsl%' 'rgba%' 'hsba%' and 'hsla%' format as percentages.
* @return {String} the formatted string.
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object.
* let myColor = color('darkorchid');
*
* // Style the text.
* textAlign(CENTER);
* textSize(16);
*
* // Display the text.
* text(myColor.toString('#rrggbb'), 50, 50);
*
* describe('The text "#9932cc" written in purple on a gray background.');
* }
* </code>
* </div>
*/
toString(format) {
const key = `${this._color.space.id}-${this._color.coords.join(",")}-${this._color.alpha}-${format}`;
let colorString = serializationMap[key];
if(!colorString){
colorString = serialize(this._color, {
format
});
serializationMap[key] = colorString;
}
return colorString;
}
/**
* Sets the red component of a color.
*
* The range depends on the <a href="#/p5/colorMode">colorMode()</a>. In the
* default RGB mode it's between 0 and 255.
*
* @param {Number} red the new red value.
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object.
* let c = color(255, 128, 128);
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 20, 35, 60);
*
* // Change the red value.
* c.setRed(64);
*
* // Draw the right rectangle.
* fill(c);
* rect(50, 20, 35, 60);
*
* describe('Two rectangles. The left one is salmon pink and the right one is teal.');
* }
* </code>
* </div>
*/
setRed(new_red, max=[0, 1]) {
if(!Array.isArray(max)){
max = [0, max];
}
const colorjsMax = Color.#colorjsMaxes[RGB][0];
const newval = map(new_red, max[0], max[1], colorjsMax[0], colorjsMax[1]);
if(this.mode === RGB || this.mode === RGBHDR){
this._color.coords[0] = newval;
}else {
// Will do an imprecise conversion to 'srgb', not recommended
const space = this._color.space.id;
const representation = to(this._color, 'srgb');
representation.coords[0] = newval;
this._color = to(representation, space);
}
}
/**
* Sets the green component of a color.
*
* The range depends on the <a href="#/p5/colorMode">colorMode()</a>. In the
* default RGB mode it's between 0 and 255.
*
* @param {Number} green the new green value.
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object.
* let c = color(255, 128, 128);
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 20, 35, 60);
*
* // Change the green value.
* c.setGreen(255);
*
* // Draw the right rectangle.
* fill(c);
* rect(50, 20, 35, 60);
*
* describe('Two rectangles. The left one is salmon pink and the right one is yellow.');
* }
* </code>
* </div>
*/
setGreen(new_green, max=[0, 1]) {
if(!Array.isArray(max)){
max = [0, max];
}
const colorjsMax = Color.#colorjsMaxes[RGB][1];
const newval = map(new_green, max[0], max[1], colorjsMax[0], colorjsMax[1]);
if(this.mode === RGB || this.mode === RGBHDR){
this._color.coords[1] = newval;
}else {
// Will do an imprecise conversion to 'srgb', not recommended
const space = this._color.space.id;
const representation = to(this._color, 'srgb');
representation.coords[1] = newval;
this._color = to(representation, space);
}
}
/**
* Sets the blue component of a color.
*
* The range depends on the <a href="#/p5/colorMode">colorMode()</a>. In the
* default RGB mode it's between 0 and 255.
*
* @param {Number} blue the new blue value.
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object.
* let c = color(255, 128, 128);
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 20, 35, 60);
*
* // Change the blue value.
* c.setBlue(255);
*
* // Draw the right rectangle.
* fill(c);
* rect(50, 20, 35, 60);
*
* describe('Two rectangles. The left one is salmon pink and the right one is pale fuchsia.');
* }
* </code>
* </div>
**/
setBlue(new_blue, max=[0, 1]) {
if(!Array.isArray(max)){
max = [0, max];
}
const colorjsMax = Color.#colorjsMaxes[RGB][2];
const newval = map(new_blue, max[0], max[1], colorjsMax[0], colorjsMax[1]);
if(this.mode === RGB || this.mode === RGBHDR){
this._color.coords[2] = newval;
}else {
// Will do an imprecise conversion to 'srgb', not recommended
const space = this._color.space.id;
const representation = to(this._color, 'srgb');
representation.coords[2] = newval;
this._color = to(representation, space);
}
}
/**
* Sets the alpha (transparency) value of a color.
*
* The range depends on the
* <a href="#/p5/colorMode">colorMode()</a>. In the default RGB mode it's
* between 0 and 255.
*
* @param {Number} alpha the new alpha value.
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object.
* let c = color(255, 128, 128);
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 20, 35, 60);
*
* // Change the alpha value.
* c.setAlpha(128);
*
* // Draw the right rectangle.
* fill(c);
* rect(50, 20, 35, 60);
*
* describe('Two rectangles. The left one is salmon pink and the right one is faded pink.');
* }
* </code>
* </div>
*/
setAlpha(new_alpha, max=[0, 1]) {
if(!Array.isArray(max)){
max = [0, max];
}
const colorjsMax = Color.#colorjsMaxes[this.mode][3];
const newval = map(new_alpha, max[0], max[1], colorjsMax[0], colorjsMax[1]);
this._color.alpha = newval;
}
_getRGBA(maxes=[1, 1, 1, 1]) {
// Get colorjs maxes
const colorjsMaxes = Color.#colorjsMaxes[RGB];
// Normalize everything to 0,1 or the provided range (map)
let coords = structuredClone(to(this._color, 'srgb').coords);
coords.push(this._color.alpha);
const rangeMaxes = maxes.map((v) => {
if(!Array.isArray(v)){
return [0, v];
}else {
return v
}
});
coords = coords.map((coord, i) => {
return map(coord, colorjsMaxes[i][0], colorjsMaxes[i][1], rangeMaxes[i][0], rangeMaxes[i][1]);
});
return coords;
}
_getMode() {
return this.mode;
}
_getRed(max=[0, 1]) {
if(!Array.isArray(max)){
max = [0, max];
}
if(this.mode === RGB || this.mode === RGBHDR){
const colorjsMax = Color.#colorjsMaxes[this.mode][0];
return map(this._color.coords[0], colorjsMax[0], colorjsMax[1], max[0], max[1]);
}else {
// Will do an imprecise conversion to 'srgb', not recommended
const colorjsMax = Color.#colorjsMaxes[RGB][0];
return map(to(this._color, 'srgb').coords[0], colorjsMax[0], colorjsMax[1], max[0], max[1]);
}
}
_getGreen(max=[0, 1]) {
if(!Array.isArray(max)){
max = [0, max];
}
if(this.mode === RGB || this.mode === RGBHDR){
const colorjsMax = Color.#colorjsMaxes[this.mode][1];
return map(this._color.coords[1], colorjsMax[0], colorjsMax[1], max[0], max[1]);
}else {
// Will do an imprecise conversion to 'srgb', not recommended
const colorjsMax = Color.#colorjsMaxes[RGB][1];
return map(to(this._color, 'srgb').coords[1], colorjsMax[0], colorjsMax[1], max[0], max[1]);
}
}
_getBlue(max=[0, 1]) {
if(!Array.isArray(max)){
max = [0, max];
}
if(this.mode === RGB || this.mode === RGBHDR){
const colorjsMax = Color.#colorjsMaxes[this.mode][2];
return map(this._color.coords[2], colorjsMax[0], colorjsMax[1], max[0], max[1]);
}else {
// Will do an imprecise conversion to 'srgb', not recommended
const colorjsMax = Color.#colorjsMaxes[RGB][2];
return map(to(this._color, 'srgb').coords[2], colorjsMax[0], colorjsMax[1], max[0], max[1]);
}
}
_getAlpha(max=[0, 1]) {
if(!Array.isArray(max)){
max = [0, max];
}
const colorjsMax = Color.#colorjsMaxes[this.mode][3];
return map(this._color.alpha, colorjsMax[0], colorjsMax[1], max[0], max[1]);
}
/**
* Hue is the same in HSB and HSL, but the maximum value may be different.
* This function will return the HSB-normalized saturation when supplied with
* an HSB color object, but will default to the HSL-normalized saturation
* otherwise.
*/
_getHue(max=[0, 360]) {
if(!Array.isArray(max)){
max = [0, max];
}
if(this.mode === HSB || this.mode === HSL){
const colorjsMax = Color.#colorjsMaxes[this.mode][0];
return map(this._color.coords[0], colorjsMax[0], colorjsMax[1], max[0], max[1]);
}else {
// Will do an imprecise conversion to 'HSL', not recommended
const colorjsMax = Color.#colorjsMaxes[HSL][0];
return map(to(this._color, 'hsl').coords[0], colorjsMax[0], colorjsMax[1], max[0], max[1]);
}
}
/**
* Saturation is scaled differently in HSB and HSL. This function will return
* the HSB saturation when supplied with an HSB color object, but will default
* to the HSL saturation otherwise.
*/
_getSaturation(max=[0, 100]) {
if(!Array.isArray(max)){
max = [0, max];
}
if(this.mode === HSB || this.mode === HSL){
const colorjsMax = Color.#colorjsMaxes[this.mode][1];
return map(this._color.coords[1], colorjsMax[0], colorjsMax[1], max[0], max[1]);
}else {
// Will do an imprecise conversion to 'HSL', not recommended
const colorjsMax = Color.#colorjsMaxes[HSL][1];
return map(to(this._color, 'hsl').coords[1], colorjsMax[0], colorjsMax[1], max[0], max[1]);
}
}
_getBrightness(max=[0, 100]) {
if(!Array.isArray(max)){
max = [0, max];
}
if(this.mode === HSB){
const colorjsMax = Color.#colorjsMaxes[this.mode][2];
return map(this._color.coords[2], colorjsMax[0], colorjsMax[1], max[0], max[1]);
}else {
// Will do an imprecise conversion to 'HSB', not recommended
const colorjsMax = Color.#colorjsMaxes[HSB][2];
return map(to(this._color, 'hsb').coords[2], colorjsMax[0], colorjsMax[1], max[0], max[1]);
}
}
_getLightness(max=[0, 100]) {
if(!Array.isArray(max)){
max = [0, max];
}
if(this.mode === HSL){
const colorjsMax = Color.#colorjsMaxes[this.mode][2];
return map(this._color.coords[2], colorjsMax[0], colorjsMax[1], max[0], max[1]);
}else {
// Will do an imprecise conversion to 'HSL', not recommended
const colorjsMax = Color.#colorjsMaxes[HSL][2];
return map(to(this._color, 'hsl').coords[2], colorjsMax[0], colorjsMax[1], max[0], max[1]);
}
}
}
function color(p5, fn, lifecycles){
/**
* A class to describe a color.
*
* Each `p5.Color` object stores the color mode
* and level maxes that were active during its construction. These values are
* used to interpret the arguments passed to the object's constructor. They
* also determine output formatting such as when
* <a href="#/p5/saturation">saturation()</a> is called.
*
* Color is stored internally as an array of ideal RGBA values in floating
* point form, normalized from 0 to 1. These values are used to calculate the
* closest screen colors, which are RGBA levels from 0 to 255. Screen colors
* are sent to the renderer.
*
* When different color representations are calculated, the results are cached
* for performance. These values are normalized, floating-point numbers.
*
* Note: <a href="#/p5/color">color()</a> is the recommended way to create an
* instance of this class.
*
* @class p5.Color
* @param {p5} [pInst] pointer to p5 instance.
*
* @param {Number[]|String} vals an array containing the color values
* for red, green, blue and alpha channel
* or CSS color.
*/
p5.Color = Color;
sRGB.fromGray = P3.fromGray = function(val, maxes, clamp){
// Use blue max
const p5Maxes = maxes.map((max) => {
if(!Array.isArray(max)){
return [0, max];
}else {
return max;
}
});
const v = map(val, p5Maxes[2][0], p5Maxes[2][1], 0, 1, clamp);
return [v, v, v];
};
HSBSpace.fromGray = HSL$1.fromGray = function(val, maxes, clamp){
// Use brightness max
const p5Maxes = maxes.map((max) => {
if(!Array.isArray(max)){
return [0, max];
}else {
return max;
}
});
const v = map(val, p5Maxes[2][0], p5Maxes[2][1], 0, 100, clamp);
return [0, 0, v];
};
HWB$1.fromGray = function(val, maxes, clamp){
// Use Whiteness and Blackness to create number line
const p5Maxes = maxes.map((max) => {
if(!Array.isArray(max)){
return [0, max];
}else {
return max;
}
});
const wbMax =
(Math.abs(p5Maxes[1][0] - p5Maxes[1][1])) / 2 +
(Math.abs(p5Maxes[2][0] - p5Maxes[2][1])) / 2;
const nVal = map(val, 0, wbMax, 0, 100);
let white, black;
if(nVal < 50){
black = nVal;
white = 100 - nVal;
}else if(nVal >= 50){
white = nVal;
black = 100 - nVal;
}
return [0, white, black];
};
Lab.fromGray =
LCH$1.fromGray =
OKLab.fromGray =
OKLCH$1.fromGray =
function(val, maxes, clamp){
// Use lightness max
const p5Maxes = maxes.map((max) => {
if(!Array.isArray(max)){
return [0, max];
}else {
return max;
}
});
const v = map(val, p5Maxes[0][0], p5Maxes[0][1], 0, 100, clamp);
return [v, 0, 0];
};
// Register color modes and initialize Color maxes to what p5 has set for itself
p5.Color.addColorMode(RGB, sRGB);
p5.Color.addColorMode(RGBHDR, P3);
p5.Color.addColorMode(HSB, HSBSpace);
p5.Color.addColorMode(HSL, HSL$1);
p5.Color.addColorMode(HWB, HWB$1);
p5.Color.addColorMode(LAB, Lab);
p5.Color.addColorMode(LCH, LCH$1);
p5.Color.addColorMode(OKLAB, OKLab);
p5.Color.addColorMode(OKLCH, OKLCH$1);
lifecycles.presetup = function(){
const pInst = this;
// Decorate set methods
const setMethods = ['Red', 'Green', 'Blue', 'Alpha'];
for(let i in setMethods){
const method = setMethods[i];
const setCopy = p5.Color.prototype['set' + method];
p5.Color.prototype['set' + method] = function(newval, max){
max = max || pInst?._renderer?.states?.colorMaxes?.[RGB][i];
return setCopy.call(this, newval, max);
};
}
// Decorate get methods
function decorateGet(channel, modes){
const getCopy = p5.Color.prototype['_get' + channel];
p5.Color.prototype['_get' + channel] = function(max){
if(Object.keys(modes).includes(this.mode)){
max = max || pInst?._renderer?.states?.colorMaxes?.[this.mode][modes[this.mode]];
}else {
const defaultMode = Object.keys(modes)[0];
max = max || pInst?._renderer?.states?.colorMaxes?.[defaultMode][modes[defaultMode]];
}
return getCopy.call(this, max);
};
}
decorateGet('Red', {
[RGB]: 0,
[RGBHDR]: 0
});
decorateGet('Green', {
[RGB]: 1,
[RGBHDR]: 1
});
decorateGet('Blue', {
[RGB]: 2,
[RGBHDR]: 2
});
decorateGet('Alpha', {
[RGB]: 3,
[RGBHDR]: 3,
[HSB]: 3,
[HSL]: 3,
[HWB]: 3,
[LAB]: 3,
[LCH]: 3,
[OKLAB]: 3,
[OKLCH]: 3
});
decorateGet('Hue', {
[HSL]: 0,
[HSB]: 0,
[HWB]: 0,
[LCH]: 2,
[OKLCH]: 2
});
decorateGet('Saturation', {
[HSL]: 1,
[HSB]: 1
});
decorateGet('Brightness', {
[HSB]: 2
});
decorateGet('Lightness', {
[HSL]: 2
});
};
}
if(typeof p5 !== 'undefined'){
color(p5, p5.prototype);
}
/**
* @module Color
* @submodule Creating & Reading
* @for p5
* @requires core
* @requires constants
*/
/**
* @typedef {'rgb'} RGB
* @property {RGB} RGB
* @final
*/
const RGB = 'rgb';
/**
* @typedef {'rgbhdr'} RGBHDR
* @property {RGBHDR} RGBHDR
* @final
*/
const RGBHDR = 'rgbhdr';
/**
* HSB (hue, saturation, brightness) is a type of color model.
* You can learn more about it at
* <a href="https://learnui.design/blog/the-hsb-color-system-practicioners-primer.html">HSB</a>.
*
* @typedef {'hsb'} HSB
* @property {HSB} HSB
* @final
*/
const HSB = 'hsb';
/**
* @typedef {'hsl'} HSL
* @property {HSL} HSL
* @final
*/
const HSL = 'hsl';
/**
* @typedef {'hwb'} HWB
* @property {HWB} HWB
* @final
*/
const HWB = 'hwb';
/**
* @typedef {'lab'} LAB
* @property {LAB} LAB
* @final
*/
const LAB = 'lab';
/**
* @typedef {'lch'} LCH
* @property {LCH} LCH
* @final
*/
const LCH = 'lch';
/**
* @typedef {'oklab'} OKLAB
* @property {OKLAB} OKLAB
* @final
*/
const OKLAB = 'oklab';
/**
* @typedef {'oklch'} OKLCH
* @property {OKLCH} OKLCH
* @final
*/
const OKLCH = 'oklch';
/**
* @typedef {'rgba'} RGBA
* @property {RGBA} RGBA
* @final
*/
const RGBA = 'rgba';
function creatingReading(p5, fn){
fn.RGB = RGB;
fn.RGBHDR = RGBHDR;
fn.HSB = HSB;
fn.HSL = HSL;
fn.HWB = HWB;
fn.LAB = LAB;
fn.LCH = LCH;
fn.OKLAB = OKLAB;
fn.OKLCH = OKLCH;
fn.RGBA = RGBA;
// Add color states to renderer state machine
p5.Renderer.states.colorMode = RGB;
p5.Renderer.states.colorMaxes = {
[RGB]: [255, 255, 255, 255],
[RGBHDR]: [255, 255, 255, 255],
[HSB]: [360, 100, 100, 1],
[HSL]: [360, 100, 100, 1],
[HWB]: [360, 100, 100, 1],
[LAB]: [100, [-125, 125], [-125, 125], 1],
[LCH]: [100, 150, 360, 1],
[OKLAB]: [100, [-125, 125], [-125, 125], 1],
[OKLCH]: [100, 150, 360, 1],
clone: function(){
const cloned = { ...this };
for (const key in cloned) {
if (cloned[key] instanceof Array) {
cloned[key] = [...cloned[key]];
}
}
return cloned;
}
};
/**
* Creates a <a href="#/p5/p5.Color">p5.Color</a> object.
*
* By default, the parameters are interpreted as RGB values. Calling
* `color(255, 204, 0)` will return a bright yellow color. The way these
* parameters are interpreted may be changed with the
* <a href="#/p5/colorMode">colorMode()</a> function.
*
* The version of `color()` with one parameter interprets the value one of two
* ways. If the parameter is a number, it's interpreted as a grayscale value.
* If the parameter is a string, it's interpreted as a CSS color string.
*
* The version of `color()` with two parameters interprets the first one as a
* grayscale value. The second parameter sets the alpha (transparency) value.
*
* The version of `color()` with three parameters interprets them as RGB, HSB,
* or HSL colors, depending on the current `colorMode()`.
*
* The version of `color()` with four parameters interprets them as RGBA, HSBA,
* or HSLA colors, depending on the current `colorMode()`. The last parameter
* sets the alpha (transparency) value.
*
* @method color
* @param {Number} gray number specifying value between white and black.
* @param {Number} [alpha] alpha value relative to current color range
* (default is 0-255).
* @return {p5.Color} resulting color.
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object using RGB values.
* let c = color(255, 204, 0);
*
* // Draw the square.
* fill(c);
* noStroke();
* square(30, 20, 55);
*
* describe('A yellow square on a gray canvas.');
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object using RGB values.
* let c1 = color(255, 204, 0);
*
* // Draw the left circle.
* fill(c1);
* noStroke();
* circle(25, 25, 80);
*
* // Create a p5.Color object using a grayscale value.
* let c2 = color(65);
*
* // Draw the right circle.
* fill(c2);
* circle(75, 75, 80);
*
* describe(
* 'Two circles on a gray canvas. The circle in the top-left corner is yellow and the one at the bottom-right is gray.'
* );
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object using a named color.
* let c = color('magenta');
*
* // Draw the square.
* fill(c);
* noStroke();
* square(20, 20, 60);
*
* describe('A magenta square on a gray canvas.');
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object using a hex color code.
* let c1 = color('#0f0');
*
* // Draw the left rectangle.
* fill(c1);
* noStroke();
* rect(0, 10, 45, 80);
*
* // Create a p5.Color object using a hex color code.
* let c2 = color('#00ff00');
*
* // Draw the right rectangle.
* fill(c2);
* rect(55, 10, 45, 80);
*
* describe('Two bright green rectangles on a gray canvas.');
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object using a RGB color string.
* let c1 = color('rgb(0, 0, 255)');
*
* // Draw the top-left square.
* fill(c1);
* square(10, 10, 35);
*
* // Create a p5.Color object using a RGB color string.
* let c2 = color('rgb(0%, 0%, 100%)');
*
* // Draw the top-right square.
* fill(c2);
* square(55, 10, 35);
*
* // Create a p5.Color object using a RGBA color string.
* let c3 = color('rgba(0, 0, 255, 1)');
*
* // Draw the bottom-left square.
* fill(c3);
* square(10, 55, 35);
*
* // Create a p5.Color object using a RGBA color string.
* let c4 = color('rgba(0%, 0%, 100%, 1)');
*
* // Draw the bottom-right square.
* fill(c4);
* square(55, 55, 35);
*
* describe('Four blue squares in the corners of a gray canvas.');
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object using a HSL color string.
* let c1 = color('hsl(160, 100%, 50%)');
*
* // Draw the left rectangle.
* noStroke();
* fill(c1);
* rect(0, 10, 45, 80);
*
* // Create a p5.Color object using a HSLA color string.
* let c2 = color('hsla(160, 100%, 50%, 0.5)');
*
* // Draw the right rectangle.
* fill(c2);
* rect(55, 10, 45, 80);
*
* describe('Two sea green rectangles. A darker rectangle on the left and a brighter one on the right.');
* }
* </code>
* </div>
*
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object using a HSB color string.
* let c1 = color('hsb(160, 100%, 50%)');
*
* // Draw the left rectangle.
* noStroke();
* fill(c1);
* rect(0, 10, 45, 80);
*
* // Create a p5.Color object using a HSBA color string.
* let c2 = color('hsba(160, 100%, 50%, 0.5)');
*
* // Draw the right rectangle.
* fill(c2);
* rect(55, 10, 45, 80);
*
* describe('Two green rectangles. A darker rectangle on the left and a brighter one on the right.');
* }
* </code>
* </div>
*
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object using RGB values.
* let c1 = color(50, 55, 100);
*
* // Draw the left rectangle.
* fill(c1);
* rect(0, 10, 45, 80);
*
* // Switch the color mode to HSB.
* colorMode(HSB, 100);
*
* // Create a p5.Color object using HSB values.
* let c2 = color(50, 55, 100);
*
* // Draw the right rectangle.
* fill(c2);
* rect(55, 10, 45, 80);
*
* describe('Two blue rectangles. A darker rectangle on the left and a brighter one on the right.');
* }
* </code>
* </div>
*/
/**
* @method color
* @param {Number} v1 red or hue value relative to
* the current color range.
* @param {Number} v2 green or saturation value
* relative to the current color range.
* @param {Number} v3 blue or brightness value
* relative to the current color range.
* @param {Number} [alpha]
* @return {p5.Color}
*/
/**
* @method color
* @param {String} value a color string.
* @return {p5.Color}
*/
/**
* @method color
* @param {Number[]} values an array containing the red, green, blue,
* and alpha components of the color.
* @return {p5.Color}
*/
/**
* @method color
* @param {p5.Color} color
* @return {p5.Color}
*/
fn.color = function(...args) {
// p5._validateParameters('color', args);
if (args[0] instanceof Color) {
// TODO: perhaps change color mode to match instance mode?
return args[0]; // Do nothing if argument is already a color object.
}
const arg = Array.isArray(args[0]) ? args[0] : args;
return new Color(
arg,
this._renderer.states.colorMode,
this._renderer.states.colorMaxes[this._renderer.states.colorMode],
{ clamp: true }
);
};
/**
* Gets the red value of a color.
*
* `red()` extracts the red value from a
* <a href="/reference/p5/p5.Color/">p5.Color</a> object, an array of color components, or
* a CSS color string.
*
* By default, `red()` returns a color's red value in the range 0
* to 255. If the <a href="/reference/p5/colorMode/">colorMode()</a> is set to RGB, it
* returns the red value in the given range.
*
* @method red
* @param {p5.Color|Number[]|String} color <a href="/reference/p5/p5.Color/">p5.Color</a> object, array of
* color components, or CSS color string.
* @return {Number} the red value.
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object.
* let c = color(175, 100, 220);
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 15, 35, 70);
*
* // Set 'redValue' to 175.
* let redValue = red(c);
*
* // Draw the right rectangle.
* fill(redValue, 0, 0);
* rect(50, 15, 35, 70);
*
* describe('Two rectangles. The left one is light purple and the right one is red.');
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a color array.
* let c = [175, 100, 220];
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 15, 35, 70);
*
* // Set 'redValue' to 175.
* let redValue = red(c);
*
* // Draw the right rectangle.
* fill(redValue, 0, 0);
* rect(50, 15, 35, 70);
*
* describe('Two rectangles. The left one is light purple and the right one is red.');
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a CSS color string.
* let c = 'rgb(175, 100, 220)';
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 15, 35, 70);
*
* // Set 'redValue' to 175.
* let redValue = red(c);
*
* // Draw the right rectangle.
* fill(redValue, 0, 0);
* rect(50, 15, 35, 70);
*
* describe('Two rectangles. The left one is light purple and the right one is red.');
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Use RGB color with values in the range 0-100.
* colorMode(RGB, 100);
*
* // Create a p5.Color object.
* let c = color(69, 39, 86);
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 15, 35, 70);
*
* // Set 'redValue' to 69.
* let redValue = red(c);
*
* // Draw the right rectangle.
* fill(redValue, 0, 0);
* rect(50, 15, 35, 70);
*
* describe('Two rectangles. The left one is light purple and the right one is red.');
* }
* </code>
* </div>
*/
fn.red = function(c) {
// p5._validateParameters('red', arguments);
// Get current red max
return this.color(c)._getRed();
};
/**
* Gets the green value of a color.
*
* `green()` extracts the green value from a
* <a href="/reference/p5/p5.Color/">p5.Color</a> object, an array of color components, or
* a CSS color string.
*
* By default, `green()` returns a color's green value in the range 0
* to 255. If the <a href="/reference/p5/colorMode/">colorMode()</a> is set to RGB, it
* returns the green value in the given range.
*
* @method green
* @param {p5.Color|Number[]|String} color <a href="/reference/p5/p5.Color/">p5.Color</a> object, array of
* color components, or CSS color string.
* @return {Number} the green value.
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object.
* let c = color(175, 100, 220);
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 15, 35, 70);
*
* // Set 'greenValue' to 100.
* let greenValue = green(c);
*
* // Draw the right rectangle.
* fill(0, greenValue, 0);
* rect(50, 15, 35, 70);
*
* describe('Two rectangles. The left one is light purple and the right one is dark green.');
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a color array.
* let c = [175, 100, 220];
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 15, 35, 70);
*
* // Set 'greenValue' to 100.
* let greenValue = green(c);
*
* // Draw the right rectangle.
* fill(0, greenValue, 0);
* rect(50, 15, 35, 70);
*
* describe('Two rectangles. The left one is light purple and the right one is dark green.');
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a CSS color string.
* let c = 'rgb(175, 100, 220)';
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 15, 35, 70);
*
* // Set 'greenValue' to 100.
* let greenValue = green(c);
*
* // Draw the right rectangle.
* fill(0, greenValue, 0);
* rect(50, 15, 35, 70);
*
* describe('Two rectangles. The left one is light purple and the right one is dark green.');
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Use RGB color with values in the range 0-100.
* colorMode(RGB, 100);
*
* // Create a p5.Color object using RGB values.
* let c = color(69, 39, 86);
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 15, 35, 70);
*
* // Set 'greenValue' to 39.
* let greenValue = green(c);
*
* // Draw the right rectangle.
* fill(0, greenValue, 0);
* rect(50, 15, 35, 70);
*
* describe('Two rectangles. The left one is light purple and the right one is dark green.');
* }
* </code>
* </div>
*/
fn.green = function(c) {
// p5._validateParameters('green', arguments);
// Get current green max
return this.color(c)._getGreen();
};
/**
* Gets the blue value of a color.
*
* `blue()` extracts the blue value from a
* <a href="/reference/p5/p5.Color/">p5.Color</a> object, an array of color components, or
* a CSS color string.
*
* By default, `blue()` returns a color's blue value in the range 0
* to 255. If the <a href="/reference/p5/colorMode/">colorMode()</a> is set to RGB, it
* returns the blue value in the given range.
*
* @method blue
* @param {p5.Color|Number[]|String} color <a href="/reference/p5/p5.Color/">p5.Color</a> object, array of
* color components, or CSS color string.
* @return {Number} the blue value.
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object using RGB values.
* let c = color(175, 100, 220);
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 15, 35, 70);
*
* // Set 'blueValue' to 220.
* let blueValue = blue(c);
*
* // Draw the right rectangle.
* fill(0, 0, blueValue);
* rect(50, 15, 35, 70);
*
* describe('Two rectangles. The left one is light purple and the right one is royal blue.');
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a color array.
* let c = [175, 100, 220];
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 15, 35, 70);
*
* // Set 'blueValue' to 220.
* let blueValue = blue(c);
*
* // Draw the right rectangle.
* fill(0, 0, blueValue);
* rect(50, 15, 35, 70);
*
* describe('Two rectangles. The left one is light purple and the right one is royal blue.');
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a CSS color string.
* let c = 'rgb(175, 100, 220)';
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 15, 35, 70);
*
* // Set 'blueValue' to 220.
* let blueValue = blue(c);
*
* // Draw the right rectangle.
* fill(0, 0, blueValue);
* rect(50, 15, 35, 70);
*
* describe('Two rectangles. The left one is light purple and the right one is royal blue.');
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Use RGB color with values in the range 0-100.
* colorMode(RGB, 100);
*
* // Create a p5.Color object using RGB values.
* let c = color(69, 39, 86);
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 15, 35, 70);
*
* // Set 'blueValue' to 86.
* let blueValue = blue(c);
*
* // Draw the right rectangle.
* fill(0, 0, blueValue);
* rect(50, 15, 35, 70);
*
* describe('Two rectangles. The left one is light purple and the right one is royal blue.');
* }
* </code>
* </div>
*/
fn.blue = function(c) {
// p5._validateParameters('blue', arguments);
// Get current blue max
return this.color(c)._getBlue();
};
/**
* Gets the alpha (transparency) value of a color.
*
* `alpha()` extracts the alpha value from a
* <a href="#/p5.Color">p5.Color</a> object, an array of color components, or
* a CSS color string.
*
* @method alpha
* @param {p5.Color|Number[]|String} color <a href="#/p5.Color">p5.Color</a> object, array of
* color components, or CSS color string.
* @return {Number} the alpha value.
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object.
* let c = color(0, 126, 255, 102);
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 15, 35, 70);
*
* // Set 'alphaValue' to 102.
* let alphaValue = alpha(c);
*
* // Draw the right rectangle.
* fill(alphaValue);
* rect(50, 15, 35, 70);
*
* describe('Two rectangles. The left one is light blue and the right one is charcoal gray.');
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a color array.
* let c = [0, 126, 255, 102];
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 15, 35, 70);
*
* // Set 'alphaValue' to 102.
* let alphaValue = alpha(c);
*
* // Draw the left rectangle.
* fill(alphaValue);
* rect(50, 15, 35, 70);
*
* describe('Two rectangles. The left one is light blue and the right one is charcoal gray.');
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a CSS color string.
* let c = 'rgba(0, 126, 255, 0.4)';
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 15, 35, 70);
*
* // Set 'alphaValue' to 102.
* let alphaValue = alpha(c);
*
* // Draw the right rectangle.
* fill(alphaValue);
* rect(50, 15, 35, 70);
*
* describe('Two rectangles. The left one is light blue and the right one is charcoal gray.');
* }
* </code>
* </div>
*/
fn.alpha = function(c) {
// p5._validateParameters('alpha', arguments);
// Get current alpha max
return this.color(c)._getAlpha();
};
/**
* Gets the hue value of a color.
*
* `hue()` extracts the hue value from a
* <a href="/reference/p5/p5.Color/">p5.Color</a> object, an array of color components, or
* a CSS color string.
*
* Hue describes a color's position on the color wheel. By default, `hue()`
* returns a color's HSL hue in the range 0 to 360. If the
* <a href="/reference/p5/colorMode/">colorMode()</a> is set to HSB or HSL, it returns the hue
* value in the given mode.
*
* @method hue
* @param {p5.Color|Number[]|String} color <a href="/reference/p5/p5.Color/">p5.Color</a> object, array of
* color components, or CSS color string.
* @return {Number} the hue value.
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Use HSL color.
* colorMode(HSL);
*
* // Create a p5.Color object.
* let c = color(0, 50, 100);
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 20, 35, 60);
*
* // Set 'hueValue' to 0.
* let hueValue = hue(c);
*
* // Draw the right rectangle.
* fill(hueValue);
* rect(50, 20, 35, 60);
*
* describe(
* 'Two rectangles. The rectangle on the left is salmon pink and the one on the right is black.'
* );
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Use HSL color.
* colorMode(HSL);
*
* // Create a color array.
* let c = [0, 50, 100];
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 20, 35, 60);
*
* // Set 'hueValue' to 0.
* let hueValue = hue(c);
*
* // Draw the right rectangle.
* fill(hueValue);
* rect(50, 20, 35, 60);
*
* describe(
* 'Two rectangles. The rectangle on the left is salmon pink and the one on the right is black.'
* );
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Use HSL color.
* colorMode(HSL);
*
* // Create a CSS color string.
* let c = 'rgb(255, 128, 128)';
*
* // Draw the left rectangle.
* noStroke();
* fill(c);
* rect(15, 20, 35, 60);
*
* // Set 'hueValue' to 0.
* let hueValue = hue(c);
*
* // Draw the right rectangle.
* fill(hueValue);
* rect(50, 20, 35, 60);
*
* describe(
* 'Two rectangles. The rectangle on the left is salmon pink and the one on the right is black.'
* );
* }
* </code>
* </div>
*/
fn.hue = function(c) {
// p5._validateParameters('hue', arguments);
return this.color(c)._getHue();
};
/**
* Gets the saturation value of a color.
*
* `saturation()` extracts the saturation value from a
* <a href="/reference/p5/p5.Color/">p5.Color</a> object, an array of color components, or
* a CSS color string.
*
* Saturation is scaled differently in HSB and HSL. By default, `saturation()`
* returns a color's HSL saturation in the range 0 to 100. If the
* <a href="/reference/p5/colorMode/">colorMode()</a> is set to HSB or HSL, it returns the