UNPKG

pencil.js

Version:

Nice modular interactive 2D drawing library.

305 lines (274 loc) 7.71 kB
import { colord, extend } from "colord"; import namesPlugin from "colord/plugins/names"; import mixPlugin from "colord/plugins/mix"; import { truncate, constrain, degreeCircle } from "@pencil.js/math"; extend([namesPlugin, mixPlugin]); /** * @module Color */ const parsedKey = Symbol("_parsed"); /** * Color class * @class */ export default class Color { /** * @typedef {Color|String|Number|Array<Number>} ColorDefinition */ /** * Color constructor * @param {ColorDefinition} colorDefinition - Many types accepted (other Color instance, color name, hex string, hex number, red/green/blue/alpha value) * @example * new Color("indigo"); // Any valid CSS color name * new Color("#123456"); // Hex string definition * new Color("#123"); // Hex shorthand string definition, #123 <=> #112233 * new Color(0x123456); // Hex number definition * new Color(0.1, 0.2, 0.3); // Red, Green, Blue definition * Every definition can have one more optional parameter for alpha (opacity) * new Color("violet", 0.5); */ constructor (...colorDefinition) { this[parsedKey] = null; this.set(...colorDefinition); } /** * Create a new copy of this color * @return {Color} */ clone () { return new Color(this); } /** * Get the red channel value * @return {Number} */ get red () { return this[parsedKey].rgba.r / 255; } /** * Set the red channel * @param {Number} value - New value */ set red (value) { this[parsedKey].rgba.r = constrain(value * 255, 0, 255); } /** * Get the green channel value * @return {Number} */ get green () { return this[parsedKey].rgba.g / 255; } /** * Set the green channel * @param {Number} value - New value */ set green (value) { this[parsedKey].rgba.g = constrain(value * 255, 0, 255); } /** * Get the blue channel value * @return {Number} */ get blue () { return this[parsedKey].rgba.b / 255; } /** * Set the blue channel * @param {Number} value - New value */ set blue (value) { this[parsedKey].rgba.b = constrain(value * 255, 0, 255); } /** * Get the transparency channel value * @return {Number} */ get alpha () { return this[parsedKey].rgba.a; } /** * Set the transparency channel * @param {Number} value - New value */ set alpha (value) { this[parsedKey].rgba.a = constrain(value, 0, 1); } /** * Return an array with red, green and blue value * @example [0.1, 0.2, 0.3] * @return {Array<Number>} */ get array () { return [ this.red, this.green, this.blue, ]; } /** * Return hexadecimal rgb notation * @example "#123456" * @return {String} */ get hex () { return this[parsedKey].toHex(); } /** * Return hexadecimal rgb notation * @example "#123456" * @return {String} */ get rgb () { return this.hex; } /** * Return the closest CSS color name * @example "aliceblue" * @return {String} */ get name () { return this[parsedKey].toName({ closest: true, }); } // TODO: do we need more getters ? User only need to interact with Color, not read values. /** * Change this values * @param {ColorDefinition} colorDefinition - Any supported color definition (see constructor) * @return {Color} Itself */ set (...colorDefinition) { const [first] = colorDefinition; let input; let alpha; if (first instanceof Color) { input = first[parsedKey].rgba; [, alpha = input.a] = colorDefinition; } else if (typeof first === "string") { input = first; [, alpha = 1] = colorDefinition; } else if (first === undefined || first === null) { input = "#000"; alpha = 1; } else if (colorDefinition.length < 3) { input = `#${truncate(colorDefinition[0]).toString(16)}`; [, alpha = 1] = colorDefinition; } else { const [r, g, b] = colorDefinition.slice(0, 3).map(value => value * 255); [,,, alpha = 1] = colorDefinition; input = { r, g, b, }; } this[parsedKey] = colord(input).alpha(alpha); return this; } /** * Change to its greyscale value * @return {Color} Itself */ grey () { this[parsedKey] = this[parsedKey].grayscale(); return this; } /** * Change hue value (0 = red, 0.5 = blue, 1 = red, 1.5 = blue ...) * @param {Number} value - Any value between 0 and 1 * @return {Color} Itself */ hue (value) { this[parsedKey] = this[parsedKey].hue(value * degreeCircle); return this; } /** * Change saturation value (0 = grey, 1 = pure color) * @param {Number} value - Any value between 0 and 1 * @return {Color} Itself */ saturation (value) { const hsl = this[parsedKey].toHsl(); hsl.s = value * 100; this[parsedKey] = colord(hsl); return this; } /** * Change lightness value (0 = black, 0.5 = pure color, 1 = white) * @param {Number} value - Any value between 0 and 1 * @return {Color} Itself */ lightness (value) { const hsl = this[parsedKey].toHsl(); hsl.l = value * 100; this[parsedKey] = colord(hsl); return this; } /** * Invert the color value * @return {Color} Itself */ reverse () { this[parsedKey] = this[parsedKey].invert(); return this; } /** * Restrict the color space to an amount of possible value * @param {Number} number - Number of allowed value * @return {Color} Itself */ level (number) { const { r, g, b } = this[parsedKey].rgba; const p = 255 / number; this[parsedKey].rgba.r = truncate(r / p) * p + (p / 2); this[parsedKey].rgba.g = truncate(g / p) * p + (p / 2); this[parsedKey].rgba.b = truncate(b / p) * p + (p / 2); return this; } /** * Change the color toward another color * @param {ColorDefinition} colorDefinition - Any other color * @param {Number} ratio - Ratio of distance to move (0 = no change, 0.5 = equal mix, 1 = same as target color) * @return {Color} Itself */ lerp (colorDefinition, ratio) { const color = Color.from(colorDefinition); this[parsedKey] = this[parsedKey].mix(color[parsedKey], ratio); return this; } /** * @return {String} */ toString () { return this.hex; } /** * Return a json ready array * @return {Array<Number>} */ toJSON () { return [ ...this.array, this.alpha, ]; } /** * Return an instance from a generic definition * @param {ColorDefinition} colorDefinition - Any valid color definition (see constructor) * @return {Color} */ static from (...colorDefinition) { const param = colorDefinition[0]; if (param instanceof Color) { return param; } return new Color(...colorDefinition); } } Color.prototype.gray = Color.prototype.grey; Color.prototype.mix = Color.prototype.lerp;