matisse
Version:
TypeScript library for mutable colour conversion and manipulation 🎨
995 lines • 59.7 kB
JavaScript
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _Colour_instances, _Colour_updateHSV, _Colour_updateRGB_HSV, _Colour_updateRGB_CMYK, _Colour_updateRGB_HSL, _Colour_updateHSV_HWB, _Colour_updateHWB, _Colour_updateCMYK, _Colour_updateHSL;
import colorString from 'color-string';
import * as math from 'mathjs';
/**
* An abstract data type representating mutable colour entities for RGB, HSV, HSL, CMYK, and HWB colour models.
* @class Colour
*/
class Colour {
/**
* @type {number}
* @default 255
* @constant
*/
static get redMax() { return 255; }
/**
* @type {number}
* @default 0
* @constant
*/
static get redMin() { return 0; }
/**
* @type {number}
* @default 255
* @constant
*/
static get greenMax() { return 255; }
/**
* @type {number}
* @default 0
* @constant
*/
static get greenMin() { return 0; }
/**
* @type {number}
* @default 255
* @constant
*/
static get blueMax() { return 255; }
/**
* @type {number}
* @default 0
* @constant
*/
static get blueMin() { return 0; }
/**
* @type {number}
* @default 360
* @constant
*/
static get hueMax() { return 360; }
/**
* @type {number}
* @default 0
* @constant
*/
static get hueMin() { return 0; }
/**
* @type {number}
* @default 1.00
* @constant
*/
static get saturationvMax() { return 1.00; }
/**
* @type {number}
* @default 0.00
* @constant
*/
static get saturationvMin() { return 0.00; }
/**
* @type {number}
* @default 1.00
* @constant
*/
static get valueMax() { return 1.00; }
/**
* @type {number}
* @default 0.00
* @constant
*/
static get valueMin() { return 0.00; }
/**
* @type {number}
* @default 1.00
* @constant
*/
static get cyanMax() { return 1.00; }
/**
* @type {number}
* @default 0.00
* @constant
*/
static get cyanMin() { return 0.00; }
/**
* @type {number}
* @default 1.00
* @constant
*/
static get magentaMax() { return 1.00; }
/**
* @type {number}
* @default 0.00
* @constant
*/
static get magentaMin() { return 0.00; }
/**
* @type {number}
* @default 1.00
* @constant
*/
static get yellowMax() { return 1.00; }
/**
* @type {number}
* @default 0.00
* @constant
*/
static get yellowMin() { return 0.00; }
/**
* @type {number}
* @default 1.00
* @constant
*/
static get saturationlMax() { return 1.00; }
/**
* @type {number}
* @default 0.00
* @constant
*/
static get saturationlMin() { return 0.00; }
/**
* @type {number}
* @default 1.00
* @constant
*/
static get lightMax() { return 1.00; }
/**
* @type {number}
* @default 0.00
* @constant
*/
static get lightMin() { return 0.00; }
/**
* @type {number}
* @default 1.00
* @constant
*/
static get whiteMax() { return 1.00; }
/**
* @type {number}
* @default 0.00
* @constant
*/
static get whiteMin() { return 0.00; }
/**
* @type {number}
* @default 1.00
* @constant
*/
static get blackMax() { return 1.00; }
/**
* @type {number}
* @default 0.00
* @constant
*/
static get blackMin() { return 0.00; }
/**
* @type {number}
* @default 1.00
* @constant
*/
static get alphaMax() { return 1.00; }
/**
* @type {number}
* @default 0.00
* @constant
*/
static get alphaMin() { return 0.00; }
/**
* Initialize a new Colour instance given a valid CSS colour string or HEX code.
* @param {string} [colourString] - A valid CSS colour string or HEX code to parse.
* @returns {Colour} A new Colour instance generated from the CSS colour string.
* @see [color-string](https://github.com/Qix-/color-string) handles all parsing of CSS colour strings.
*/
constructor(colourString) {
_Colour_instances.add(this);
this.attributes = {
red: 0,
green: 0,
blue: 0,
hue: 0,
saturationv: 0,
value: 0,
cyan: 0,
magenta: 0,
yellow: 0,
saturationl: 0,
light: 0,
white: 0,
black: 0,
alpha: 0
};
if (colourString != undefined) {
try {
const colorData = colorString.get(colourString);
if (colorData === null) { // color-string returns null for invalid strings
throw new SyntaxError("The given colourString value was not recognized as a valid CSS colour string or HEX code.");
}
const { model, value } = colorData;
switch (model) {
case "rgb":
return Colour.RGB(value[0], value[1], value[2], value[3]);
case "hsl":
return Colour.HSL(value[0], value[1] / 100, value[2] / 100, value[3]);
case "hwb":
return Colour.HWB(value[0], value[1] / 100, value[2] / 100, value[3]);
}
}
catch (error) {
throw new SyntaxError("The given colourString value was not recognized as a valid CSS colour string or HEX code.");
}
}
}
/**
* Initialize a new Colour instance from RGB colour attributes.
* @param {number} red - The value of the R channel [0, 255].
* @param {number} green - The value of the G channel [0, 255].
* @param {number} blue - The value of the B channel [0, 255].
* @param {number} [alpha=1.00] - The percentage of transparency or opacity channel [0, 1]. Defaults to {@link Colour.alphaMax}.
* @returns {Colour} A new Colour instance generated from the RGB colour attributes.
*/
static RGB(red, green, blue, alpha = Colour.alphaMax) {
if (red > Colour.redMax || green > Colour.greenMax || blue > Colour.blueMax || alpha > Colour.alphaMax) {
throw new RangeError("RGB attributes must be in the following acceptable ranges...red:[0, 255], green:[0, 255], blue:[0, 255], alpha:[0, 1].");
}
else if (red < Colour.redMin || green < Colour.greenMin || blue < Colour.blueMin || alpha < Colour.alphaMin) {
throw new RangeError("RGB attributes must be in the following acceptable ranges...red:[0, 255], green:[0, 255], blue:[0, 255], alpha:[0, 1].");
}
let colour = new Colour();
colour.attributes.red = roundInt(red);
colour.attributes.green = roundInt(green);
colour.attributes.blue = roundInt(blue);
colour.attributes.alpha = roundFloat(alpha);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateHSV).call(colour);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateHSL).call(colour);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateCMYK).call(colour);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateHWB).call(colour);
return colour;
}
/**
* Initialize a new Colour instance from HSV colour attributes.
* @param {number} hue - The value of the H channel [0, 360].
* @param {number} saturationv - The percentage of the S channel [0, 1].
* @param {number} value - The percentage of the V channel [0, 1].
* @param {number} [alpha=1.00] - The percentage of transparency or opacity channel [0, 1]. Defaults to {@link Colour.alphaMax}.
* @returns {Colour} A new Colour instance generated from the HSV colour attributes.
*/
static HSV(hue, saturationv, value, alpha = Colour.alphaMax) {
if (hue > Colour.hueMax || saturationv > Colour.saturationvMax || value > Colour.valueMax || alpha > Colour.alphaMax) {
throw new RangeError("HSV attributes must be in the following acceptable ranges...hue:[0, 360], saturationv:[0, 1], value:[0, 1], alpha:[0, 1].");
}
else if (hue < Colour.hueMin || saturationv < Colour.saturationvMin || value < Colour.valueMin || alpha < Colour.alphaMin) {
throw new RangeError("HSV attributes must be in the following acceptable ranges...hue:[0, 360], saturationv:[0, 1], value:[0, 1], alpha:[0, 1].");
}
let colour = new Colour();
colour.attributes.hue = roundInt(hue);
colour.attributes.saturationv = roundFloat(saturationv);
colour.attributes.value = roundFloat(value);
colour.attributes.alpha = roundFloat(alpha);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateRGB_HSV).call(colour);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateCMYK).call(colour);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateHSL).call(colour);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateHWB).call(colour);
return colour;
}
/**
* Initialize a new Colour instance from CMYK colour attributes.
* @param {number} cyan - The value of the C channel [0, 1].
* @param {number} magenta - The value of the M channel [0, 1].
* @param {number} yellow - The value of the Y channel [0, 1].
* @param {number} black - The value of the K channel [0, 1].
* @param {number} [alpha=1.00] - The percentage of transparency or opacity channel [0, 1]. Defaults to {@link Colour.alphaMax}.
* @returns {Colour} A new Colour instance generated from the CMYK colour attributes.
*/
static CMYK(cyan, magenta, yellow, black, alpha = Colour.alphaMax) {
if (cyan > Colour.cyanMax || magenta > Colour.magentaMax || yellow > Colour.yellowMax || black > Colour.blackMax || alpha > Colour.alphaMax) {
throw new RangeError("CMYK attributes must be in the following acceptable range...cyan:[0, 1], magenta:[0, 1], yellow:[0, 1], black:[0, 1], alpha:[0, 1].");
}
else if (cyan < Colour.cyanMin || magenta < Colour.magentaMin || yellow < Colour.yellowMin || black < Colour.blackMin || alpha < Colour.alphaMin) {
throw new RangeError("CMYK attributes must be in the following acceptable range...cyan:[0, 1], magenta:[0, 1], yellow:[0, 1], black:[0, 1], alpha:[0, 1].");
}
let colour = new Colour();
colour.attributes.cyan = roundFloat(cyan);
colour.attributes.magenta = roundFloat(magenta);
colour.attributes.yellow = roundFloat(yellow);
colour.attributes.black = roundFloat(black);
colour.attributes.alpha = roundFloat(alpha);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateRGB_CMYK).call(colour);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateHSV).call(colour);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateHSL).call(colour);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateHWB).call(colour);
return colour;
}
/**
* Initialize a new Colour instance from HSL colour attributes.
* @param {number} hue - The value of the H channel [0, 360].
* @param {number} saturationl - The value of the S channel [0, 1].
* @param {number} light - The value of the L channel [0, 1].
* @param {number} [alpha=1.00] - The percentage of transparency or opacity channel [0, 1]. Defaults to {@link Colour.alphaMax}.
* @returns {Colour} A new Colour instance generated from the HSL colour attributes.
*/
static HSL(hue, saturationl, light, alpha = Colour.alphaMax) {
if (hue > Colour.hueMax || saturationl > Colour.saturationlMax || light > Colour.lightMax || alpha > Colour.alphaMax) {
throw new RangeError("HSL attributes must be in the following acceptable ranges...hue:[0, 360], saturationl:[0, 1], light:[0, 1], alpha:[0, 1].");
}
else if (hue < Colour.hueMin || saturationl < Colour.saturationlMin || light < Colour.lightMin || alpha < Colour.alphaMin) {
throw new RangeError("HSL attributes must be in the following acceptable ranges...hue:[0, 360], saturationl:[0, 1], light:[0, 1], alpha:[0, 1].");
}
let colour = new Colour();
colour.attributes.hue = roundInt(hue);
colour.attributes.saturationl = roundFloat(saturationl);
colour.attributes.light = roundFloat(light);
colour.attributes.alpha = roundFloat(alpha);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateRGB_HSL).call(colour);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateCMYK).call(colour);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateHSV).call(colour);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateHWB).call(colour);
return colour;
}
/**
* Initialize a new Colour instance from HWB colour attributes.
* @param {number} hue - The value of the H channel [0, 360].
* @param {number} white - The value of the W channel [0, 1].
* @param {number} black - The value of the B channel [0, 1].
* @param {number} [alpha=1.00] - The percentage of transparency or opacity channel [0, 1]. Defaults to {@link Colour.alphaMax}.
* @returns {Colour} A new Colour instance generated from the HWB colour attributes.
*/
static HWB(hue, white, black, alpha = Colour.alphaMax) {
if (hue > Colour.hueMax || white > Colour.whiteMax || black > Colour.blackMax || alpha > Colour.alphaMax) {
throw new RangeError("HWB attributes must be in the following acceptable ranges...hue:[0, 360], white:[0, 1], black:[0, 1], alpha:[0, 1].");
}
else if (hue < Colour.hueMin || white < Colour.whiteMin || black < Colour.blackMin || alpha < Colour.alphaMin) {
throw new RangeError("HWB attributes must be in the following acceptable ranges...hue:[0, 360], white:[0, 1], black:[0, 1], alpha:[0, 1].");
}
let colour = new Colour();
colour.attributes.hue = roundInt(hue);
colour.attributes.white = roundFloat(white);
colour.attributes.black = roundFloat(black);
colour.attributes.alpha = roundFloat(alpha);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateHSV_HWB).call(colour);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateRGB_HSV).call(colour);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateCMYK).call(colour);
__classPrivateFieldGet(colour, _Colour_instances, "m", _Colour_updateHSL).call(colour);
return colour;
}
/** @type {number} */
get red() { return this.attributes.red; }
set red(newRed) {
if (newRed < Colour.redMin || newRed > Colour.redMax) {
throw new RangeError("The red attribute must be in the following acceptable range: [0, 255].");
}
this.attributes.red = roundInt(newRed);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSV).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSL).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateCMYK).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHWB).call(this);
}
/** @type {number} */
get green() { return this.attributes.green; }
set green(newGreen) {
if (newGreen < Colour.greenMin || newGreen > Colour.greenMax) {
throw new RangeError("The green attribute must be in the following acceptable range: [0, 255].");
}
this.attributes.green = roundInt(newGreen);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSV).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSL).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateCMYK).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHWB).call(this);
}
/** @type {number} */
get blue() { return this.attributes.blue; }
set blue(newBlue) {
if (newBlue < Colour.blueMin || newBlue > Colour.blueMax) {
throw new RangeError("The blue attribute must be in the following acceptable range: [0, 255].");
}
this.attributes.blue = roundInt(newBlue);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSV).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSL).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateCMYK).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHWB).call(this);
}
/** @type {number} */
get hue() { return this.attributes.hue; }
set hue(newHue) {
if (newHue < Colour.hueMin || newHue >= Colour.hueMax) {
throw new RangeError("The hue attribute must be in the following acceptable range: [0, 360].");
}
this.attributes.hue = roundInt(newHue);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateRGB_HSV).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateCMYK).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSL).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHWB).call(this);
}
/** @type {number} */
get saturationv() { return this.attributes.saturationv; }
set saturationv(newSaturationv) {
if (newSaturationv < Colour.saturationvMin || newSaturationv > Colour.saturationvMax) {
throw new RangeError("The saturationv attribute must be in the following acceptable range: [0, 1].");
}
this.attributes.saturationv = roundFloat(newSaturationv);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateRGB_HSV).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateCMYK).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSL).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHWB).call(this);
}
/** @type {number} */
get value() { return this.attributes.value; }
set value(newValue) {
if (newValue < Colour.valueMin || newValue > Colour.valueMax) {
throw new RangeError("The value attribute must be in the following acceptable range: [0, 1].");
}
this.attributes.value = roundFloat(newValue);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateRGB_HSV).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateCMYK).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSL).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHWB).call(this);
}
/** @type {number} */
get cyan() { return this.attributes.cyan; }
set cyan(newCyan) {
if (newCyan < Colour.cyanMin || newCyan > Colour.cyanMax) {
throw new RangeError("The cyan attribute must be in the following acceptable range: [0, 1].");
}
this.attributes.cyan = roundFloat(newCyan);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateRGB_CMYK).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSL).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSV).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHWB).call(this);
}
/** @type {number} */
get magenta() { return this.attributes.magenta; }
set magenta(newMagenta) {
if (newMagenta < Colour.magentaMin || newMagenta > Colour.magentaMax) {
throw new RangeError("The magenta attribute must be in the following acceptable range: [0, 1].");
}
this.attributes.magenta = roundFloat(newMagenta);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateRGB_CMYK).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSL).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSV).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHWB).call(this);
}
/** @type {number} */
get yellow() { return this.attributes.yellow; }
set yellow(newYellow) {
if (newYellow < Colour.yellowMin || newYellow > Colour.yellowMax) {
throw new RangeError("The yellow attribute must be in the following acceptable range: [0, 1].");
}
this.attributes.yellow = roundFloat(newYellow);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateRGB_CMYK).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSL).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSV).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHWB).call(this);
}
/** @type {number} */
get saturationl() { return this.attributes.saturationl; }
set saturationl(newSaturationl) {
if (newSaturationl < Colour.saturationlMin || newSaturationl > Colour.saturationlMax) {
throw new RangeError("The saturationl attribute must be in the following acceptable range: [0, 1].");
}
this.attributes.saturationl = roundFloat(newSaturationl);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateRGB_HSL).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSV).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateCMYK).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHWB).call(this);
}
/** @type {number} */
get light() { return this.attributes.light; }
set light(newLight) {
if (newLight < Colour.lightMin || newLight > Colour.lightMax) {
throw new RangeError("The light attribute must be in the following acceptable range: [0, 1].");
}
this.attributes.light = roundFloat(newLight);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateRGB_HSL).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSV).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateCMYK).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHWB).call(this);
}
/** @type {number} */
get white() { return this.attributes.white; }
set white(newWhite) {
if (newWhite < Colour.whiteMin || newWhite > Colour.whiteMax) {
throw new RangeError("The white attribute must be in the following acceptable range: [0, 1].");
}
this.attributes.white = roundFloat(newWhite);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSV_HWB).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateRGB_HSV).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateCMYK).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSL).call(this);
}
/** @type {number} */
get black() { return this.attributes.black; }
set black(newBlack) {
if (newBlack < Colour.blackMin || newBlack > Colour.blackMax) {
throw new RangeError("The black attribute must be in the following acceptable range: [0, 1].");
}
this.attributes.black = roundFloat(newBlack);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSV_HWB).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateRGB_HSV).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateCMYK).call(this);
__classPrivateFieldGet(this, _Colour_instances, "m", _Colour_updateHSL).call(this);
}
/** @type {number} */
get alpha() { return this.attributes.alpha; }
set alpha(newAlpha) {
if (newAlpha < Colour.alphaMin || newAlpha > Colour.alphaMax) {
throw new RangeError("The alpha attribute must be in the following acceptable range: [0, 1].");
}
this.attributes.alpha = roundFloat(newAlpha);
}
/**
* Check if another Colour instance is equivalent.
* @param {Colour} colour - Another Colour instance to compare equality with.
* @returns {boolean} True if both colours have the same red, green, blue, and alpha values.
*/
equals(colour) {
return (this.red == colour.red &&
this.green == colour.green &&
this.blue == colour.blue &&
this.alpha == colour.alpha);
}
/**
* Returns a copy of the Colour instance.
* @returns {Colour} An identical Colour object.
*/
copy() {
return Colour.RGB(this.red, this.green, this.blue, this.alpha);
}
/**
* Return a valid hexadecimal colour code that represents the colour.
* @returns {string} A HEX code representing the colour.
*/
toHEX() {
return colorString.to.hex([this.red, this.green, this.blue, this.alpha]);
}
}
_Colour_instances = new WeakSet(), _Colour_updateHSV = function _Colour_updateHSV() {
//https://www.rapidtables.com/convert/color/rgb-to-hsv.html
let cMax = Math.max(this.attributes.red, this.attributes.green, this.attributes.blue) / 255;
let cMin = Math.min(this.attributes.red, this.attributes.green, this.attributes.blue) / 255;
let delta = cMax - cMin;
//Updating hue
if (delta == 0) {
this.attributes.hue = 0;
}
else if (cMax == this.attributes.red / 255) {
this.attributes.hue = roundInt(60 * (((this.attributes.green / 255 - this.attributes.blue / 255) / delta) % 6));
}
else if (cMax == this.attributes.green / 255) {
this.attributes.hue = roundInt(60 * (((this.attributes.blue / 255 - this.attributes.red / 255) / delta) + 2));
}
else if (cMax == this.attributes.blue / 255) {
this.attributes.hue = roundInt(60 * (((this.attributes.red / 255 - this.attributes.green / 255) / delta) + 4));
}
// hue loop bug fix
if (this.attributes.hue < 0) {
this.attributes.hue += 360;
}
//Updating saturation
if (cMax == 0) {
this.attributes.saturationv = 0;
}
else {
this.attributes.saturationv = roundFloat(delta / cMax);
}
//Updating Value
this.attributes.value = roundFloat(cMax);
}, _Colour_updateRGB_HSV = function _Colour_updateRGB_HSV() {
//https://www.rapidtables.com/convert/color/hsv-to-rgb.html
let c = this.attributes.value * this.attributes.saturationv;
let x = c * (1 - Math.abs((this.attributes.hue / 60) % 2 - 1));
let m = this.attributes.value - c;
if (this.attributes.hue >= 0 && this.attributes.hue < 60) {
this.attributes.red = roundInt((c + m) * 255);
this.attributes.green = roundInt((x + m) * 255);
this.attributes.blue = roundInt((0 + m) * 255);
}
else if (this.attributes.hue >= 60 && this.attributes.hue < 120) {
this.attributes.red = roundInt((x + m) * 255);
this.attributes.green = roundInt((c + m) * 255);
this.attributes.blue = roundInt((0 + m) * 255);
}
else if (this.attributes.hue >= 120 && this.attributes.hue < 180) {
this.attributes.red = roundInt((0 + m) * 255);
this.attributes.green = roundInt((c + m) * 255);
this.attributes.blue = roundInt((x + m) * 255);
}
else if (this.attributes.hue >= 180 && this.attributes.hue < 240) {
this.attributes.red = roundInt((0 + m) * 255);
this.attributes.green = roundInt((x + m) * 255);
this.attributes.blue = roundInt((c + m) * 255);
}
else if (this.attributes.hue >= 240 && this.attributes.hue < 300) {
this.attributes.red = roundInt((x + m) * 255);
this.attributes.green = roundInt((0 + m) * 255);
this.attributes.blue = roundInt((c + m) * 255);
}
else if (this.attributes.hue >= 300 && this.attributes.hue < 360) {
this.attributes.red = roundInt((c + m) * 255);
this.attributes.green = roundInt((0 + m) * 255);
this.attributes.blue = roundInt((x + m) * 255);
}
}, _Colour_updateRGB_CMYK = function _Colour_updateRGB_CMYK() {
this.attributes.red = roundInt(255 * (1 - this.attributes.cyan) * (1 - this.attributes.black));
this.attributes.green = roundInt(255 * (1 - this.attributes.magenta) * (1 - this.attributes.black));
this.attributes.blue = roundInt(255 * (1 - this.attributes.yellow) * (1 - this.attributes.black));
}, _Colour_updateRGB_HSL = function _Colour_updateRGB_HSL() {
//https://www.rapidtables.com/convert/color/hsl-to-rgb.html
let c = (1 - Math.abs(2 * this.attributes.light - 1)) * this.attributes.saturationl;
let x = c * (1 - Math.abs((this.attributes.hue / 60) % 2 - 1));
let m = this.attributes.light - c / 2;
if (this.attributes.hue >= 0 && this.attributes.hue < 60) {
this.attributes.red = roundInt((c + m) * 255);
this.attributes.green = roundInt((x + m) * 255);
this.attributes.blue = roundInt((0 + m) * 255);
}
else if (this.attributes.hue >= 60 && this.attributes.hue < 120) {
this.attributes.red = roundInt((x + m) * 255);
this.attributes.green = roundInt((c + m) * 255);
this.attributes.blue = roundInt((0 + m) * 255);
}
else if (this.attributes.hue >= 120 && this.attributes.hue < 180) {
this.attributes.red = roundInt((0 + m) * 255);
this.attributes.green = roundInt((c + m) * 255);
this.attributes.blue = roundInt((x + m) * 255);
}
else if (this.attributes.hue >= 180 && this.attributes.hue < 240) {
this.attributes.red = roundInt((0 + m) * 255);
this.attributes.green = roundInt((x + m) * 255);
this.attributes.blue = roundInt((c + m) * 255);
}
else if (this.attributes.hue >= 240 && this.attributes.hue < 300) {
this.attributes.red = roundInt((x + m) * 255);
this.attributes.green = roundInt((0 + m) * 255);
this.attributes.blue = roundInt((c + m) * 255);
}
else if (this.attributes.hue >= 300 && this.attributes.hue < 360) {
this.attributes.red = roundInt((c + m) * 255);
this.attributes.green = roundInt((0 + m) * 255);
this.attributes.blue = roundInt((x + m) * 255);
}
}, _Colour_updateHSV_HWB = function _Colour_updateHSV_HWB() {
this.attributes.saturationv = roundFloat(1 - this.attributes.white / (1 - this.attributes.black));
this.attributes.value = roundFloat(1 - this.attributes.black);
}, _Colour_updateHWB = function _Colour_updateHWB() {
this.attributes.white = roundFloat(Math.min(this.attributes.red, this.attributes.green, this.attributes.blue) / 255);
this.attributes.black = roundFloat(1 - Math.max(this.attributes.red, this.attributes.green, this.attributes.blue) / 255);
}, _Colour_updateCMYK = function _Colour_updateCMYK() {
let blackAttribute = 1 - Math.max(this.attributes.red, this.attributes.green, this.attributes.blue) / 255;
this.attributes.black = roundFloat(blackAttribute);
// Handle division by zero if blackAttribute is 1 (i.e. colour is black)
if (1 - blackAttribute === 0) {
this.attributes.cyan = 0;
this.attributes.magenta = 0;
this.attributes.yellow = 0;
}
else {
this.attributes.cyan = roundFloat((1 - this.attributes.red / 255 - blackAttribute) / (1 - blackAttribute));
this.attributes.magenta = roundFloat((1 - this.attributes.green / 255 - blackAttribute) / (1 - blackAttribute));
this.attributes.yellow = roundFloat((1 - this.attributes.blue / 255 - blackAttribute) / (1 - blackAttribute));
}
}, _Colour_updateHSL = function _Colour_updateHSL() {
let r = this.attributes.red / 255;
let g = this.attributes.green / 255;
let b = this.attributes.blue / 255;
let cMax = Math.max(r, g, b);
let cMin = Math.min(r, g, b);
let delta = cMax - cMin;
this.attributes.light = roundFloat((cMax + cMin) / 2);
if (delta == 0) {
this.attributes.saturationl = 0.00;
}
else {
this.attributes.saturationl = roundFloat(delta / (1 - Math.abs(cMax + cMin - 1)));
}
};
export default Colour;
function roundInt(num) {
return Math.round(num);
}
function roundFloat(num) {
return Number(num.toFixed(2).replace('-0', '0'));
}
/**
* Determine the inverse colour or the colour on the opposite side of the colour wheel.
* @param {Colour} colour - The colour to invert.
* @returns {Colour} The colour resulting from the negation.
*/
export function negate(colour) {
let red = 255 - colour.red;
let green = 255 - colour.green;
let blue = 255 - colour.blue;
let alpha = colour.alpha;
return Colour.RGB(red, green, blue, alpha);
}
/**
* Rotate a given colour a certain number of degrees in 3-dimensional space.
* @param {Colour} colour - The colour to rotate.
* @param {number} degrees - The number of degrees to rotate the colour.
* @returns {Colour} The colour resulting from the rotation.
*/
export function rotate(colour, degrees) {
let hue = colour.hue;
hue = (hue + degrees) % 360;
hue = hue < 0 ? 360 + hue : hue;
return Colour.HSL(hue, colour.saturationl, colour.light, colour.alpha);
}
/**
* Determine the equivalent [grayscale colour of a given colour](https://www.tutorialspoint.com/dip/grayscale_to_rgb_conversion.htm).
* @param {Colour} colour - The colour to grayscale.
* @returns {Colour} The resulting colour from the grayscale transformation.
*/
export function grayscale(colour) {
let weightedTotal = roundInt(0.3 * colour.red + 0.59 * colour.green + 0.11 * colour.blue);
return Colour.RGB(weightedTotal, weightedTotal, weightedTotal, colour.alpha);
}
/**
* Calculate the [WCAG contrast ratio](http://www.w3.org/TR/WCAG20/#contrast-ratiodef)
* between two colours. (Note: The order of the colours does not matter!).
* @param {Colour} colour1 - The first colour to be compared.
* @param {Colour} colour2 - The second colour to be compared.
* @returns {number} The WCAG contrast ratio of the two colours (values ranging between 1 and 21).
*/
export function contrast(colour1, colour2) {
const lum1 = luminosity(colour1);
const lum2 = luminosity(colour2);
if (lum1 > lum2) {
return roundInt(((lum1 + 0.05) / (lum2 + 0.05)) * 100) / 100;
}
return roundInt(((lum2 + 0.05) / (lum1 + 0.05)) * 100) / 100;
}
/**
* Calculate the [colourfulness index](https://infoscience.epfl.ch/record/33994/files/HaslerS03.pdf) of a given colour as defined by Hasler and Süsstrunk (2003).
* @param {Colour} colour - The colour to calculate colourfulness of.
* @returns {number} The resulting colourfulness grading.
*/
export function colourfulness(colour) {
let rg = Math.abs(colour.red - colour.green);
let yb = Math.abs(0.5 * (colour.red + colour.green) - colour.blue);
// compute the mean and standard deviation of both `rg` and `yb`.
// mathjs functions return 'any' by default, so we cast to number
let rg_mean = math.mean(rg);
let rg_std = math.std(rg);
let yb_mean = math.mean(yb);
let yb_std = math.std(yb);
// combine the mean and standard deviations.
let std_root = math.sqrt((rg_std ** 2) + (yb_std ** 2));
let mean_root = math.sqrt((rg_mean ** 2) + (yb_mean ** 2)); // Corrected: rg_mean, yb_mean
return std_root + (0.3 * mean_root);
}
/**
* Calculate the [temperature](https://ams.com/documents/20143/80162/TCS34xx_AN000517_1-00.pdf) of a given colour.
* @param {Colour} colour - The colour to calculate temperature of.
* @returns {number} The resulting temperature grading in degrees Kelvin (K).
*/
export function temperature(colour) {
// get XYZ values (CIE tristimulus values)
let X = -0.14282 * colour.red + 1.54924 * colour.green + -0.95641 * colour.blue;
let Y = -0.32466 * colour.red + 1.57837 * colour.green + -0.73191 * colour.blue;
let Z = -0.68202 * colour.red + 0.77073 * colour.green + 0.56332 * colour.blue;
// normalize values
let x = X / (X + Y + Z);
let y = Y / (X + Y + Z);
// calculate correlated colour temperature
let n = (x - 0.3320) / (0.1858 - y);
let CCT = 449 * (n ** 3) + 3525 * (n ** 2) + 6823.3 * n + 5520.33;
return CCT;
}
/**
* Calculate the [relative luminance](https://www.w3.org/WAI/GL/wiki/Relative_luminance) of a given colour as defined by the WCAG.
* @param {Colour} colour - The colour to calculate luminence of.
* @returns {number} The resulting luminence grading.
*/
export function luminosity(colour) {
const lum = [];
const rgb = [colour.red, colour.green, colour.blue];
for (let i = 0; i < rgb.length; i++) {
const chan = rgb[i] / 255;
lum[i] = (chan <= 0.03928) ? (chan / 12.92) : ((chan + 0.055) / 1.055) ** 2.4;
}
return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
}
/**
* Evenly interpolate two colours and produce the resulting midpoint colour.
* @param {Colour} colour1 - The first colour to include in the mix.
* @param {Colour} colour2 - The second colour to include in the mix.
* @param {number} percent - The percentage of the blend colour to mix.
* @returns {Colour} The colour resulting from the mix.
*/
export function mix(colour1, colour2, percent) {
const weight = 2 * percent - 1;
const alphaDiff = colour2.alpha - colour1.alpha; // Renamed to avoid conflict with class member
const weight1 = (((weight * alphaDiff === -1) ? weight : (weight + alphaDiff) / (1 + weight * alphaDiff)) + 1) / 2;
const weight2 = 1 - weight1;
return Colour.RGB(weight1 * colour2.red + weight2 * colour1.red, weight1 * colour2.green + weight2 * colour1.green, weight1 * colour2.blue + weight2 * colour1.blue, colour2.alpha * percent + colour1.alpha * (1 - percent));
}
/**
* Interpolate a given colour with white to create a tint.
* @param {Colour} colour - A colour to tint.
* @param {number} percent - The percentage of white to mix; setting 100% results in #FFFFFF.
* @returns {Colour} The colour resulting from tinting the original colour.
*/
export function tint(colour, percent) {
return mix(colour, new Colour("#FFFFFF"), percent);
}
/**
* Interpolate a given colour with black to create a shade.
* @param {Colour} colour - A colour to shade.
* @param {number} percent - The percentage of black to mix; setting 100% results in #000000.
* @returns {Colour} The colour resulting from shading the original colour.
*/
export function shade(colour, percent) {
return mix(colour, new Colour("#000000"), percent);
}
/**
* Interpolate a given colour with gray to create a tone.
* @param {Colour} colour - A colour to tone.
* @param {number} percent - The percentage of gray to mix; setting 100% results in #808080.
* @returns {Colour} The colour resulting from toning the original colour.
*/
export function tone(colour, percent) {
return mix(colour, new Colour("#808080"), percent);
}
/**
* This is the blend mode which specifies no blending. The blending formula simply selects the blend colour.
* @param {Colour} baseColour - The base colour being blended.
* @param {Colour} blendColour - The colour being applied with the designated blend mode.
* @returns {Colour} The colour resulting from the blend.
*/
export function normal(baseColour, blendColour) {
return _blend(baseColour, blendColour, _separableBlend, _normal);
}
/**
* Looks at the colour information in each channel and multiplies the base colour by the blend colour. The result colour is always a darker colour. Multiplying any colour with black produces black. Multiplying any colour with white leaves the colour unchanged.
* @param {Colour} baseColour - The base colour being blended.
* @param {Colour} blendColour - The colour being applied with the designated blend mode.
* @returns {Colour} The colour resulting from the blend.
*/
export function multiply(baseColour, blendColour) {
return _blend(baseColour, blendColour, _separableBlend, _multiply);
}
/**
* Looks at each channel’s colour information and multiplies the inverse of the blend and base colours. The result colour is always a lighter colour. Screening with black leaves the colour unchanged. Screening with white produces white. The effect is similar to projecting multiple photographic slides on top of each other.
* @param {Colour} baseColour - The base colour being blended.
* @param {Colour} blendColour - The colour being applied with the designated blend mode.
* @returns {Colour} The colour resulting from the blend.
*/
export function screen(baseColour, blendColour) {
return _blend(baseColour, blendColour, _separableBlend, _screen);
}
/**
* Multiplies or screens the colours, depending on the base colour. The base colour is not replaced, but mixed with the blend colour to reflect the lightness or darkness of the original colour.
* @param {Colour} baseColour - The base colour being blended.
* @param {Colour} blendColour - The colour being applied with the designated blend mode.
* @returns {Colour} The colour resulting from the blend.
*/
export function overlay(baseColour, blendColour) {
return _blend(baseColour, blendColour, _separableBlend, _overlay);
}
/**
* Looks at the colour information in each channel and selects the base or blend colour—whichever is darker—as the result colour.
* @param {Colour} baseColour - The base colour being blended.
* @param {Colour} blendColour - The colour being applied with the designated blend mode.
* @returns {Colour} The colour resulting from the blend.
*/
export function darken(baseColour, blendColour) {
return _blend(baseColour, blendColour, _separableBlend, _darken);
}
/**
* Looks at the colour information in each channel and selects the base or blend colour—whichever is lighter—as the result colour.
* @param {Colour} baseColour - The base colour being blended.
* @param {Colour} blendColour - The colour being applied with the designated blend mode.
* @returns {Colour} The colour resulting from the blend.
*/
export function lighten(baseColour, blendColour) {
return _blend(baseColour, blendColour, _separableBlend, _lighten);
}
/**
* Looks at the colour information in each channel and brightens the base colour to reflect the blend colour by decreasing contrast between the two. Blending with black produces no change.
* @param {Colour} baseColour - The base colour being blended.
* @param {Colour} blendColour - The colour being applied with the designated blend mode.
* @returns {Colour} The colour resulting from the blend.
*/
export function colourDodge(baseColour, blendColour) {
return _blend(baseColour, blendColour, _separableBlend, _colourDodge);
}
/**
* Looks at the colour information in each channel and darkens the base colour to reflect the blend colour by increasing the contrast between the two. Blending with white produces no change.
* @param {Colour} baseColour - The base colour being blended.
* @param {Colour} blendColour - The colour being applied with the designated blend mode.
* @returns {Colour} The colour resulting from the blend.
*/
export function colourBurn(baseColour, blendColour) {
return _blend(baseColour, blendColour, _separableBlend, _colourBurn);
}
/**
* Multiplies or screens the colours, depending on the blend colour. The effect is similar to shining a harsh spotlight on the colour. If the blend colour (light source) is lighter than 50% gray, the colour is lightened, as if it were screened. This is useful for adding highlights to an colour. If the blend colour is darker than 50% gray, the colour is darkened, as if it were multiplied. This is useful for adding shadows to an colour. Painting with pure black or white results in pure black or white.
* @param {Colour} baseColour - The base colour being blended.
* @param {Colour} blendColour - The colour being applied with the designated blend mode.
* @returns {Colour} The colour resulting from the blend.
*/
export function hardLight(baseColour, blendColour) {
return _blend(baseColour, blendColour, _separableBlend, _hardLight);
}
/**
* Darkens or lightens the colours, depending on the blend colour. The effect is similar to shining a diffused spotlight on the colour. If the blend colour (light source) is lighter than 50% gray, the colour is lightened as if it were dodged. If the blend colour is darker than 50% gray, the colour is darkened as if it were burned in. Painting with pure black or white produces a distinctly darker or lighter area, but does not result in pure black or white.
* @param {Colour} baseColour - The base colour being blended.
* @param {Colour} blendColour - The colour being applied with the designated blend mode.
* @returns {Colour} The colour resulting from the blend.
*/
export function softLight(baseColour, blendColour) {
return _blend(baseColour, blendColour, _separableBlend, _softLight);
}
/**
* Looks at the colour information in each channel and subtracts either the blend colour from the base colour or the base colour from the blend colour, depending on which has the greater brightness value. Blending with white inverts the base colour values; blending with black produces no change.
* @param {Colour} baseColour - The base colour being blended.
* @param {Colour} blendColour - The colour being applied with the designated blend mode.
* @returns {Colour} The colour resulting from the blend.
*/
export function difference(baseColour, blendColour) {
return _blend(baseColour, blendColour, _separableBlend, _difference);
}
/**
* Creates an effect similar to but lower in contrast than the Difference mode. Blending with white inverts the base colour values. Blending with black produces no change.
* @param {Colour} baseColour - The base colour being blended.
* @param {Colour} blendColour - The colour being applied with the designated blend mode.
* @returns {Colour} The colour resulting from the blend.
*/
export function exclusion(baseColour, blendColour) {
return _blend(baseColour, blendColour, _separableBlend, _exclusion);
}
function _blend(baseColour, blendColour, abstractModeCallback, concreteModeCallback) {
let compositeColour = abstractModeCallback(baseColour, blendColour, concreteModeCallback);
let compositeAlpha = _constrain(blendColour.alpha + baseColour.alpha - blendColour.alpha * baseColour.alpha, Colour.alphaMin, Colour.alphaMax);
let red = _constrain(_alphaComposition(baseColour.alpha, blendColour.alpha, compositeAlpha, baseColour.red, blendColour.red, compositeColour.red), Colour.redMin, Colour.redMax);
let green = _constrain(_alphaComposition(baseColour.alpha, blendColour.alpha, compositeAlpha, baseColour.green, blendColour.green, compositeColour.green), Colour.greenMin, Colour.greenMax);
let blue = _constrain(_alphaComposition(baseColour.alpha, blendColour.alpha, compositeAlpha, baseColour.blue, blendColour.blue, compositeColour.blue), Colour.blueMin, Colour.blueMax);
return Colour.RGB(red, green, blue, compositeAlpha);
}
/**
* Apply a separable blend mode callback function to a given base colour and blend colour.
* @param {Colour} baseColour
* @param {Colour} blendColour
* @param {function(number, number):number} callback
* @returns {Colour} The colour resulting from the separable blend
* @ignore
*/
function _separableBlend(baseColour, blendColour, callback) {
const red = callback(baseColour.red / 255, blendColour.red / 255) * 255;
const green = callback(baseColour.green / 255, blendColour.green / 255) * 255;
const blue = callback(baseColour.blue / 255, blendColour.blue / 255) * 255;
return Colour.RGB(red, green, blue);
}
/**
* Applies the appropriate alpha blending to a blend process using [alpha compositing](https://www.w3.org/TR/compositing-1/#blending).
* @param {number} baseAlpha
* @param {number} blendAlpha
* @param {number} compositeAlpha
* @param {number} baseChannel
* @param {number} blendChannel
* @param {number} compositeChannel
* @returns {number}
* @ignore
*/
function _alphaComposition(baseAlpha, blendAlpha, compositeAlpha, baseChannel, blendChannel, compositeChannel) {
const resultChannel = roundInt((1 - baseAlpha) * blendChannel + baseAlpha * compositeChannel);
return (1 - blendAlpha / compositeAlpha) * baseChannel + (blendAlpha / compositeAlpha) * resultChannel;
}
/**
* Force a number to fit between a desired minimum or maximim value.
* @param {number} num - The value to be constrained
* @param {number} min - The minimum value it should be constrained to
* @param {number} max - The maximum value it should be constrained to
* @returns {number} Returns min if num is less than min. Returns max if num is greater than max. Otherwise, it returns back num.
* @ignore
*/
function _constrain(num, min, max) {
return Math.min(Math.max(num, min), max);
}
function _normal(baseChannel, blendChannel) {
return blendChannel;
}
functi