UNPKG

colorjs.io

Version:

Let’s get serious about color

1 lines 252 kB
{"version":3,"file":"color.cjs","sources":["../src/multiply-matrices.js","../src/util.js","../src/hooks.js","../src/defaults.js","../src/adapt.js","../src/space.js","../src/spaces/xyz-d65.js","../src/rgbspace.js","../src/parse.js","../src/getColor.js","../src/getAll.js","../src/get.js","../src/setAll.js","../src/set.js","../src/spaces/xyz-d50.js","../src/spaces/lab.js","../src/angles.js","../src/spaces/lch.js","../src/deltaE/deltaE2000.js","../src/inGamut.js","../src/clone.js","../src/toGamut.js","../src/to.js","../src/serialize.js","../src/spaces/rec2020-linear.js","../src/spaces/rec2020.js","../src/spaces/p3-linear.js","../src/spaces/srgb-linear.js","../src/keywords.js","../src/spaces/srgb.js","../src/spaces/p3.js","../src/display.js","../src/distance.js","../src/equals.js","../src/luminance.js","../src/contrast/WCAG21.js","../src/contrast/APCA.js","../src/contrast/Michelson.js","../src/contrast/Weber.js","../src/contrast/Lstar.js","../src/spaces/lab-d65.js","../src/contrast/deltaPhi.js","../src/contrast.js","../src/chromaticity.js","../src/deltaE/deltaE76.js","../src/deltaE/deltaECMC.js","../src/spaces/xyz-abs-d65.js","../src/spaces/jzazbz.js","../src/spaces/jzczhz.js","../src/deltaE/deltaEJz.js","../src/spaces/ictcp.js","../src/deltaE/deltaEITP.js","../src/spaces/oklab.js","../src/deltaE/deltaEOK.js","../src/deltaE.js","../src/variations.js","../src/interpolation.js","../src/spaces/hsl.js","../src/spaces/hsv.js","../src/spaces/hwb.js","../src/spaces/a98rgb-linear.js","../src/spaces/a98rgb.js","../src/spaces/prophoto-linear.js","../src/spaces/prophoto.js","../src/spaces/oklch.js","../src/spaces/rec2100-pq.js","../src/spaces/rec2100-hlg.js","../src/CATs.js","../src/spaces/acescg.js","../src/spaces/acescc.js","../src/color.js","../src/spaces/index.js","../src/space-accessors.js","../src/index.js"],"sourcesContent":["// A is m x n. B is n x p. product is m x p.\nexport default function multiplyMatrices(A, B) {\n\tlet m = A.length;\n\n\tif (!Array.isArray(A[0])) {\n\t\t// A is vector, convert to [[a, b, c, ...]]\n\t\tA = [A];\n\t}\n\n\tif (!Array.isArray(B[0])) {\n\t\t// B is vector, convert to [[a], [b], [c], ...]]\n\t\tB = B.map(x => [x]);\n\t}\n\n\tlet p = B[0].length;\n\tlet B_cols = B[0].map((_, i) => B.map(x => x[i])); // transpose B\n\tlet product = A.map(row => B_cols.map(col => {\n\t\tlet ret = 0;\n\n\t\tif (!Array.isArray(row)) {\n\t\t\tfor (let c of col) {\n\t\t\t\tret += row * c;\n\t\t\t}\n\n\t\t\treturn ret;\n\t\t}\n\n\t\tfor (let i=0; i < row.length; i++) {\n\t\t\tret += row[i] * (col[i] || 0);\n\t\t}\n\n\t\treturn ret;\n\t}));\n\n\tif (m === 1) {\n\t\tproduct = product[0]; // Avoid [[a, b, c, ...]]\n\t}\n\n\tif (p === 1) {\n\t\treturn product.map(x => x[0]); // Avoid [[a], [b], [c], ...]]\n\t}\n\n\treturn product;\n}\n","/**\n * Various utility functions\n */\n\nexport {default as multiplyMatrices} from \"./multiply-matrices.js\";\n\n/**\n * Check if a value is a string (including a String object)\n * @param {*} str - Value to check\n * @returns {boolean}\n */\nexport function isString (str) {\n\treturn type(str) === \"string\";\n}\n\n/**\n * Determine the internal JavaScript [[Class]] of an object.\n * @param {*} o - Value to check\n * @returns {string}\n */\nexport function type (o) {\n\tlet str = Object.prototype.toString.call(o);\n\n\treturn (str.match(/^\\[object\\s+(.*?)\\]$/)[1] || \"\").toLowerCase();\n}\n\n/**\n * Round a number to a certain number of significant digits\n * @param {number} n - The number to round\n * @param {number} precision - Number of significant digits\n */\nexport function toPrecision(n, precision) {\n\tn = +n;\n\tprecision = +precision;\n\tlet integerLength = (Math.floor(n) + \"\").length;\n\n\tif (precision > integerLength) {\n\t\treturn +n.toFixed(precision - integerLength);\n\t}\n\telse {\n\t\tlet p10 = 10 ** (integerLength - precision);\n\t\treturn Math.round(n / p10) * p10;\n\t}\n}\n\n/**\n* Parse a CSS function, regardless of its name and arguments\n* @param String str String to parse\n* @return {{name, args, rawArgs}}\n*/\nexport function parseFunction (str) {\n\tif (!str) {\n\t\treturn;\n\t}\n\n\tstr = str.trim();\n\n\tconst isFunctionRegex = /^([a-z]+)\\((.+?)\\)$/i;\n\tconst isNumberRegex = /^-?[\\d.]+$/;\n\tlet parts = str.match(isFunctionRegex);\n\n\tif (parts) {\n\t\t// It is a function, parse args\n\t\tlet args = [];\n\t\tparts[2].replace(/\\/?\\s*([-\\w.]+(?:%|deg)?)/g, ($0, arg) => {\n\t\t\tif (/%$/.test(arg)) {\n\t\t\t\t// Convert percentages to 0-1 numbers\n\t\t\t\targ = new Number(arg.slice(0, -1) / 100);\n\t\t\t\targ.type = \"<percentage>\";\n\t\t\t}\n\t\t\telse if (/deg$/.test(arg)) {\n\t\t\t\t// Drop deg from degrees and convert to number\n\t\t\t\t// TODO handle other units too\n\t\t\t\targ = new Number(+arg.slice(0, -3));\n\t\t\t\targ.type = \"<angle>\";\n\t\t\t\targ.unit = \"deg\";\n\t\t\t}\n\t\t\telse if (isNumberRegex.test(arg)) {\n\t\t\t\t// Convert numerical args to numbers\n\t\t\t\targ = new Number(arg);\n\t\t\t\targ.type = \"<number>\";\n\t\t\t}\n\n\t\t\tif ($0.startsWith(\"/\")) {\n\t\t\t\t// It's alpha\n\t\t\t\targ = arg instanceof Number? arg : new Number(arg);\n\t\t\t\targ.alpha = true;\n\t\t\t}\n\n\t\t\targs.push(arg);\n\t\t});\n\n\t\treturn {\n\t\t\tname: parts[1].toLowerCase(),\n\t\t\trawName: parts[1],\n\t\t\trawArgs: parts[2],\n\t\t\t// An argument could be (as of css-color-4):\n\t\t\t// a number, percentage, degrees (hue), ident (in color())\n\t\t\targs\n\t\t};\n\t}\n}\n\nexport function last(arr) {\n\treturn arr[arr.length - 1];\n}\n\nexport function interpolate (start, end, p) {\n\tif (isNaN(start)) {\n\t\treturn end;\n\t}\n\n\tif (isNaN(end)) {\n\t\treturn start;\n\t}\n\n\treturn start + (end - start) * p;\n}\n\nexport function interpolateInv (start, end, value) {\n\treturn (value - start) / (end - start);\n}\n\nexport function mapRange(from, to, value) {\n\treturn interpolate(to[0], to[1], interpolateInv(from[0], from[1], value));\n}\n\nexport function parseCoordGrammar (coordGrammars) {\n\treturn coordGrammars.map(coordGrammar => {\n\t\treturn coordGrammar.split(\"|\").map(type => {\n\t\t\ttype = type.trim();\n\t\t\tlet range = type.match(/^(<[a-z]+>)\\[(-?[.\\d]+),\\s*(-?[.\\d]+)\\]?$/);\n\n\t\t\tif (range) {\n\t\t\t\tlet ret = new String(range[1]);\n\t\t\t\tret.range = [+range[2], +range[3]];\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\treturn type;\n\t\t});\n\t});\n}","/**\n * A class for adding deep extensibility to any piece of JS code\n */\nexport class Hooks {\n\tadd (name, callback, first) {\n\t\tif (typeof arguments[0] != \"string\") {\n\t\t\t// Multiple hooks\n\t\t\tfor (var name in arguments[0]) {\n\t\t\t\tthis.add(name, arguments[0][name], arguments[1]);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\t(Array.isArray(name)? name : [name]).forEach(function(name) {\n\t\t\tthis[name] = this[name] || [];\n\n\t\t\tif (callback) {\n\t\t\t\tthis[name][first? \"unshift\" : \"push\"](callback);\n\t\t\t}\n\t\t}, this);\n\t}\n\n\trun (name, env) {\n\t\tthis[name] = this[name] || [];\n\t\tthis[name].forEach(function(callback) {\n\t\t\tcallback.call(env && env.context? env.context : env, env);\n\t\t});\n\t}\n};\n\n/**\n * The instance of {@link Hooks} used throughout Color.js\n */\nconst hooks = new Hooks();\n\nexport default hooks;","// Global defaults one may want to configure\nconst hasDOM = typeof document !== \"undefined\";\n\nexport default {\n\tgamut_mapping: \"lch.c\",\n\tprecision: 5,\n\tdeltaE: \"76\", // Default deltaE method\n}","import hooks from \"./hooks.js\";\nimport {multiplyMatrices} from \"./util.js\"\n\nexport const WHITES = {\n\t// for compatibility, the four-digit chromaticity-derived ones everyone else uses\n\tD50: [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585],\n\tD65: [0.3127 / 0.3290, 1.00000, (1.0 - 0.3127 - 0.3290) / 0.3290],\n}\n\nexport function getWhite(name) {\n\tif (Array.isArray(name)) {\n\t\treturn name;\n\t}\n\n\treturn WHITES[name];\n}\n\n// Adapt XYZ from white point W1 to W2\nexport default function adapt (W1, W2, XYZ, options = {}) {\n\tW1 = getWhite(W1);\n\tW2 = getWhite(W2);\n\n\tif (!W1 || !W2) {\n\t\tthrow new TypeError(`Missing white point to convert ${!W1? \"from\" : \"\"}${!W1&&!W2? \"/\" : \"\"}${!W2? \"to\" : \"\"}`);\n\t}\n\n\tif (W1 === W2) {\n\t\t// Same whitepoints, no conversion needed\n\t\treturn XYZ;\n\t}\n\n\tlet env = {W1, W2, XYZ, options};\n\n\thooks.run(\"chromatic-adaptation-start\", env);\n\n\tif (!env.M) {\n\t\tif (env.W1 === WHITES.D65 && env.W2 === WHITES.D50) {\n\t\t\tenv.M = [\n\t\t\t\t[ 1.0479298208405488, 0.022946793341019088, -0.05019222954313557 ],\n\t\t\t\t[ 0.029627815688159344, 0.990434484573249, -0.01707382502938514 ],\n\t\t\t\t[ -0.009243058152591178, 0.015055144896577895, 0.7518742899580008 ]\n\t\t\t];\n\t\t}\n\t\telse if (env.W1 === WHITES.D50 && env.W2 === WHITES.D65) {\n\n\t\t\tenv.M = [\n\t\t\t\t[ 0.9554734527042182, -0.023098536874261423, 0.0632593086610217 ],\n\t\t\t\t[ -0.028369706963208136, 1.0099954580058226, 0.021041398966943008 ],\n\t\t\t\t[ 0.012314001688319899, -0.020507696433477912, 1.3303659366080753 ]\n\t\t\t];\n\t\t}\n\t}\n\n\thooks.run(\"chromatic-adaptation-end\", env);\n\n\tif (env.M) {\n\t\treturn multiplyMatrices(env.M, env.XYZ);\n\t}\n\telse {\n\t\tthrow new TypeError(\"Only Bradford CAT with white points D50 and D65 supported for now.\");\n\t}\n}\n","import {type, parseCoordGrammar, toPrecision, mapRange} from \"./util.js\";\nimport {getWhite} from \"./adapt.js\";\nimport hooks from \"./hooks.js\";\n\nconst ε = .000075;\n\n/**\n * Class to represent a color space\n */\nexport default class ColorSpace {\n\tconstructor (options) {\n\t\tthis.id = options.id;\n\t\tthis.name = options.name;\n\t\tthis.base = options.base ? ColorSpace.get(options.base) : null;\n\t\tthis.aliases = options.aliases;\n\n\t\tif (this.base) {\n\t\t\tthis.fromBase = options.fromBase;\n\t\t\tthis.toBase = options.toBase;\n\t\t}\n\n\t\t// Coordinate metadata\n\n\t\tlet coords = options.coords ?? this.base.coords;\n\t\tthis.coords = coords;\n\n\t\t// White point\n\n\t\tlet white = options.white ?? this.base.white ?? \"D65\";\n\t\tthis.white = getWhite(white);\n\n\t\t// Sort out formats\n\n\t\tthis.formats = options.formats ?? {};\n\n\t\tfor (let name in this.formats) {\n\t\t\tlet format = this.formats[name];\n\t\t\tformat.type ||= \"function\";\n\t\t\tformat.name ||= name;\n\t\t}\n\n\t\tif (options.cssId && !this.formats.functions?.color) {\n\t\t\tthis.formats.color = { id: options.cssId };\n\t\t\tObject.defineProperty(this, \"cssId\", {value: options.cssId});\n\t\t}\n\t\telse if (this.formats?.color && !this.formats?.color.id) {\n\t\t\tthis.formats.color.id = this.id;\n\t\t}\n\n\t\t// Other stuff\n\t\tthis.referred = options.referred;\n\n\t\t// Compute ancestors and store them, since they will never change\n\t\tthis.#path = this.#getPath().reverse();\n\n\t\thooks.run(\"colorspace-init-end\", this);\n\t}\n\n\tinGamut (coords, {epsilon = ε} = {}) {\n\t\tif (this.isPolar) {\n\t\t\t// Do not check gamut through polar coordinates\n\t\t\tcoords = this.toBase(coords);\n\n\t\t\treturn this.base.inGamut(coords, {epsilon});\n\t\t}\n\n\t\tlet coordMeta = Object.values(this.coords);\n\n\t\treturn coords.every((c, i) => {\n\t\t\tlet meta = coordMeta[i];\n\n\t\t\tif (meta.type !== \"angle\" && meta.range) {\n\t\t\t\tif (Number.isNaN(c)) {\n\t\t\t\t\t// NaN is always in gamut\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tlet [min, max] = meta.range;\n\t\t\t\treturn (min === undefined || c >= min - epsilon)\n\t\t\t\t && (max === undefined || c <= max + epsilon);\n\t\t\t}\n\n\t\t\treturn true;\n\t\t});\n\t}\n\n\tget cssId () {\n\t\treturn this.formats.functions?.color?.id || this.id;\n\t}\n\n\tget isPolar() {\n\t\tfor (let id in this.coords) {\n\t\t\tif (this.coords[id].type === \"angle\") {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t#processFormat(format) {\n\t\tif (format.coords && !format.coordGrammar) {\n\t\t\tformat.type ||= \"function\";\n\t\t\tformat.name ||= \"color\";\n\n\t\t\t// Format has not been processed\n\t\t\tformat.coordGrammar = parseCoordGrammar(format.coords);\n\n\t\t\tlet coordFormats = Object.entries(this.coords).map(([id, coordMeta], i) => {\n\t\t\t\t// Preferred format for each coord is the first one\n\t\t\t\tlet outputType = format.coordGrammar[i][0];\n\n\t\t\t\tlet fromRange = coordMeta.range || coordMeta.refRange;\n\t\t\t\tlet toRange = outputType.range, suffix = \"\";\n\n\t\t\t\t// Non-strict equals intentional since outputType could be a string object\n\t\t\t\tif (outputType == \"<percentage>\") {\n\t\t\t\t\ttoRange = [0, 100];\n\t\t\t\t\tsuffix = \"%\";\n\t\t\t\t}\n\t\t\t\telse if (outputType == \"<angle>\") {\n\t\t\t\t\tsuffix = \"deg\";\n\t\t\t\t}\n\n\t\t\t\treturn {fromRange, toRange, suffix};\n\t\t\t});\n\n\t\t\tformat.serializeCoords = (coords, precision) => {\n\t\t\t\treturn coords.map((c, i) => {\n\t\t\t\t\tlet {fromRange, toRange, suffix} = coordFormats[i];\n\n\t\t\t\t\tif (fromRange && toRange) {\n\t\t\t\t\t\tc = mapRange(fromRange, toRange, c);\n\t\t\t\t\t}\n\n\t\t\t\t\tc = toPrecision(c, precision);\n\n\t\t\t\t\tif (suffix) {\n\t\t\t\t\t\tc += suffix;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn c;\n\t\t\t\t})\n\t\t\t};\n\t\t}\n\n\t\treturn format;\n\t}\n\n\tgetFormat (format) {\n\t\tif (typeof format === \"object\") {\n\t\t\tformat = this.#processFormat(format);\n\t\t\treturn format;\n\t\t}\n\n\t\tlet ret;\n\t\tif (format === \"default\") {\n\t\t\t// Get first format\n\t\t\tret = Object.values(this.formats)[0];\n\t\t}\n\t\telse {\n\t\t\tret = this.formats[format];\n\t\t}\n\n\t\tif (ret) {\n\t\t\tret = this.#processFormat(ret);\n\t\t\treturn ret;\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t#path\n\n\t#getPath () {\n\t\tlet ret = [this];\n\n\t\tfor (let space = this; space = space.base;) {\n\t\t\tret.push(space);\n\t\t}\n\n\t\treturn ret;\n\t}\n\n\tto (space, coords) {\n\t\tif (arguments.length === 1) {\n\t\t\t[space, coords] = [space.space, space.coords];\n\t\t}\n\n\t\tspace = ColorSpace.get(space);\n\n\t\tif (this === space) {\n\t\t\t// Same space, no change needed\n\t\t\treturn coords;\n\t\t}\n\n\t\t// Convert NaN to 0, which seems to be valid in every coordinate of every color space\n\t\tcoords = coords.map(c => Number.isNaN(c)? 0 : c);\n\n\t\t// Find connection space = lowest common ancestor in the base tree\n\t\tlet myPath = this.#path;\n\t\tlet otherPath = space.#path;\n\n\t\tlet connectionSpace, connectionSpaceIndex;\n\n\t\tfor (let i=0; i < myPath.length; i++) {\n\t\t\tif (myPath[i] === otherPath[i]) {\n\t\t\t\tconnectionSpace = myPath[i];\n\t\t\t\tconnectionSpaceIndex = i;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (!connectionSpace) {\n\t\t\t// This should never happen\n\t\t\tthrow new Error(`Cannot convert between color spaces ${this} and ${space}: no connection space was found`);\n\t\t}\n\n\t\t// Go up from current space to connection space\n\t\tfor (let i = myPath.length - 1; i > connectionSpaceIndex; i--) {\n\t\t\tcoords = myPath[i].toBase(coords);\n\t\t}\n\n\t\t// Go down from connection space to target space\n\t\tfor (let i = connectionSpaceIndex + 1; i < otherPath.length; i++) {\n\t\t\tcoords = otherPath[i].fromBase(coords);\n\t\t}\n\n\t\treturn coords;\n\t}\n\n\tfrom (space, coords) {\n\t\tif (arguments.length === 1) {\n\t\t\t[space, coords] = [space.space, space.coords];\n\t\t}\n\n\t\tspace = ColorSpace.get(space);\n\n\t\treturn space.to(this, coords);\n\t}\n\n\ttoString () {\n\t\treturn `${this.name} (${this.id})`;\n\t}\n\n\tgetMinCoords () {\n\t\tlet ret = [];\n\n\t\tfor (let id in this.coords) {\n\t\t\tlet meta = this.coords[id];\n\t\t\tlet range = meta.range || meta.refRange;\n\t\t\tret.push(range?.min ?? 0);\n\t\t}\n\n\t\treturn ret;\n\t}\n\n\tstatic registry = {}\n\n\t// Returns array of unique color spaces\n\tstatic get all () {\n\t\treturn [...new Set(Object.values(ColorSpace.registry))];\n\t}\n\n\tstatic register (id, space) {\n\t\tif (arguments.length === 1) {\n\t\t\tspace = arguments[0];\n\t\t\tid = space.id;\n\t\t}\n\n\t\tspace = this.get(space);\n\n\t\tif (this.registry[id] && this.registry[id] !== space) {\n\t\t\tthrow new Error(`Duplicate color space registration: '${id}'`);\n\t\t}\n\t\tthis.registry[id] = space;\n\n\t\t// Register aliases when called without an explicit ID.\n\t\tif (arguments.length === 1 && space.aliases) {\n\t\t\tfor (let alias of space.aliases) {\n\t\t\t\tthis.register(alias, space);\n\t\t\t}\n\t\t}\n\n\t\treturn space;\n\t}\n\n\t/**\n\t * Lookup ColorSpace object by name\n\t * @param {ColorSpace | string} name\n\t */\n\tstatic get (space, ...alternatives) {\n\t\tif (!space || space instanceof ColorSpace) {\n\t\t\treturn space;\n\t\t}\n\n\t\tlet argType = type(space);\n\n\t\tif (argType === \"string\") {\n\t\t\t// It's a color space id\n\t\t\tlet ret = ColorSpace.registry[space.toLowerCase()];\n\n\t\t\tif (!ret) {\n\t\t\t\tthrow new TypeError(`No color space found with id = \"${space}\"`);\n\t\t\t}\n\n\t\t\treturn ret;\n\t\t}\n\n\t\tif (alternatives.length) {\n\t\t\treturn ColorSpace.get(...alternatives);\n\t\t}\n\n\t\tthrow new TypeError(`${space} is not a valid color space`);\n\t}\n\n\t/**\n\t * Get metadata about a coordinate of a color space\n\t *\n\t * @static\n\t * @param {Array | string} ref\n\t * @param {ColorSpace | string} [workingSpace]\n\t * @return {Object}\n\t */\n\tstatic resolveCoord (ref, workingSpace) {\n\t\tlet coordType = type(ref);\n\t\tlet space, coord;\n\n\t\tif (coordType === \"string\") {\n\t\t\tif (ref.includes(\".\")) {\n\t\t\t\t// Absolute coordinate\n\t\t\t\t[space, coord] = ref.split(\".\");\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Relative coordinate\n\t\t\t\t[space, coord] = [, ref];\n\t\t\t}\n\t\t}\n\t\telse if (Array.isArray(ref)) {\n\t\t\t[space, coord] = ref;\n\t\t}\n\t\telse {\n\t\t\t// Object\n\t\t\tspace = ref.space;\n\t\t\tcoord = ref.coordId;\n\t\t}\n\n\t\tspace = ColorSpace.get(space);\n\n\t\tif (!space) {\n\t\t\tspace = workingSpace;\n\t\t}\n\n\t\tif (!space) {\n\t\t\tthrow new TypeError(`Cannot resolve coordinate reference ${ref}: No color space specified and relative references are not allowed here`);\n\t\t}\n\n\t\tcoordType = type(coord);\n\n\t\tif (coordType === \"number\" || coordType === \"string\" && coord >= 0) {\n\t\t\t// Resolve numerical coord\n\t\t\tlet meta = Object.entries(space.coords)[coord];\n\n\t\t\tif (meta) {\n\t\t\t\treturn {space, id: meta[0], index: coord, ...meta[1]}\n\t\t\t}\n\t\t}\n\n\t\tspace = ColorSpace.get(space);\n\n\t\tlet normalizedCoord = coord.toLowerCase();\n\n\t\tlet i = 0;\n\t\tfor (let id in space.coords) {\n\t\t\tlet meta = space.coords[id];\n\n\t\t\tif (id.toLowerCase() === normalizedCoord || meta.name?.toLowerCase() === normalizedCoord) {\n\t\t\t\treturn {space, id, index: i, ...meta};\n\t\t\t}\n\n\t\t\ti++;\n\t\t}\n\n\t\tthrow new TypeError(`No \"${coord}\" coordinate found in ${space.name}. Its coordinates are: ${Object.keys(space.coords).join(\", \")}`);\n\t}\n\n\tstatic DEFAULT_FORMAT = {\n\t\ttype: \"functions\",\n\t\tname: \"color\",\n\t}\n}\n","import ColorSpace from \"../space.js\";\n\nexport default new ColorSpace({\n\tid: \"xyz-d65\",\n\tname: \"XYZ D65\",\n\tcoords: {\n\t\tx: {name: \"X\"},\n\t\ty: {name: \"Y\"},\n\t\tz: {name: \"Z\"},\n\t},\n\twhite: \"D65\",\n\tformats: {\n\t\tcolor: {\n\t\t\tids: ['xyz-d65', 'xyz'],\n\t\t}\n\t},\n\taliases: ['xyz'],\n});\n","import ColorSpace from \"./space.js\";\nimport {multiplyMatrices} from \"./util.js\";\nimport adapt from \"./adapt.js\";\nimport XYZ_D65 from \"./spaces/xyz-d65.js\";\n\n/**\n * Convenience class for RGB color spaces\n * @extends {ColorSpace}\n */\nexport default class RGBColorSpace extends ColorSpace {\n\t/**\n\t * Creates a new RGB ColorSpace.\n\t * If coords are not specified, they will use the default RGB coords.\n\t * Instead of `fromBase()` and `toBase()` functions,\n\t * you can specify to/from XYZ matrices and have `toBase()` and `fromBase()` automatically generated.\n\t * @param {*} options - Same options as {@link ColorSpace} plus:\n\t * @param {number[][]} options.toXYZ_M - Matrix to convert to XYZ\n\t * @param {number[][]} options.fromXYZ_M - Matrix to convert from XYZ\n\t */\n\tconstructor (options) {\n\t\tif (!options.coords) {\n\t\t\toptions.coords = {\n\t\t\t\tr: {\n\t\t\t\t\trange: [0, 1],\n\t\t\t\t\tname: \"Red\"\n\t\t\t\t},\n\t\t\t\tg: {\n\t\t\t\t\trange: [0, 1],\n\t\t\t\t\tname: \"Green\"\n\t\t\t\t},\n\t\t\t\tb: {\n\t\t\t\t\trange: [0, 1],\n\t\t\t\t\tname: \"Blue\"\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\tif (!options.base) {\n\t\t\toptions.base = XYZ_D65;\n\t\t}\n\n\t\tif (options.toXYZ_M && options.fromXYZ_M) {\n\t\t\toptions.toBase ??= rgb => {\n\t\t\t\tlet xyz = multiplyMatrices(options.toXYZ_M, rgb);\n\n\t\t\t\tif (this.white !== this.base.white) {\n\t\t\t\t\t// Perform chromatic adaptation\n\t\t\t\t\txyz = adapt(this.white, this.base.white, xyz);\n\t\t\t\t}\n\n\t\t\t\treturn xyz;\n\t\t\t};\n\n\t\t\toptions.fromBase ??= xyz => {\n\t\t\t\txyz = adapt(this.base.white, this.white, xyz);\n\t\t\t\treturn multiplyMatrices(options.fromXYZ_M, xyz);\n\t\t\t};\n\t\t}\n\n\t\toptions.referred ??= \"display\";\n\n\t\tsuper(options);\n\t}\n}\n","import * as util from \"./util.js\";\nimport hooks from \"./hooks.js\";\nimport ColorSpace from \"./space.js\";\n\n// CSS color to Color object\nexport default function parse (str) {\n\tlet env = {\"str\": String(str)?.trim()};\n\thooks.run(\"parse-start\", env);\n\n\tif (env.color) {\n\t\treturn env.color;\n\t}\n\n\tenv.parsed = util.parseFunction(env.str);\n\n\tif (env.parsed) {\n\t\t// Is a functional syntax\n\t\tlet name = env.parsed.name;\n\n\t\tif (name === \"color\") {\n\t\t\t// color() function\n\t\t\tlet id = env.parsed.args.shift();\n\t\t\tlet alpha = env.parsed.rawArgs.indexOf(\"/\") > 0? env.parsed.args.pop() : 1;\n\n\t\t\tfor (let space of ColorSpace.all) {\n\t\t\t\tlet colorSpec = space.getFormat(\"color\");\n\n\t\t\t\tif (colorSpec) {\n\t\t\t\t\tif (id === colorSpec.id || colorSpec.ids?.includes(id)) {\n\t\t\t\t\t\t// From https://drafts.csswg.org/css-color-4/#color-function\n\t\t\t\t\t\t// If more <number>s or <percentage>s are provided than parameters that the colorspace takes, the excess <number>s at the end are ignored.\n\t\t\t\t\t\t// If less <number>s or <percentage>s are provided than parameters that the colorspace takes, the missing parameters default to 0. (This is particularly convenient for multichannel printers where the additional inks are spot colors or varnishes that most colors on the page won’t use.)\n\t\t\t\t\t\tlet argCount = Object.keys(space.coords).length;\n\t\t\t\t\t\tlet coords = Array(argCount).fill(0);\n\t\t\t\t\t\tcoords.forEach((_, i) => coords[i] = env.parsed.args[i] || 0);\n\n\t\t\t\t\t\treturn {spaceId: space.id, coords, alpha};\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Not found\n\t\t\tlet didYouMean = \"\";\n\t\t\tif (id in ColorSpace.registry) {\n\t\t\t\t// Used color space id instead of color() id, these are often different\n\t\t\t\tlet cssId = ColorSpace.registry[id].formats?.functions?.color?.id;\n\n\t\t\t\tif (cssId) {\n\t\t\t\t\tdidYouMean = `Did you mean color(${cssId})?`;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow new TypeError(`Cannot parse color(${id}). ` + (didYouMean || \"Missing a plugin?\"));\n\t\t}\n\t\telse {\n\t\t\tfor (let space of ColorSpace.all) {\n\t\t\t\t// color space specific function\n\t\t\t\tlet format = space.getFormat(name);\n\t\t\t\tif (format && format.type === \"function\") {\n\t\t\t\t\tlet alpha = 1;\n\n\t\t\t\t\tif (format.lastAlpha || util.last(env.parsed.args).alpha) {\n\t\t\t\t\t\talpha = env.parsed.args.pop();\n\t\t\t\t\t}\n\n\t\t\t\t\tlet coords = env.parsed.args;\n\n\t\t\t\t\tif (format.coordGrammar) {\n\t\t\t\t\t\tObject.entries(space.coords).forEach(([id, coordMeta], i) => {\n\t\t\t\t\t\t\tlet coordGrammar = format.coordGrammar[i];\n\t\t\t\t\t\t\tlet providedType = coords[i]?.type;\n\n\t\t\t\t\t\t\t// Find grammar alternative that matches the provided type\n\t\t\t\t\t\t\t// Non-strict equals is intentional because we are comparing w/ string objects\n\t\t\t\t\t\t\tcoordGrammar = coordGrammar.find(c => c == providedType);\n\n\t\t\t\t\t\t\t// Check that each coord conforms to its grammar\n\t\t\t\t\t\t\tif (!coordGrammar) {\n\t\t\t\t\t\t\t\t// Type does not exist in the grammar, throw\n\t\t\t\t\t\t\t\tlet coordName = coordMeta.name || id;\n\t\t\t\t\t\t\t\tthrow new TypeError(`${providedType} not allowed for ${coordName} in ${name}()`);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tlet fromRange = coordGrammar.range;\n\n\t\t\t\t\t\t\tif (providedType === \"<percentage>\") {\n\t\t\t\t\t\t\t\tfromRange ||= [0, 1];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tlet toRange = coordMeta.range || coordMeta.refRange;\n\n\t\t\t\t\t\t\tif (fromRange && toRange) {\n\n\t\t\t\t\t\t\t\tcoords[i] = util.mapRange(fromRange, toRange, coords[i]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tspaceId: space.id,\n\t\t\t\t\t\tcoords, alpha\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\telse {\n\t\t// Custom, colorspace-specific format\n\t\tfor (let space of ColorSpace.all) {\n\n\t\t\tfor (let formatId in space.formats) {\n\t\t\t\tlet format = space.formats[formatId];\n\n\t\t\t\tif (format.type !== \"custom\") {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (format.test && !format.test(env.str)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet color = format.parse(env.str);\n\n\t\t\t\tif (color) {\n\t\t\t\t\tcolor.alpha ??= 1;\n\t\t\t\t\treturn color;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\n\t// If we're here, we couldn't parse\n\tthrow new TypeError(`Could not parse ${str} as a color. Missing a plugin?`);\n}","import ColorSpace from \"./space.js\";\nimport {isString} from \"./util.js\";\nimport parse from \"./parse.js\";\n\n/**\n * Resolves a color reference (object or string) to a plain color object\n * @param {Color | {space, coords, alpha} | string} color\n * @returns {{space, coords, alpha}}\n */\nexport default function getColor (color) {\n\tif (!color) {\n\t\tthrow new TypeError(\"Empty color reference\");\n\t}\n\n\tif (isString(color)) {\n\t\tcolor = parse(color);\n\t}\n\n\t// Object fixup\n\tlet space = color.space || color.spaceId;\n\n\tif (!(space instanceof ColorSpace)) {\n\t\t// Convert string id to color space object\n\t\tcolor.space = ColorSpace.get(space);\n\t}\n\n\tif (color.alpha === undefined) {\n\t\tcolor.alpha = 1;\n\t}\n\n\treturn color;\n}","import ColorSpace from \"./space.js\";\n\n/**\n * Get the coordinates of a color in another color space\n *\n * @param {string | ColorSpace} space\n * @returns {number[]}\n */\nexport default function getAll (color, space) {\n\tspace = ColorSpace.get(space);\n\treturn space.from(color);\n}","import ColorSpace from \"./space.js\";\nimport getAll from \"./getAll.js\";\n\nexport default function get (color, prop) {\n\tlet {space, index} = ColorSpace.resolveCoord(prop, color.space);\n\tlet coords = getAll(color, space);\n\treturn coords[index];\n}","import ColorSpace from \"./space.js\";\n\nexport default function setAll (color, space, coords) {\n\tspace = ColorSpace.get(space);\n\tcolor.coords = space.to(color.space, coords);\n\treturn color;\n}","import ColorSpace from \"./space.js\";\nimport getColor from \"./getColor.js\";\nimport get from \"./get.js\";\nimport getAll from \"./getAll.js\";\nimport setAll from \"./setAll.js\";\nimport {type} from \"./util.js\";\n\n// Set properties and return current instance\nexport default function set (color, prop, value) {\n\tcolor = getColor(color);\n\n\tif (arguments.length === 2 && type(arguments[1]) === \"object\") {\n\t\t// Argument is an object literal\n\t\tlet object = arguments[1];\n\t\tfor (let p in object) {\n\t\t\tset(color, p, object[p]);\n\t\t}\n\t}\n\telse {\n\t\tif (typeof value === \"function\") {\n\t\t\tvalue = value(get(color, prop));\n\t\t}\n\n\t\tlet {space, index} = ColorSpace.resolveCoord(prop, color.space);\n\t\tlet coords = getAll(color, space);\n\t\tcoords[index] = value;\n\t\tsetAll(color, space, coords);\n\t}\n\n\treturn color;\n}","import ColorSpace from \"../space.js\";\nimport adapt from \"../adapt.js\";\nimport XYZ_D65 from \"./xyz-d65.js\";\n\nexport default new ColorSpace({\n\tid: \"xyz-d50\",\n\tname: \"XYZ D50\",\n\twhite: \"D50\",\n\tbase: XYZ_D65,\n\tfromBase: coords => adapt(XYZ_D65.white, \"D50\", coords),\n\ttoBase: coords => adapt(\"D50\", XYZ_D65.white, coords),\n\tformats: {\n\t\tcolor: {}\n\t},\n});\n","import ColorSpace from \"../space.js\";\nimport {WHITES} from \"../adapt.js\";\nimport xyz_d50 from \"./xyz-d50.js\";\n\n// κ * ε = 2^3 = 8\nconst ε = 216/24389; // 6^3/29^3 == (24/116)^3\nconst ε3 = 24/116;\nconst κ = 24389/27; // 29^3/3^3\n\nlet white = WHITES.D50;\n\nexport default new ColorSpace({\n\tid: \"lab\",\n\tname: \"Lab\",\n\tcoords: {\n\t\tl: {\n\t\t\trefRange: [0, 100],\n\t\t\tname: \"L\"\n\t\t},\n\t\ta: {\n\t\t\trefRange: [-125, 125]\n\t\t},\n\t\tb: {\n\t\t\trefRange: [-125, 125]\n\t\t}\n\t},\n\n\t// Assuming XYZ is relative to D50, convert to CIE Lab\n\t// from CIE standard, which now defines these as a rational fraction\n\twhite,\n\n\tbase: xyz_d50,\n\t// Convert D50-adapted XYX to Lab\n\t// CIE 15.3:2004 section 8.2.1.1\n\tfromBase (XYZ) {\n\t\t// compute xyz, which is XYZ scaled relative to reference white\n\t\tlet xyz = XYZ.map((value, i) => value / white[i]);\n\n\t\t// now compute f\n\t\tlet f = xyz.map(value => value > ε ? Math.cbrt(value) : (κ * value + 16)/116);\n\n\t\treturn [\n\t\t\t(116 * f[1]) - 16, \t // L\n\t\t\t500 * (f[0] - f[1]), // a\n\t\t\t200 * (f[1] - f[2]) // b\n\t\t];\n\t},\n\t// Convert Lab to D50-adapted XYZ\n\t// Same result as CIE 15.3:2004 Appendix D although the derivation is different\n\t// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html\n\ttoBase (Lab) {\n\t\t// compute f, starting with the luminance-related term\n\t\tlet f = [];\n\t\tf[1] = (Lab[0] + 16)/116;\n\t\tf[0] = Lab[1]/500 + f[1];\n\t\tf[2] = f[1] - Lab[2]/200;\n\n\t\t// compute xyz\n\t\tlet xyz = [\n\t\t\tf[0] > ε3 ? Math.pow(f[0], 3) : (116*f[0]-16)/κ,\n\t\t\tLab[0] > 8 ? Math.pow((Lab[0]+16)/116, 3) : Lab[0]/κ,\n\t\t\tf[2] > ε3 ? Math.pow(f[2], 3) : (116*f[2]-16)/κ\n\t\t];\n\n\t\t// Compute XYZ by scaling xyz by reference white\n\t\treturn xyz.map((value, i) => value * white[i]);\n\t},\n\n\tformats: {\n\t\t\"lab\": {\n\t\t\tcoords: [\"<number> | <percentage>\", \"<number>\", \"<number>\"],\n\t\t}\n\t}\n});\n","export function constrain (angle) {\n\treturn ((angle % 360) + 360) % 360;\n}\n\nexport function adjust (arc, angles) {\n\tif (arc === \"raw\") {\n\t\treturn angles;\n\t}\n\n\tlet [a1, a2] = angles.map(constrain);\n\n\tlet angleDiff = a2 - a1;\n\n\tif (arc === \"increasing\") {\n\t\tif (angleDiff < 0) {\n\t\t\ta2 += 360;\n\t\t}\n\t}\n\telse if (arc === \"decreasing\") {\n\t\tif (angleDiff > 0) {\n\t\t\ta1 += 360;\n\t\t}\n\t}\n\telse if (arc === \"longer\") {\n\t\tif (-180 < angleDiff && angleDiff < 180) {\n\t\t\tif (angleDiff > 0) {\n\t\t\t\ta2 += 360;\n\t\t\t}\n\t\t\telse {\n\t\t\t\ta1 += 360;\n\t\t\t}\n\t\t}\n\t}\n\telse if (arc === \"shorter\") {\n\t\tif (angleDiff > 180) {\n\t\t\ta1 += 360;\n\t\t}\n\t\telse if (angleDiff < -180) {\n\t\t\ta2 += 360;\n\t\t}\n\t}\n\n\treturn [a1, a2];\n}\n","import ColorSpace from \"../space.js\";\nimport Lab from \"./lab.js\";\nimport {constrain as constrainAngle} from \"../angles.js\";\n\nexport default new ColorSpace({\n\tid: \"lch\",\n\tname: \"LCH\",\n\tcoords: {\n\t\tl: {\n\t\t\trefRange: [0, 100],\n\t\t\tname: \"Lightness\"\n\t\t},\n\t\tc: {\n\t\t\trefRange: [0, 150],\n\t\t\tname: \"Chroma\"\n\t\t},\n\t\th: {\n\t\t\trefRange: [0, 360],\n\t\t\ttype: \"angle\",\n\t\t\tname: \"Hue\"\n\t\t}\n\t},\n\n\tbase: Lab,\n\tfromBase (Lab) {\n\t\t// Convert to polar form\n\t\tlet [L, a, b] = Lab;\n\t\tlet hue;\n\t\tconst ε = 0.02;\n\n\t\tif (Math.abs(a) < ε && Math.abs(b) < ε) {\n\t\t\thue = NaN;\n\t\t}\n\t\telse {\n\t\t\thue = Math.atan2(b, a) * 180 / Math.PI;\n\t\t}\n\n\t\treturn [\n\t\t\tL, // L is still L\n\t\t\tMath.sqrt(a ** 2 + b ** 2), // Chroma\n\t\t\tconstrainAngle(hue) // Hue, in degrees [0 to 360)\n\t\t];\n\t},\n\ttoBase (LCH) {\n\t\t// Convert from polar form\n\t\tlet [Lightness, Chroma, Hue] = LCH;\n\t\t// Clamp any negative Chroma\n\t\tif (Chroma < 0) {\n\t\t\tChroma = 0;\n\t\t};\n\t\t// Deal with NaN Hue\n\t\tif (isNaN(Hue)) {\n\t\t\tHue = 0;\n\t\t}\n\t\treturn [\n\t\t\tLightness, // L is still L\n\t\t\tChroma * Math.cos(Hue * Math.PI / 180), // a\n\t\t\tChroma * Math.sin(Hue * Math.PI / 180) // b\n\t\t];\n\t},\n\n\tformats: {\n\t\t\"lch\": {\n\t\t\tcoords: [\"<number> | <percentage>\", \"<number>\", \"<number> | <angle>\"],\n\t\t}\n\t}\n});\n","import lab from \"../spaces/lab.js\";\nimport lch from \"../spaces/lch.js\";\n\n// deltaE2000 is a statistically significant improvement\n// and is recommended by the CIE and Idealliance\n// especially for color differences less than 10 deltaE76\n// but is wicked complicated\n// and many implementations have small errors!\n// DeltaE2000 is also discontinuous; in case this\n// matters to you, use deltaECMC instead.\n\nconst Gfactor = 25 ** 7;\nconst π = Math.PI;\nconst r2d = 180 / π;\nconst d2r = π / 180;\n\nexport default function (color, sample, {kL = 1, kC = 1, kH = 1} = {}) {\n\t// Given this color as the reference\n\t// and the function parameter as the sample,\n\t// calculate deltaE 2000.\n\n\t// This implementation assumes the parametric\n\t// weighting factors kL, kC and kH\n\t// for the influence of viewing conditions\n\t// are all 1, as sadly seems typical.\n\t// kL should be increased for lightness texture or noise\n\t// and kC increased for chroma noise\n\n\tlet [L1, a1, b1] = lab.from(color);\n\tlet C1 = lch.from(lab, [L1, a1, b1])[1];\n\tlet [L2, a2, b2] = lab.from(sample);\n\tlet C2 = lch.from(lab, [L2, a2, b2])[1];\n\n\t// Check for negative Chroma,\n\t// which might happen through\n\t// direct user input of LCH values\n\n\tif (C1 < 0) {\n\t\tC1 = 0;\n\t}\n\tif (C2 < 0) {\n\t\tC2 = 0;\n\t}\n\n\tlet Cbar = (C1 + C2)/2; // mean Chroma\n\n\t// calculate a-axis asymmetry factor from mean Chroma\n\t// this turns JND ellipses for near-neutral colors back into circles\n\tlet C7 = Cbar ** 7;\n\n\tlet G = 0.5 * (1 - Math.sqrt(C7/(C7 + Gfactor)));\n\n\t// scale a axes by asymmetry factor\n\t// this by the way is why there is no Lab2000 colorspace\n\tlet adash1 = (1 + G) * a1;\n\tlet adash2 = (1 + G) * a2;\n\n\t// calculate new Chroma from scaled a and original b axes\n\tlet Cdash1 = Math.sqrt(adash1 ** 2 + b1 ** 2);\n\tlet Cdash2 = Math.sqrt(adash2 ** 2 + b2 ** 2);\n\n\t// calculate new hues, with zero hue for true neutrals\n\t// and in degrees, not radians\n\n\tlet h1 = (adash1 === 0 && b1 === 0)? 0: Math.atan2(b1, adash1);\n\tlet h2 = (adash2 === 0 && b2 === 0)? 0: Math.atan2(b2, adash2);\n\n\tif (h1 < 0) {\n\t\th1 += 2 * π;\n\t}\n\tif (h2 < 0) {\n\t\th2 += 2 * π;\n\t}\n\n\th1 *= r2d;\n\th2 *= r2d;\n\n\t// Lightness and Chroma differences; sign matters\n\tlet ΔL = L2 - L1;\n\tlet ΔC = Cdash2 - Cdash1;\n\n\t// Hue difference, getting the sign correct\n\tlet hdiff = h2 - h1;\n\tlet hsum = h1 + h2;\n\tlet habs = Math.abs(hdiff);\n\tlet Δh;\n\n\tif (Cdash1 * Cdash2 === 0) {\n\t\tΔh = 0;\n\t}\n\telse if (habs <= 180) {\n\t\tΔh = hdiff;\n\t}\n\telse if (hdiff > 180) {\n\t\tΔh = hdiff - 360;\n\t}\n\telse if (hdiff < -180) {\n\t\tΔh = hdiff + 360;\n\t}\n\telse {\n\t\tconsole.log(\"the unthinkable has happened\");\n\t}\n\n\t// weighted Hue difference, more for larger Chroma\n\tlet ΔH = 2 * Math.sqrt(Cdash2 * Cdash1) * Math.sin(Δh * d2r / 2);\n\n\t// calculate mean Lightness and Chroma\n\tlet Ldash = (L1 + L2)/2;\n\tlet Cdash = (Cdash1 + Cdash2)/2;\n\tlet Cdash7 = Math.pow(Cdash, 7);\n\n\t// Compensate for non-linearity in the blue region of Lab.\n\t// Four possibilities for hue weighting factor,\n\t// depending on the angles, to get the correct sign\n\tlet hdash;\n\tif (Cdash1 * Cdash2 === 0) {\n\t\thdash = hsum; // which should be zero\n\t}\n\telse if (habs <= 180) {\n\t\thdash = hsum / 2;\n\t}\n\telse if (hsum < 360) {\n\t\thdash = (hsum + 360) / 2;\n\t}\n\telse {\n\t\thdash = (hsum - 360) / 2;\n\t}\n\n\t// positional corrections to the lack of uniformity of CIELAB\n\t// These are all trying to make JND ellipsoids more like spheres\n\n\t// SL Lightness crispening factor\n\t// a background with L=50 is assumed\n\tlet lsq = (Ldash - 50) ** 2;\n\tlet SL = 1 + ((0.015 * lsq) / Math.sqrt(20 + lsq));\n\n\t// SC Chroma factor, similar to those in CMC and deltaE 94 formulae\n\tlet SC = 1 + 0.045 * Cdash;\n\n\t// Cross term T for blue non-linearity\n\tlet T = 1;\n\tT -= (0.17 * Math.cos(( hdash - 30) * d2r));\n\tT += (0.24 * Math.cos( 2 * hdash * d2r));\n\tT += (0.32 * Math.cos(((3 * hdash) + 6) * d2r));\n\tT -= (0.20 * Math.cos(((4 * hdash) - 63) * d2r));\n\n\t// SH Hue factor depends on Chroma,\n\t// as well as adjusted hue angle like deltaE94.\n\tlet SH = 1 + 0.015 * Cdash * T;\n\n\t// RT Hue rotation term compensates for rotation of JND ellipses\n\t// and Munsell constant hue lines\n\t// in the medium-high Chroma blue region\n\t// (Hue 225 to 315)\n\tlet Δθ = 30 * Math.exp(-1 * (((hdash - 275)/25) ** 2));\n\tlet RC = 2 * Math.sqrt(Cdash7/(Cdash7 + Gfactor));\n\tlet RT = -1 * Math.sin(2 * Δθ * d2r) * RC;\n\n\t// Finally calculate the deltaE, term by term as root sume of squares\n\tlet dE = (ΔL / (kL * SL)) ** 2;\n\tdE += (ΔC / (kC * SC)) ** 2;\n\tdE += (ΔH / (kH * SH)) ** 2;\n\tdE += RT * (ΔC / (kC * SC)) * (ΔH / (kH * SH));\n\treturn Math.sqrt(dE);\n\t// Yay!!!\n};","import ColorSpace from \"./space.js\";\nimport getColor from \"./getColor.js\";\n\nconst ε = .000075;\n\n/**\n * Check if a color is in gamut of either its own or another color space\n * @return {Boolean} Is the color in gamut?\n */\nexport default function inGamut (color, space = color.space, {epsilon = ε} = {}) {\n\tcolor = getColor(color);\n\tspace = ColorSpace.get(space);\n\tlet coords = color.coords;\n\n\tif (space !== color.space) {\n\t\tcoords = space.from(color);\n\t}\n\n\treturn space.inGamut(coords, {epsilon});\n}","export default function clone(color) {\n\treturn {\n\t\tspace: color.space,\n\t\tcoords: color.coords.slice(),\n\t\talpha: color.alpha\n\t};\n}","import * as util from \"./util.js\";\nimport ColorSpace from \"./space.js\";\nimport defaults from \"./defaults.js\";\nimport deltaE2000 from \"./deltaE/deltaE2000.js\";\nimport inGamut from \"./inGamut.js\";\nimport to from \"./to.js\";\nimport get from \"./get.js\";\nimport set from \"./set.js\";\nimport clone from \"./clone.js\";\n\n/**\n * Force coordinates to be in gamut of a certain color space.\n * Mutates the color it is passed.\n * @param {Object} options\n * @param {string} options.method - How to force into gamut.\n * If \"clip\", coordinates are just clipped to their reference range.\n * If in the form [colorSpaceId].[coordName], that coordinate is reduced\n * until the color is in gamut. Please note that this may produce nonsensical\n * results for certain coordinates (e.g. hue) or infinite loops if reducing the coordinate never brings the color in gamut.\n * @param {ColorSpace|string} options.space - The space whose gamut we want to map to\n */\nexport default function toGamut (color, {method = defaults.gamut_mapping, space = color.space} = {}) {\n\tif (util.isString(arguments[1])) {\n\t\tspace = arguments[1];\n\t}\n\n\tspace = ColorSpace.get(space);\n\n\tif (inGamut(color, space, {epsilon: 0})) {\n\t\treturn color;\n\t}\n\n\t// 3 spaces:\n\t// color.space: current color space\n\t// space: space whose gamut we are mapping to\n\t// mapSpace: space with the coord we're reducing\n\tlet spaceColor = to(color, space);\n\n\tif (method !== \"clip\" && !inGamut(color, space)) {\n\t\tlet clipped = toGamut(clone(spaceColor), {method: \"clip\", space});\n\t\tif (deltaE2000(color, clipped) > 2) {\n\t\t\t// Reduce a coordinate of a certain color space until the color is in gamut\n\t\t\tlet coordMeta = ColorSpace.resolveCoord(method);\n\t\t\tlet mapSpace = coordMeta.space;\n\t\t\tlet coordId = coordMeta.id;\n\n\t\t\tlet mappedColor = to(spaceColor, mapSpace);\n\t\t\tlet bounds = coordMeta.range || coordMeta.refRange;\n\t\t\tlet min = bounds[0];\n\t\t\tlet ε = .01; // for deltaE\n\t\t\tlet low = min;\n\t\t\tlet high = get(mappedColor, coordId);\n\n\t\t\twhile (high - low > ε) {\n\t\t\t\tlet clipped = clone(mappedColor);\n\t\t\t\tclipped = toGamut(clipped, {space, method: \"clip\"});\n\t\t\t\tlet deltaE = deltaE2000(mappedColor, clipped);\n\n\t\t\t\tif (deltaE - 2 < ε) {\n\t\t\t\t\tlow = get(mappedColor, coordId);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\thigh = get(mappedColor, coordId);\n\t\t\t\t}\n\n\t\t\t\tset(mappedColor, coordId, (low + high) / 2);\n\t\t\t}\n\n\t\t\tspaceColor = to(mappedColor, space);\n\t\t}\n\t\telse {\n\t\t\tspaceColor = clipped;\n\t\t}\n\t}\n\n\tif (method === \"clip\" // Dumb coord clipping\n\t\t// finish off smarter gamut mapping with clip to get rid of ε, see #17\n\t\t|| !inGamut(spaceColor, space, {epsilon: 0})\n\t) {\n\t\tlet bounds = Object.values(space.coords).map(c => c.range || []);\n\n\t\tspaceColor.coords = spaceColor.coords.map((c, i) => {\n\t\t\tlet [min, max] = bounds[i];\n\n\t\t\tif (min !== undefined) {\n\t\t\t\tc = Math.max(min, c);\n\t\t\t}\n\n\t\t\tif (max !== undefined) {\n\t\t\t\tc = Math.min(c, max);\n\t\t\t}\n\n\t\t\treturn c;\n\t\t});\n\t}\n\n\tif (space !== color.space) {\n\t\tspaceColor = to(spaceColor, color.space);\n\t}\n\n\tcolor.coords = spaceColor.coords;\n\treturn color;\n}\n\ntoGamut.returns = \"color\";","import getColor from \"./getColor.js\";\nimport ColorSpace from \"./space.js\";\nimport toGamut from \"./toGamut.js\";\n\n/**\n * Convert to color space and return a new color\n * @param {Object|string} space - Color space object or id\n * @param {Object} options\n * @param {boolean} options.inGamut - Whether to force resulting color in gamut\n * @returns {Color}\n */\nexport default function to (color, space, {inGamut} = {}) {\n\tcolor = getColor(color);\n\tspace = ColorSpace.get(space);\n\n\tlet coords = space.from(color);\n\tlet ret = {space, coords, alpha: color.alpha};\n\n\tif (inGamut) {\n\t\tret = toGamut(ret);\n\t}\n\n\treturn ret;\n}\n\nto.returns = \"color\";","import * as util from \"./util.js\";\nimport ColorSpace from \"./space.js\";\nimport defaults from \"./defaults.js\";\nimport getColor from \"./getColor.js\";\nimport to from \"./to.js\";\nimport checkInGamut from \"./inGamut.js\";\nimport toGamut from \"./toGamut.js\";\nimport clone from \"./clone.js\";\n\n/**\n * Generic toString() method, outputs a color(spaceId ...coords) function, a functional syntax, or custom formats defined by the color space\n * @param {Object} options\n * @param {number} options.precision - Significant digits\n * @param {boolean} options.inGamut - Adjust coordinates to fit in gamut first? [default: false]\n */\nexport default function serialize (color, {\n\tprecision = defaults.precision,\n\tformat = \"default\",\n\tinGamut = true,\n\t...customOptions\n} = {}) {\n\tlet ret;\n\n\tcolor = getColor(color);\n\n\tlet formatId = format;\n\tformat = color.space.getFormat(format)\n\t\t ?? color.space.getFormat(\"default\")\n\t\t ?? ColorSpace.DEFAULT_FORMAT;\n\n\tinGamut ||= format.toGamut;\n\n\tlet coords = color.coords;\n\n\t// Convert NaN to zeros to have a chance at a valid CSS color\n\t// Also convert -0 to 0\n\t// This also clones it so we can manipulate it\n\tcoords = coords.map(c => c? c : 0);\n\n\tif (inGamut && !checkInGamut(color)) {\n\t\tcoords = toGamut(clone(color), inGamut === true? undefined : inGamut).coords;\n\t}\n\n\tif (format.type === \"custom\") {\n\t\tcustomOptions.precision = precision;\n\n\t\tif (format.serialize) {\n\t\t\tret = format.serialize(coords, color.alpha, customOptions);\n\t\t}\n\t\telse {\n\t\t\tthrow new TypeError(`format ${formatId} can only be used to parse colors, not for serialization`);\n\t\t}\n\t}\n\telse {\n\t\t// Functional syntax\n\t\tlet name = format.name || \"color\";\n\n\t\tif (format.serializeCoords) {\n\t\t\tcoords = format.serializeCoords(coords, precision);\n\t\t}\n\t\telse {\n\t\t\tif (precision !== null) {\n\t\t\t\tcoords = coords.map(c => util.toPrecision(c, precision));\n\t\t\t}\n\t\t}\n\n\t\tlet args = [...coords];\n\n\t\tif (name === \"color\") {\n\t\t\t// If output is a color() function, add colorspace id as first argument\n\t\t\tlet cssId = format.id || format.ids?.[0] || color.space.id;\n\t\t\targs.unshift(cssId);\n\t\t}\n\n\t\tlet alpha = color.alpha;\n\t\tif (precision !== null) {\n\t\t\talpha = util.toPrecision(alpha, precision);\n\t\t}\n\n\t\tlet strAlpha = color.alpha < 1? ` ${format.commas? \",\" : \"/\"} ${alpha}` : \"\";\n\t\tret = `${name}(${args.join(format.commas? \", \" : \" \")}${strAlpha})`;\n\t}\n\n\treturn ret;\n}","import RGBColorSpace from \"../rgbspace.js\";\n\n// convert an array of linear-light rec2020 values to CIE XYZ\n// using D65 (no chromatic adaptation)\n// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html\n// 0 is actually calculated as 4.994106574466076e-17\nconst toXYZ_M = [\n\t[ 0.6369580483012914, 0.14461690358620832, 0.1688809751641721 ],\n\t[ 0.2627002120112671, 0.6779980715188708, 0.05930171646986196 ],\n\t[ 0.000000000000000, 0.028072693049087428, 1.060985057710791 ]\n];\n\n// from ITU-R BT.2124-0 Annex 2 p.3\nconst fromXYZ_M = [\n\t[ 1.716651187971268, -0.355670783776392, -0.253366281373660 ],\n\t[ -0.666684351832489, 1.616481236634939, 0.0157685458139111 ],\n\t[ 0.017639857445311, -0.042770613257809, 0.942103121235474 ]\n]\n\nexport default new RGBColorSpace({\n\tid: \"rec2020-linear\",\n\tname: \"Linear REC.2020\",\n\twhite: \"D65\",\n\ttoXYZ_M,\n\tfromXYZ_M\n});\n","import RGBColorSpace from \"../rgbspace.js\";\nimport REC2020Linear from \"./rec2020-linear.js\";\n// import sRGB from \"./srgb.js\";\n\nconst α = 1.09929682680944;\nconst β = 0.018053968510807;\n\nexport default new RGBColorSpace({\n\tid: \"rec2020\",\n\tname: \"REC.2020\",\n\tbase: REC2020Linear,\n\t// Non-linear transfer function from Rec. ITU-R BT.2020-2 table 4\n\ttoBase (RGB) {\n\t\treturn RGB.map(function (val) {\n\t\t\tif (val < β * 4.5 ) {\n\t\t\t\treturn val / 4.5;\n\t\t\t}\n\n\t\t\treturn Math.pow((val + α -1 ) / α, 1/0.45);\n\t\t});\n\t},\n\tfromBase (RGB) {\n\t\treturn RGB.map(function (val) {\n\t\t\tif (val >= β ) {\n\t\t\t\treturn α * Math.pow(val, 0.45) - (α - 1);\n\t\t\t}\n\n\t\t\treturn 4.5 * val;\n\t\t});\n\t},\n\tformats: {\n\t\tcolor: {},\n\t}\n});\n","import RGBColorSpace from \"../rgbspace.js\";\n\nconst toXYZ_M = [\n\t[0.4865709486482162, 0.26566769316909306, 0.1982172852343625],\n\t[0.2289745640697488, 0.6917385218365064, 0.079286914093745],\n\t[0.0000000000000000, 0.04511338185890264, 1.043944368900976]\n];\n\nconst fromXYZ_M = [\n\t[ 2.493496911941425, -0.9313836179191239, -0.40271078445071684],\n\t[-0.8294889695615747, 1.7626640603183463, 0.023624685841943577],\n\t[ 0.03584583024378447, -0.07617238926804182, 0.9568845240076872]\n]\n\nexport default new RGBColorSpace({\n\tid: \"p3-linear\",\n\tname: \"Linear P3\",\n\twhite: \"D65\",\n\ttoXYZ_M,\n\tfromXYZ_M\n});\n","import RGBColorSpace from \"../rgbspace.js\";\n\n// This is the linear-light version of sRGB\n// as used for example in SVG filters\n// or in Canvas\n\n// This matrix was calculated directly from the RGB and white chromaticities\n// when rounded to 8 decimal places, it agrees completely with the official matrix\n// see https://github.com/w3c/csswg-drafts/issues/5922\nconst toXYZ_M = [\n\t[ 0.41239079926595934, 0.357584339383878, 0.1804807884018343 ],\n\t[ 0.21263900587151027, 0.715168678767756, 0.07219231536073371 ],\n\t[ 0.01933081871559182, 0.11919477979462598, 0.9505321522496607 ]\n];\n\n// This matrix is the inverse of the above;\n// again it agrees with the official definition when rounded to 8 decimal places\nconst fromXYZ_M = [\n\t[ 3.2409699419045226, -1.537383177570094, -0.4986107602930034 ],\n\t[ -0.9692436362808796, 1.8759675015077202, 0.04155505740717559 ],\n\t[ 0.05563007969699366, -0.20397695888897652, 1.0569715142428786 ]\n];\n\nexport default new RGBColorSpace({\n\tid: \"srgb-linear\",\n\tname: \"Linear sRGB\",\n\twhite: \"D65\",\n\ttoXYZ_M,\n\tfromXYZ_M,\n\tformats: {\n\t\tcolor: {}\n\t},\n});\n","/* List of CSS color keywords\n * Note that this does not include currentColor, transparent,\n * or system colors\n */\n\n// To produce: Visit https://www.w3.org/TR/css-color-4/#named-colors\n// and run in the console:\n// copy($$(\"tr\", $(\".named-color-table tbody\")).map(tr => `\"${tr.cells[2].textContent.trim()}\": [${tr.cells[4].textContent.trim().split(/\\s+/).map(c => c === \"0\"? \"0\" : c === \"255\"? \"1\" : c + \" / 255\").join(\", \")}]`).join(\",\\n\"))\nexport default {\n\t\"aliceblue\": [240 / 255, 248 / 255, 1],\n\t\"antiquewhite\": [250 / 255, 235 / 255, 215 / 255],\n\t\"aqua\": [0, 1, 1],\n\t\"aquamarine\": [127 / 255, 1, 212 / 255],\n\t\"azure\": [240 / 255, 1, 1],\n\t\"beige\": [245 / 255, 245 / 255, 220 / 255],\n\t\"bisque\": [1, 228 / 255, 196 / 255],\n\t\"black\": [0, 0, 0],\n\t\"blanchedalmond\": [1, 235 / 255, 205 / 255],\n\t\"blue\": [0, 0, 1],\n\t\"blueviolet\": [138 / 255, 43 / 255, 226 / 255],\n\t\"brown\": [165 / 255, 42 / 255, 42 / 255],\n\t\"burlywood\": [222 / 255, 184 / 255, 135 / 255],\n\t\"cadetblue\": [95 / 255, 158 / 255, 160 / 255],\n\t\"chartreuse\": [127 / 255, 1, 0],\n\t\"chocolate\": [210 / 255, 105 / 255, 30 / 255],\n\t\"coral\": [1, 127 / 255, 80 / 255],\n\t\"cornflowerblue\": [100 / 255, 149 / 255, 237 / 255],\n\t\"cornsilk\": [1, 248 / 255, 220 / 255],\n\t\"crimson\": [220 / 255, 20 / 255, 60 / 255],\n\t\"cyan\": [0, 1, 1],\n\t\"darkblue\": [0, 0, 139 / 255],\n\t\"darkcyan\": [0, 139 / 255, 139 / 255],\n\t\"darkgoldenrod\": [184 / 255, 134 / 255, 11 / 255],\n\t\"darkgray\": [169 / 255, 169 / 255, 169 / 255],\n\t\"darkgreen\": [0, 100 / 255, 0],\n\t\"darkgrey\": [169 / 255, 169 / 255, 169 / 255],\n\t\"darkkhaki\": [189 / 255, 183 / 255, 107 / 255],\n\t\"darkmagenta\": [139 / 255, 0, 139 / 255],\n\t\"darkoliv