UNPKG

colorjs.io

Version:

Let’s get serious about color

1 lines 430 kB
{"version":3,"file":"color.cjs","sources":["../src/multiply-matrices.js","../src/util.js","../src/hooks.js","../src/defaults.js","../src/Type.js","../src/Format.js","../src/adapt.js","../src/parse.js","../src/getColor.js","../src/ColorSpace.js","../src/spaces/xyz-d65.js","../src/RGBColorSpace.js","../src/tryColor.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/spaces/oklab.js","../src/deltaE/deltaEOK.js","../src/inGamut.js","../src/clone.js","../src/distance.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/deltaE/deltaEOK2.js","../src/spaces/cam16.js","../src/spaces/hct.js","../src/deltaE/deltaEHCT.js","../src/deltaE/index.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/deltas.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/index.js","../src/contrast.js","../src/chromaticity.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/rec2020-oetf.js","../src/spaces/oklch.js","../src/spaces/okhsl.js","../src/spaces/oklrab.js","../src/spaces/oklrch.js","../src/spaces/okhsv.js","../src/spaces/luv.js","../src/spaces/lchuv.js","../src/spaces/hsluv.js","../src/spaces/hpluv.js","../src/spaces/rec2100-linear.js","../src/spaces/rec2100-pq.js","../src/spaces/rec2100-hlg.js","../src/CATs.js","../src/spaces/acescg.js","../src/spaces/acescc.js","../src/spaces/index-fn.js","../src/color.js","../src/spaces/index.js","../src/space-accessors.js","../src/index.js"],"sourcesContent":["/** @import { Matrix3x3, Vector3 } from \"./types.js\" */\n\n/**\n * A is m x n. B is n x p. product is m x p.\n *\n * Array arguments are treated like vectors:\n * - A becomes 1 x n\n * - B becomes n x 1\n *\n * Returns Matrix m x p or equivalent array or number\n *\n * @overload\n * @param {number[]} A Vector 1 x n\n * @param {number[]} B Vector n x 1\n * @returns {number} Scalar number\n *\n * @overload\n * @param {number[][]} A Matrix m x n\n * @param {number[]} B Vector n x 1\n * @returns {number[]} Array with length m\n *\n * @overload\n * @param {number[]} A Vector 1 x n\n * @param {number[][]} B Matrix n x p\n * @returns {number[]} Array with length p\n *\n * @overload\n * @param {number[][]} A Matrix m x n\n * @param {number[][]} B Matrix n x p\n * @returns {number[][]} Matrix m x p\n *\n * @param {number[] | number[][]} A Matrix m x n or a vector\n * @param {number[] | number[][]} B Matrix n x p or a vector\n * @returns {number | number[] | number[][]} Matrix m x p or equivalent array or number\n */\nexport default function multiplyMatrices (A, B) {\n\tlet m = A.length;\n\t/** @type {number[][]} */\n\tlet AM;\n\t/** @type {number[][]} */\n\tlet BM;\n\tlet aVec = false;\n\tlet bVec = false;\n\n\tif (!Array.isArray(A[0])) {\n\t\t// A is vector, convert to [[a, b, c, ...]]\n\t\tAM = [/** @type {number[]} */ (A)];\n\t\tm = AM.length;\n\t\taVec = true;\n\t}\n\telse {\n\t\tAM = /** @type {number[][]} */ (A);\n\t}\n\n\tif (!Array.isArray(B[0])) {\n\t\t// B is vector, convert to [[a], [b], [c], ...]]\n\t\tBM = B.length > 0 ? B.map(x => [x]) : [[]]; // Avoid mapping empty array\n\t\tbVec = true;\n\t}\n\telse {\n\t\tBM = /** @type {number[][]} */ (B);\n\t}\n\n\tlet p = BM[0].length;\n\tlet BM_cols = BM[0].map((_, i) => BM.map(x => x[i])); // transpose B\n\t/** @type {number[] | number[][]} */\n\tlet product = AM.map(row =>\n\t\tBM_cols.map(col => {\n\t\t\tlet ret = 0;\n\n\t\t\tif (!Array.isArray(row)) {\n\t\t\t\tfor (let c of col) {\n\t\t\t\t\tret += row * c;\n\t\t\t\t}\n\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\tfor (let i = 0; i < row.length; i++) {\n\t\t\t\tret += row[i] * (col[i] || 0);\n\t\t\t}\n\n\t\t\treturn ret;\n\t\t}));\n\n\tif (m === 1 && aVec) {\n\t\tproduct = product[0]; // Avoid [[a, b, c, ...]]\n\t}\n\tif (p === 1 && bVec) {\n\t\tif (m === 1 && aVec) {\n\t\t\treturn product[0]; // Avoid [[a]], return a number\n\t\t}\n\t\telse {\n\t\t\treturn product.map(x => x[0]); // Avoid [[a], [b], [c], ...]]\n\t\t}\n\t}\n\n\treturn product;\n}\n\n// dot3 and transform functions adapted from https://github.com/texel-org/color/blob/9793c7d4d02b51f068e0f3fd37131129a4270396/src/core.js\n//\n// The MIT License (MIT)\n// Copyright (c) 2024 Matt DesLauriers\n\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE\n// OR OTHER DEALINGS IN THE SOFTWARE.\n\n/**\n * Returns the dot product of two vectors each with a length of 3.\n *\n * @param {Vector3} a\n * @param {Vector3} b\n * @returns {number}\n */\nfunction dot3 (a, b) {\n\treturn a[0] * b[0] + a[1] * b[1] + a[2] * b[2];\n}\n\n/**\n * Transforms a vector of length 3 by a 3x3 matrix. Specify the same input and output\n * vector to transform in place.\n *\n * @param {Vector3} input\n * @param {Matrix3x3} matrix\n * @param {Vector3} [out]\n * @returns {Vector3}\n */\nexport function multiply_v3_m3x3 (input, matrix, out = [0, 0, 0]) {\n\tconst x = dot3(input, matrix[0]);\n\tconst y = dot3(input, matrix[1]);\n\tconst z = dot3(input, matrix[2]);\n\tout[0] = x;\n\tout[1] = y;\n\tout[2] = z;\n\treturn out;\n}\n","/**\n * Various utility functions\n */\n\nexport { default as multiplyMatrices, multiply_v3_m3x3 } from \"./multiply-matrices.js\";\n\n/**\n * Check if a value is a string (including a String object)\n * @param {any} str - Value to check\n * @returns {str is string}\n */\nexport function isString (str) {\n\treturn type(str) === \"string\";\n}\n\n/**\n * Determine the internal JavaScript [[Class]] of an object.\n * @param {any} 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 * @param {number} n\n * @param {{ precision?: number | undefined, unit?: string | undefined }} options\n * @returns {string}\n */\nexport function serializeNumber (n, { precision = 16, unit }) {\n\tif (isNone(n)) {\n\t\treturn \"none\";\n\t}\n\n\tn = +toPrecision(n, precision);\n\n\treturn n + (unit ?? \"\");\n}\n\n/**\n * Check if a value corresponds to a none argument\n * @param {any} n - Value to check\n * @returns {n is null}\n */\nexport function isNone (n) {\n\treturn n === null;\n}\n\n/**\n * Replace none values with 0\n * @param {number | null} n\n * @returns {number}\n */\nexport function skipNone (n) {\n\treturn isNone(n) ? 0 : n;\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\tif (n === 0) {\n\t\treturn 0;\n\t}\n\tlet integer = ~~n;\n\tlet digits = 0;\n\tif (integer && precision) {\n\t\tdigits = ~~Math.log10(Math.abs(integer)) + 1;\n\t}\n\tconst multiplier = 10.0 ** (precision - digits);\n\treturn Math.floor(n * multiplier + 0.5) / multiplier;\n}\n\n/**\n * @param {number} start\n * @param {number} end\n * @param {number} p\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\n/**\n * @param {number} start\n * @param {number} end\n * @param {number} value\n */\nexport function interpolateInv (start, end, value) {\n\treturn (value - start) / (end - start);\n}\n\n/**\n * @param {[number, number]} from\n * @param {[number, number]} to\n * @param {number} value\n */\nexport function mapRange (from, to, value) {\n\tif (\n\t\t!from ||\n\t\t!to ||\n\t\tfrom === to ||\n\t\t(from[0] === to[0] && from[1] === to[1]) ||\n\t\tisNaN(value) ||\n\t\tvalue === null\n\t) {\n\t\t// Ranges missing or the same\n\t\treturn value;\n\t}\n\n\treturn interpolate(to[0], to[1], interpolateInv(from[0], from[1], value));\n}\n\n/**\n * Clamp value between the minimum and maximum\n * @param {number} min minimum value to return\n * @param {number} val the value to return if it is between min and max\n * @param {number} max maximum value to return\n */\nexport function clamp (min, val, max) {\n\treturn Math.max(Math.min(max, val), min);\n}\n\n/**\n * Copy sign of one value to another.\n * @param {number} to - Number to copy sign to\n * @param {number} from - Number to copy sign from\n */\nexport function copySign (to, from) {\n\treturn Math.sign(to) === Math.sign(from) ? to : -to;\n}\n\n/**\n * Perform pow on a signed number and copy sign to result\n * @param {number} base The base number\n * @param {number} exp The exponent\n */\nexport function spow (base, exp) {\n\treturn copySign(Math.abs(base) ** exp, base);\n}\n\n/**\n * Perform a divide, but return zero if the denominator is zero\n * @param {number} n The numerator\n * @param {number} d The denominator\n */\nexport function zdiv (n, d) {\n\treturn d === 0 ? 0 : n / d;\n}\n\n/**\n * Perform a bisect on a sorted list and locate the insertion point for\n * a value in arr to maintain sorted order.\n * @param {number[]} arr - array of sorted numbers\n * @param {number} value - value to find insertion point for\n * @param {number} lo - used to specify a the low end of a subset of the list\n * @param {number} hi - used to specify a the high end of a subset of the list\n */\nexport function bisectLeft (arr, value, lo = 0, hi = arr.length) {\n\twhile (lo < hi) {\n\t\tconst mid = (lo + hi) >> 1;\n\t\tif (arr[mid] < value) {\n\t\t\tlo = mid + 1;\n\t\t}\n\t\telse {\n\t\t\thi = mid;\n\t\t}\n\t}\n\treturn lo;\n}\n\n/**\n * Determines whether an argument is an instance of a constructor, including subclasses.\n * This is done by first just checking `instanceof`,\n * and then comparing the string names of the constructors if that fails.\n * @param {any} arg\n * @param {C} constructor\n * @template {new (...args: any) => any} C\n * @returns {arg is InstanceType<C>}\n */\nexport function isInstance (arg, constructor) {\n\tif (arg instanceof constructor) {\n\t\treturn true;\n\t}\n\n\tconst targetName = constructor.name;\n\n\twhile (arg) {\n\t\tconst proto = Object.getPrototypeOf(arg);\n\t\tconst constructorName = proto?.constructor?.name;\n\t\tif (constructorName === targetName) {\n\t\t\treturn true;\n\t\t}\n\t\tif (!constructorName || constructorName === \"Object\") {\n\t\t\treturn false;\n\t\t}\n\t\targ = proto;\n\t}\n\n\treturn false;\n}\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;\n","// Global defaults one may want to configure\nexport default {\n\tgamut_mapping: \"css\",\n\tprecision: 5,\n\tdeltaE: \"76\", // Default deltaE method\n\tverbose: globalThis?.process?.env?.NODE_ENV?.toLowerCase() !== \"test\",\n\twarn: function warn (msg) {\n\t\tif (this.verbose) {\n\t\t\tglobalThis?.console?.warn?.(msg);\n\t\t}\n\t},\n};\n","import { serializeNumber, mapRange, isInstance } from \"./util.js\";\n\nexport default class Type {\n\t// Class properties - declared here so that type inference works\n\ttype;\n\tcoordMeta;\n\tcoordRange;\n\t/** @type {[number, number]} */\n\trange;\n\n\t/**\n\t * @param {any} type\n\t * @param {import(\"./types.js\").CoordMeta} coordMeta\n\t */\n\tconstructor (type, coordMeta) {\n\t\tif (typeof type === \"object\") {\n\t\t\tthis.coordMeta = type;\n\t\t}\n\n\t\tif (coordMeta) {\n\t\t\tthis.coordMeta = coordMeta;\n\t\t\tthis.coordRange = coordMeta.range ?? coordMeta.refRange;\n\t\t}\n\n\t\tif (typeof type === \"string\") {\n\t\t\tlet params = type\n\t\t\t\t.trim()\n\t\t\t\t.match(/^(?<type><[a-z]+>)(\\[(?<min>-?[.\\d]+),\\s*(?<max>-?[.\\d]+)\\])?$/);\n\n\t\t\tif (!params) {\n\t\t\t\tthrow new TypeError(`Cannot parse ${type} as a type definition.`);\n\t\t\t}\n\n\t\t\tthis.type = params.groups.type;\n\t\t\tlet { min, max } = params.groups;\n\n\t\t\tif (min || max) {\n\t\t\t\tthis.range = [+min, +max];\n\t\t\t}\n\t\t}\n\t}\n\n\t/** @returns {[number, number]} */\n\tget computedRange () {\n\t\tif (this.range) {\n\t\t\treturn this.range;\n\t\t}\n\t\tif (this.type === \"<percentage>\") {\n\t\t\treturn this.percentageRange();\n\t\t}\n\t\telse if (this.type === \"<angle>\") {\n\t\t\treturn [0, 360];\n\t\t}\n\t\treturn null;\n\t}\n\n\tget unit () {\n\t\tif (this.type === \"<percentage>\") {\n\t\t\treturn \"%\";\n\t\t}\n\t\telse if (this.type === \"<angle>\") {\n\t\t\treturn \"deg\";\n\t\t}\n\n\t\treturn \"\";\n\t}\n\n\t/**\n\t * Map a number to the internal representation\n\t * @param {number} number\n\t */\n\tresolve (number) {\n\t\tif (this.type === \"<angle>\") {\n\t\t\treturn number;\n\t\t}\n\n\t\tlet fromRange = this.computedRange;\n\t\tlet toRange = this.coordRange;\n\n\t\tif (this.type === \"<percentage>\") {\n\t\t\ttoRange ??= this.percentageRange();\n\t\t}\n\n\t\treturn mapRange(fromRange, toRange, number);\n\t}\n\n\t/**\n\t * Serialize a number from the internal representation to a string\n\t * @param {number} number\n\t * @param {number} [precision]\n\t */\n\tserialize (number, precision) {\n\t\tlet toRange = this.type === \"<percentage>\" ? this.percentageRange(100) : this.computedRange;\n\n\t\tlet unit = this.unit;\n\n\t\tnumber = mapRange(this.coordRange, toRange, number);\n\t\treturn serializeNumber(number, { unit, precision });\n\t}\n\n\ttoString () {\n\t\tlet ret = this.type;\n\n\t\tif (this.range) {\n\t\t\tlet [min = \"\", max = \"\"] = this.range;\n\t\t\tret += `[${min},${max}]`;\n\t\t}\n\n\t\treturn ret;\n\t}\n\n\t/**\n\t * Returns a percentage range for values of this type\n\t * @param {number} scale\n\t * @returns {[number, number]}\n\t */\n\tpercentageRange (scale = 1) {\n\t\tlet range;\n\t\tif (\n\t\t\t(this.coordMeta && this.coordMeta.range) ||\n\t\t\t(this.coordRange && this.coordRange[0] >= 0)\n\t\t) {\n\t\t\trange = [0, 1];\n\t\t}\n\t\telse {\n\t\t\trange = [-1, 1];\n\t\t}\n\t\treturn [range[0] * scale, range[1] * scale];\n\t}\n\n\tstatic get (type, coordMeta) {\n\t\tif (isInstance(type, this)) {\n\t\t\treturn type;\n\t\t}\n\n\t\treturn new this(type, coordMeta);\n\t}\n}\n","import { isInstance, isNone } from \"./util.js\";\nimport Type from \"./Type.js\";\n\n/** @import { ColorSpace, Coords } from \"./types.js\" */\n\n// Type re-exports\n/** @typedef {import(\"./types.js\").Format} FormatInterface */\n\n/**\n * @internal\n * Used to index {@link FormatInterface Format} objects and store an instance.\n * Not meant for external use\n */\nexport const instance = Symbol(\"instance\");\n\n/**\n * Remove the first element of an array type\n * @template {any[]} T\n * @typedef {T extends [any, ...infer R] ? R : T[number][]} RemoveFirstElement\n */\n\n/**\n * @class Format\n * @implements {Omit<FormatInterface, \"coords\" | \"serializeCoords\">}\n * Class to hold a color serialization format\n */\nexport default class Format {\n\t// Class properties - declared here so that type inference works\n\ttype;\n\tname;\n\tspaceCoords;\n\t/** @type {Type[][]} */\n\tcoords;\n\t/** @type {string | undefined} */\n\tid;\n\t/** @type {boolean | undefined} */\n\talpha;\n\n\t/**\n\t * @param {FormatInterface} format\n\t * @param {ColorSpace} space\n\t */\n\tconstructor (format, space = format.space) {\n\t\tformat[instance] = this;\n\t\tthis.type = \"function\";\n\t\tthis.name = \"color\";\n\n\t\tObject.assign(this, format);\n\n\t\tthis.space = space;\n\n\t\tif (this.type === \"custom\") {\n\t\t\t// Nothing else to do here\n\t\t\treturn;\n\t\t}\n\n\t\tthis.spaceCoords = Object.values(space.coords);\n\n\t\tif (!this.coords) {\n\t\t\t// @ts-expect-error Strings are converted to the correct type later\n\t\t\tthis.coords = this.spaceCoords.map(coordMeta => {\n\t\t\t\tlet ret = [\"<number>\", \"<percentage>\"];\n\n\t\t\t\tif (coordMeta.type === \"angle\") {\n\t\t\t\t\tret.push(\"<angle>\");\n\t\t\t\t}\n\n\t\t\t\treturn ret;\n\t\t\t});\n\t\t}\n\n\t\tthis.coords = this.coords.map(\n\t\t\t/** @param {string | string[] | Type[]} types */ (types, i) => {\n\t\t\t\tlet coordMeta = this.spaceCoords[i];\n\n\t\t\t\tif (typeof types === \"string\") {\n\t\t\t\t\ttypes = types.trim().split(/\\s*\\|\\s*/);\n\t\t\t\t}\n\n\t\t\t\treturn types.map(type => Type.get(type, coordMeta));\n\t\t\t},\n\t\t);\n\t}\n\n\t/**\n\t * @param {Coords} coords\n\t * @param {number} precision\n\t * @param {Type[]} types\n\t */\n\tserializeCoords (coords, precision, types) {\n\t\ttypes = coords.map((_, i) =>\n\t\t\tType.get(types?.[i] ?? this.coords[i][0], this.spaceCoords[i]));\n\t\treturn coords.map((c, i) => types[i].serialize(c, precision));\n\t}\n\n\t/**\n\t * Validates the coordinates of a color against a format's coord grammar and\n\t * maps the coordinates to the range or refRange of the coordinates.\n\t * @param {Coords} coords\n\t * @param {[string, string, string]} types\n\t */\n\tcoerceCoords (coords, types) {\n\t\treturn Object.entries(this.space.coords).map(([id, coordMeta], i) => {\n\t\t\tlet arg = coords[i];\n\n\t\t\tif (isNone(arg) || isNaN(arg)) {\n\t\t\t\t// Nothing to do here\n\t\t\t\treturn arg;\n\t\t\t}\n\n\t\t\t// Find grammar alternative that matches the provided type\n\t\t\t// Non-strict equals is intentional because we are comparing w/ string objects\n\t\t\tlet providedType = types[i];\n\t\t\tlet type = this.coords[i].find(c => c.type == providedType);\n\n\t\t\t// Check that each coord conforms to its grammar\n\t\t\tif (!type) {\n\t\t\t\t// Type does not exist in the grammar, throw\n\t\t\t\tlet coordName = coordMeta.name || id;\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t`${providedType ?? /** @type {any} */ (arg)?.raw ?? arg} not allowed for ${coordName} in ${this.name}()`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\targ = type.resolve(arg);\n\n\t\t\tif (type.range) {\n\t\t\t\t// Adjust type to include range\n\t\t\t\ttypes[i] = type.toString();\n\t\t\t}\n\n\t\t\treturn arg;\n\t\t});\n\t}\n\n\t/**\n\t * @returns {boolean | Required<FormatInterface>[\"serialize\"]}\n\t */\n\tcanSerialize () {\n\t\treturn this.type === \"function\" || /** @type {any} */ (this).serialize;\n\t}\n\n\t/**\n\t * @param {string} str\n\t * @returns {(import(\"./types.js\").ColorConstructor) | undefined | null}\n\t */\n\tparse (str) {\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param {Format | FormatInterface} format\n\t * @param {RemoveFirstElement<ConstructorParameters<typeof Format>>} args\n\t * @returns {Format}\n\t */\n\tstatic get (format, ...args) {\n\t\tif (!format || isInstance(format, this)) {\n\t\t\treturn /** @type {Format} */ (format);\n\t\t}\n\n\t\tif (format[instance]) {\n\t\t\treturn format[instance];\n\t\t}\n\n\t\treturn new Format(format, ...args);\n\t}\n}\n","import hooks from \"./hooks.js\";\nimport { multiply_v3_m3x3 } from \"./util.js\";\n\n// Type re-exports\n/** @typedef {import(\"./types.js\").White} White */\n\n/** @type {Record<string, White>} */\n// prettier-ignore\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\n/**\n *\n * @param {string | White} name\n * @returns {White}\n */\nexport function getWhite (name) {\n\tif (Array.isArray(name)) {\n\t\treturn name;\n\t}\n\n\treturn WHITES[name];\n}\n\n/**\n * Adapt XYZ from white point W1 to W2\n * @param {White | string} W1\n * @param {White | string} W2\n * @param {[number, number, number]} XYZ\n * @param {{ method?: string | undefined }} options\n * @returns {[number, number, number]}\n */\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(\n\t\t\t`Missing white point to convert ${!W1 ? \"from\" : \"\"}${!W1 && !W2 ? \"/\" : \"\"}${!W2 ? \"to\" : \"\"}`,\n\t\t);\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\t// prettier-ignore\n\t\t\tenv.M = [\n\t\t\t\t[ 1.0479297925449969, 0.022946870601609652, -0.05019226628920524 ],\n\t\t\t\t[ 0.02962780877005599, 0.9904344267538799, -0.017073799063418826 ],\n\t\t\t\t[ -0.009243040646204504, 0.015055191490298152, 0.7518742814281371 ],\n\t\t\t];\n\t\t}\n\t\telse if (env.W1 === WHITES.D50 && env.W2 === WHITES.D65) {\n\t\t\t// prettier-ignore\n\t\t\tenv.M = [\n\t\t\t\t[ 0.955473421488075, -0.02309845494876471, 0.06325924320057072 ],\n\t\t\t\t[ -0.0283697093338637, 1.0099953980813041, 0.021041441191917323 ],\n\t\t\t\t[ 0.012314014864481998, -0.020507649298898964, 1.330365926242124 ],\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 multiply_v3_m3x3(env.XYZ, env.M);\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 { isNone, clamp } from \"./util.js\";\nimport hooks from \"./hooks.js\";\nimport ColorSpace from \"./ColorSpace.js\";\nimport defaults from \"./defaults.js\";\n\n/** @import { ColorConstructor } from \"./types.js\" */\n\n// Type re-exports\n/** @typedef {import(\"./types.js\").ArgumentMeta} ArgumentMeta */\n/** @typedef {import(\"./types.js\").ParseFunctionReturn} ParseFunctionReturn */\n/** @typedef {import(\"./types.js\").ParseOptions} ParseOptions */\n\n/**\n * Convert a CSS Color string to a color object\n * @param {string} str\n * @param {ParseOptions} [options]\n * @returns {ColorConstructor}\n */\nexport default function parse (str, options) {\n\tlet env = {\n\t\tstr: String(str)?.trim(),\n\t\toptions,\n\t};\n\n\thooks.run(\"parse-start\", env);\n\n\tif (env.color) {\n\t\treturn env.color;\n\t}\n\n\tenv.parsed = parseFunction(env.str);\n\tlet ret;\n\tlet meta = env.options ? (env.options.parseMeta ?? env.options.meta) : null;\n\n\tif (env.parsed) {\n\t\t// Is a functional syntax\n\t\tlet name = env.parsed.name;\n\t\tlet format;\n\t\tlet space;\n\t\tlet coords = env.parsed.args;\n\t\tlet types = coords.map((c, i) => env.parsed.argMeta[i]?.type);\n\n\t\tif (name === \"color\") {\n\t\t\t// color() function\n\t\t\tlet id = coords.shift();\n\t\t\ttypes.shift();\n\t\t\t// Check against both <dashed-ident> and <ident> versions\n\t\t\tlet alternateId = id.startsWith(\"--\") ? id.substring(2) : `--${id}`;\n\t\t\tlet ids = [id, alternateId];\n\t\t\tformat = ColorSpace.findFormat({ name, id: ids, type: \"function\" });\n\n\t\t\tif (!format) {\n\t\t\t\t// Not found\n\t\t\t\tlet didYouMean;\n\n\t\t\t\tlet registryId = id in ColorSpace.registry ? id : alternateId;\n\t\t\t\tif (registryId in ColorSpace.registry) {\n\t\t\t\t\t// Used color space id instead of color() id, these are often different\n\t\t\t\t\tlet cssId = ColorSpace.registry[registryId].formats?.color?.id;\n\n\t\t\t\t\tif (cssId) {\n\t\t\t\t\t\tlet altColor = str.replace(\"color(\" + id, \"color(\" + cssId);\n\t\t\t\t\t\tdidYouMean = `Did you mean ${altColor}?`;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t`Cannot parse ${env.str}. ` + (didYouMean ?? \"Missing a plugin?\"),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tspace = format.space;\n\n\t\t\tif (format.id.startsWith(\"--\") && !id.startsWith(\"--\")) {\n\t\t\t\tdefaults.warn(\n\t\t\t\t\t`${space.name} is a non-standard space and not currently supported in the CSS spec. ` +\n\t\t\t\t\t\t`Use prefixed color(${format.id}) instead of color(${id}).`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (id.startsWith(\"--\") && !format.id.startsWith(\"--\")) {\n\t\t\t\tdefaults.warn(\n\t\t\t\t\t`${space.name} is a standard space and supported in the CSS spec. ` +\n\t\t\t\t\t\t`Use color(${format.id}) instead of prefixed color(${id}).`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tformat = ColorSpace.findFormat({ name, type: \"function\" });\n\t\t\tspace = format.space;\n\t\t}\n\n\t\tif (meta) {\n\t\t\tObject.assign(meta, {\n\t\t\t\tformat,\n\t\t\t\tformatId: format.name,\n\t\t\t\ttypes,\n\t\t\t\tcommas: env.parsed.commas,\n\t\t\t});\n\t\t}\n\n\t\tlet alpha = 1;\n\n\t\tif (env.parsed.lastAlpha) {\n\t\t\talpha = env.parsed.args.pop();\n\n\t\t\tif (meta) {\n\t\t\t\tmeta.alphaType = types.pop();\n\t\t\t}\n\t\t}\n\n\t\tlet coordCount = format.coords.length;\n\n\t\tif (coords.length !== coordCount) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`Expected ${coordCount} coordinates for ${space.id} in ${env.str}), got ${coords.length}`,\n\t\t\t);\n\t\t}\n\n\t\tcoords = format.coerceCoords(coords, types);\n\n\t\tret = { spaceId: space.id, coords, alpha };\n\t}\n\telse {\n\t\t// Custom, colorspace-specific format\n\t\tspaceloop: for (let space of ColorSpace.all) {\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\t// Convert to Format object\n\t\t\t\tlet formatObject = space.getFormat(format);\n\n\t\t\t\tlet color = formatObject.parse(env.str);\n\n\t\t\t\tif (color) {\n\t\t\t\t\tif (meta) {\n\t\t\t\t\t\tObject.assign(meta, { format: formatObject, formatId });\n\t\t\t\t\t}\n\n\t\t\t\t\tret = color;\n\t\t\t\t\tbreak spaceloop;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!ret) {\n\t\t// If we're here, we couldn't parse\n\t\tthrow new TypeError(`Could not parse ${str} as a color. Missing a plugin?`);\n\t}\n\n\t// Clamp alpha to [0, 1]\n\tret.alpha = isNone(ret.alpha)\n\t\t? ret.alpha\n\t\t: ret.alpha === undefined\n\t\t\t? 1\n\t\t\t: clamp(0, ret.alpha, 1);\n\n\treturn ret;\n}\n\n/**\n * Units and multiplication factors for the internally stored numbers\n */\nexport const units = {\n\t\"%\": 0.01,\n\tdeg: 1,\n\tgrad: 0.9,\n\trad: 180 / Math.PI,\n\tturn: 360,\n};\n\nexport const regex = {\n\t// Need to list calc(NaN) explicitly as otherwise its ending paren would terminate the function call\n\tfunction: /^([a-z]+)\\(((?:calc\\(NaN\\)|.)+?)\\)$/i,\n\tnumber: /^([-+]?(?:[0-9]*\\.)?[0-9]+(e[-+]?[0-9]+)?)$/i,\n\tunitValue: RegExp(`(${Object.keys(units).join(\"|\")})$`),\n\n\t// NOTE The -+ are not just for prefix, but also for idents, and e+N notation!\n\tsingleArgument: /\\/?\\s*(none|NaN|calc\\(NaN\\)|[-+\\w.]+(?:%|deg|g?rad|turn)?)/g,\n};\n\n/**\n * Parse a single function argument\n * @param {string} rawArg\n * @returns {{value: number, meta: ArgumentMeta}}\n */\nexport function parseArgument (rawArg) {\n\t/** @type {Partial<ArgumentMeta>} */\n\tlet meta = {};\n\tlet unit = rawArg.match(regex.unitValue)?.[0];\n\t/** @type {string | number} */\n\tlet value = (meta.raw = rawArg);\n\n\tif (unit) {\n\t\t// It’s a dimension token\n\t\tmeta.type = unit === \"%\" ? \"<percentage>\" : \"<angle>\";\n\t\tmeta.unit = unit;\n\t\tmeta.unitless = Number(value.slice(0, -unit.length)); // unitless number\n\n\t\tvalue = meta.unitless * units[unit];\n\t}\n\telse if (regex.number.test(value)) {\n\t\t// It's a number\n\t\t// Convert numerical args to numbers\n\t\tvalue = Number(value);\n\t\tmeta.type = \"<number>\";\n\t}\n\telse if (value === \"none\") {\n\t\tvalue = null;\n\t}\n\telse if (value === \"NaN\" || value === \"calc(NaN)\") {\n\t\tvalue = NaN;\n\t\tmeta.type = \"<number>\";\n\t}\n\telse {\n\t\tmeta.type = \"<ident>\";\n\t}\n\n\treturn { value: /** @type {number} */ (value), meta: /** @type {ArgumentMeta} */ (meta) };\n}\n\n/**\n * Parse a CSS function, regardless of its name and arguments\n * @param {string} str String to parse\n * @return {ParseFunctionReturn | void}\n */\nexport function parseFunction (str) {\n\tif (!str) {\n\t\treturn;\n\t}\n\n\tstr = str.trim();\n\n\tlet parts = str.match(regex.function);\n\n\tif (parts) {\n\t\t// It is a function, parse args\n\t\tlet args = [];\n\t\tlet argMeta = [];\n\t\tlet lastAlpha = false;\n\t\tlet name = parts[1].toLowerCase();\n\n\t\tlet separators = parts[2].replace(regex.singleArgument, ($0, rawArg) => {\n\t\t\tlet { value, meta } = parseArgument(rawArg);\n\n\t\t\tif (\n\t\t\t\t// If there's a slash here, it's modern syntax\n\t\t\t\t$0.startsWith(\"/\") ||\n\t\t\t\t// If there's still elements to process after there's already 3 in `args` (and the we're not dealing with \"color()\"), it's likely to be a legacy color like \"hsl(0, 0%, 0%, 0.5)\"\n\t\t\t\t(name !== \"color\" && args.length === 3)\n\t\t\t) {\n\t\t\t\t// It's alpha\n\t\t\t\tlastAlpha = true;\n\t\t\t}\n\n\t\t\targs.push(value);\n\t\t\targMeta.push(meta);\n\t\t\treturn \"\";\n\t\t});\n\n\t\treturn {\n\t\t\tname,\n\t\t\targs,\n\t\t\targMeta,\n\t\t\tlastAlpha,\n\t\t\tcommas: separators.includes(\",\"),\n\t\t\trawName: parts[1],\n\t\t\trawArgs: parts[2],\n\t\t};\n\t}\n}\n","import ColorSpace from \"./ColorSpace.js\";\nimport { isString, isInstance } from \"./util.js\";\nimport parse from \"./parse.js\";\n\n/** @import { ColorTypes, ParseOptions as GetColorOptions, PlainColorObject } from \"./types.js\" */\n\n/**\n * Resolves a color reference (object or string) to a plain color object\n * @overload\n * @param {ColorTypes} color\n * @param {GetColorOptions} [options]\n * @returns {PlainColorObject}\n */\n/**\n * @overload\n * @param {ColorTypes[]} color\n * @param {GetColorOptions} [options]\n * @returns {PlainColorObject[]}\n */\nexport default function getColor (color, options) {\n\tif (Array.isArray(color)) {\n\t\treturn color.map(c => getColor(c, options));\n\t}\n\n\tif (!color) {\n\t\tthrow new TypeError(\"Empty color reference\");\n\t}\n\n\tif (isString(color)) {\n\t\tcolor = parse(color, options);\n\t}\n\n\t// Object fixup\n\tlet space = color.space || color.spaceId;\n\n\tif (typeof space === \"string\") {\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}\n","/**\n * @packageDocumentation\n * Defines the class and other types related to creating color spaces.\n * For the builtin color spaces, see the `spaces` module.\n */\nimport { type, isNone, isInstance } from \"./util.js\";\nimport Format from \"./Format.js\";\nimport { getWhite } from \"./adapt.js\";\nimport hooks from \"./hooks.js\";\nimport getColor from \"./getColor.js\";\n\nconst ε = 0.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\n\t\tfor (let name in coords) {\n\t\t\tif (!(\"name\" in coords[name])) {\n\t\t\t\tcoords[name].name = name;\n\t\t\t}\n\t\t}\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 (!this.formats.color?.id) {\n\t\t\tthis.formats.color = {\n\t\t\t\t...(this.formats.color ?? {}),\n\t\t\t\tid: options.cssId || this.id,\n\t\t\t};\n\t\t}\n\n\t\t// Gamut space\n\n\t\tif (options.gamutSpace) {\n\t\t\t// Gamut space explicitly specified\n\t\t\tthis.gamutSpace =\n\t\t\t\toptions.gamutSpace === \"self\" ? this : ColorSpace.get(options.gamutSpace);\n\t\t}\n\t\telse {\n\t\t\t// No gamut space specified, calculate a sensible default\n\t\t\tif (this.isPolar) {\n\t\t\t\t// Do not check gamut through polar coordinates\n\t\t\t\tthis.gamutSpace = this.base;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthis.gamutSpace = this;\n\t\t\t}\n\t\t}\n\n\t\t// Optimize inGamut for unbounded spaces\n\t\tif (this.gamutSpace.isUnbounded) {\n\t\t\tthis.inGamut = (coords, options) => {\n\t\t\t\treturn true;\n\t\t\t};\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\tObject.defineProperty(this, \"path\", {\n\t\t\tvalue: getPath(this).reverse(),\n\t\t\twritable: false,\n\t\t\tenumerable: true,\n\t\t\tconfigurable: true,\n\t\t});\n\n\t\thooks.run(\"colorspace-init-end\", this);\n\t}\n\n\tinGamut (coords, { epsilon = ε } = {}) {\n\t\tif (!this.equals(this.gamutSpace)) {\n\t\t\tcoords = this.to(this.gamutSpace, coords);\n\t\t\treturn this.gamutSpace.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 (isNone(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 (\n\t\t\t\t\t(min === undefined || c >= min - epsilon) &&\n\t\t\t\t\t(max === undefined || c <= max + epsilon)\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn true;\n\t\t});\n\t}\n\n\tget isUnbounded () {\n\t\treturn Object.values(this.coords).every(coord => !(\"range\" in coord));\n\t}\n\n\tget cssId () {\n\t\treturn this.formats?.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/**\n\t * Lookup a format in this color space\n\t * @param {string | object | Format} format - Format id if string. If object, it's converted to a `Format` object and returned.\n\t * @returns {Format}\n\t */\n\tgetFormat (format) {\n\t\tif (!format) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (format === \"default\") {\n\t\t\tformat = Object.values(this.formats)[0];\n\t\t}\n\t\telse if (typeof format === \"string\") {\n\t\t\tformat = this.formats[format];\n\t\t}\n\n\t\tlet ret = Format.get(format, this);\n\n\t\tif (ret !== format && format.name in this.formats) {\n\t\t\t// Update the format we have on file so we can find it more quickly next time\n\t\t\tthis.formats[format.name] = ret;\n\t\t}\n\n\t\treturn ret;\n\t}\n\n\t/**\n\t * Check if this color space is the same as another color space reference.\n\t * Allows proxying color space objects and comparing color spaces with ids.\n\t * @param {string | ColorSpace} space ColorSpace object or id to compare to\n\t * @returns {boolean}\n\t */\n\tequals (space) {\n\t\tif (!space) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn this === space || this.id === space || this.id === space.id;\n\t}\n\n\tto (space, coords) {\n\t\tif (arguments.length === 1) {\n\t\t\tconst color = getColor(space);\n\t\t\t[space, coords] = [color.space, color.coords];\n\t\t}\n\n\t\tspace = ColorSpace.get(space);\n\n\t\tif (this.equals(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 => (isNone(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].equals(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(\n\t\t\t\t`Cannot convert between color spaces ${this} and ${space}: no connection space was found`,\n\t\t\t);\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\tconst color = getColor(space);\n\t\t\t[space, coords] = [color.space, color.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 || isInstance(space, this)) {\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 * Look up all color spaces for a format that matches certain criteria\n\t * @param {object | string} filters\n\t * @param {Array<ColorSpace>} [spaces=ColorSpace.all]\n\t * @returns {Format | null}\n\t */\n\tstatic findFormat (filters, spaces = ColorSpace.all) {\n\t\tif (!filters) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (typeof filters === \"string\") {\n\t\t\tfilters = { name: filters };\n\t\t}\n\n\t\tfor (let space of spaces) {\n\t\t\tfor (let [name, format] of Object.entries(space.formats)) {\n\t\t\t\tformat.name ??= name;\n\t\t\t\tformat.type ??= \"function\";\n\n\t\t\t\tlet matches =\n\t\t\t\t\t(!filters.name || format.name === filters.name) &&\n\t\t\t\t\t(!filters.type || format.type === filters.type);\n\n\t\t\t\tif (filters.id) {\n\t\t\t\t\tlet ids = format.ids || [format.id];\n\t\t\t\t\tlet filterIds = Array.isArray(filters.id) ? filters.id : [filters.id];\n\t\t\t\t\tmatches &&= filterIds.some(id => ids.includes(id));\n\t\t\t\t}\n\n\t\t\t\tif (matches) {\n\t\t\t\t\tlet ret = Format.get(format, space);\n\n\t\t\t\t\tif (ret !== format) {\n\t\t\t\t\t\tspace.formats[format.name] = ret;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn ret;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn null;\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(\n\t\t\t\t`Cannot resolve coordinate reference ${ref}: No color space specified and relative references are not allowed here`,\n\t\t\t);\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 (\n\t\t\t\tid.toLowerCase() === normalizedCoord ||\n\t\t\t\tmeta.name?.toLowerCase() === normalizedCoord\n\t\t\t) {\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(\n\t\t\t`No \"${coord}\" coordinate found in ${space.name}. Its coordinates are: ${Object.keys(space.coords).join(\", \")}`,\n\t\t);\n\t}\n\n\tstatic DEFAULT_FORMAT = {\n\t\ttype: \"functions\",\n\t\tname: \"color\",\n\t};\n}\n\nfunction getPath (space) {\n\tlet ret = [space];\n\n\tfor (let s = space; (s = s.base); ) {\n\t\tret.push(s);\n\t}\n\n\treturn ret;\n}\n","import ColorSpace from \"../ColorSpace.js\";\n\nexport default new ColorSpace({\n\tid: \"xyz-d65\",\n\tname: \"XYZ D65\",\n\tcoords: {\n\t\tx: {\n\t\t\trefRange: [0, 1],\n\t\t\tname: \"X\",\n\t\t},\n\t\ty: {\n\t\t\trefRange: [0, 1],\n\t\t\tname: \"Y\",\n\t\t},\n\t\tz: {\n\t\t\trefRange: [0, 1],\n\t\t\tname: \"Z\",\n\t\t},\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 \"./ColorSpace.js\";\nimport { multiply_v3_m3x3 } from \"./util.js\";\nimport adapt from \"./adapt.js\";\nimport XYZ_D65 from \"./spaces/xyz-d65.js\";\n\n// Type re-exports\n/** @typedef {import(\"./types.js\").RGBOptions} RGBOptions */\n\n/** Convenience class for RGB color spaces */\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 {RGBOptions} options\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 = multiply_v3_m3x3(rgb, options.toXYZ_M);\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 multiply_v3_m3x3(xyz, options.fromXYZ_M);\n\t\t\t};\n\t\t}\n\n\t\toptions.referred ??= \"display\";\n\n\t\tsuper(options);\n\t}\n}\n","import { isString } from \"./util.js\";\nimport getColor from \"./getColor.js\";\n\n/** @import { ColorTypes, PlainColorObject } from \"./types.js\" */\n\n// Type re-exports\n/** @typedef {import(\"./types.js\").TryColorOptions} TryColorOptions */\n\n/**\n * Resolves a color reference (object or string) to a plain color object, or `null` if resolution fails.\n * Can resolve more complex CSS colors (e.g. relative colors, `calc()`, CSS variables, `color-mix()`, etc.) through the DOM.\n *\n * @overload\n * @param {ColorTypes} color\n * @param {TryColorOptions} [options]\n * @returns {PlainColorObject | null}\n */\n/**\n * @overload\n * @param {ColorTypes[]} color\n * @param {TryColorOptions} [options]\n * @returns {(PlainColorObject | null)[]}\n */\nexport default function tryColor (color, options = {}) {\n\tif (Array.isArray(color)) {\n\t\treturn color.map(c => tryColor(c, options));\n\t}\n\n\tlet { cssProperty = \"background-color\", element, ...getColorOptions } = options;\n\tlet error = null;\n\ttry {\n\t\treturn getColor(color, getColorOptions);\n\t}\n\tcatch (e) {\n\t\terror = e;\n\t}\n\n\tlet { CSS, getComputedStyle } = globalThis;\n\tif (isString(color) && element && CSS && getComputedStyle) {\n\t\t// Try resolving the color using the DOM, if supported in CSS\n\t\tif (CSS.supports(cssProperty, color)) {\n\t\t\tlet previousValue = element.style[cssProperty];\n\n\t\t\tif (color !== previousValue) {\n\t\t\t\telement.style[cssProperty] = color;\n\t\t\t}\n\n\t\t\tlet computedColor = getComputedStyle(element).getPropertyValue(cssProperty);\n\n\t\t\tif (color !== previousValue) {\n\t\t\t\telement.style[cssProperty] = previousValue;\n\t\t\t}\n\n\t\t\tif (computedColor !== color) {\n\t\t\t\t// getComputedStyle() changed the color, try again\n\t\t\t\ttry {\n\t\t\t\t\treturn getColor(computedColor, getColorOptions);\n\t\t\t\t}\n\t\t\t\tcatch (e) {\n\t\t\t\t\terror = e;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Still not resolved\n\t\t\t\terror = {\n\t\t\t\t\tmessage: \"Color value is a valid CSS color, but it could not be resolved :(\",\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n\n\t// If we're here, we failed to resolve the color\n\tif (options.errorMeta) {\n\t\toptions.errorMeta.error = error;\n\t}\n\n\treturn null;\n}\n","import ColorSpace from \"./ColorSpace.js\";\nimport getColor from \"./getColor.js\";\nimport { toPrecision } from \"./util.js\";\n\n/** @import { ColorTypes, Coords } from \"./types.js\" */\n\n/**\n * Options for {@link getAll}\n * @typedef GetAllOptions\n * @property {string | ColorSpace | undefined} [space]\n * The color space to convert to. Defaults to the color's current space\n * @property {number | undefined} [precision]\n * The number of significant digits to round the coordinates to\n */\n\n/**\n * Get the coordinates of a color in any color space\n * @overload\n * @param {ColorTypes} color\n * @param {string | ColorSpace} [options=color.space] The color space to convert to. Defaults to the color's current space\n * @returns {Coords} The color coordinates in the given color space\n */\n/**\n * @overload\n * @param {ColorTypes} color\n * @param {GetAllOptions} [options]\n * @returns {Coords} The color coordinates in the given color space\n */\nexport default function getAll (color, options) {\n\tcolor = getColor(color);\n\n\tlet space = ColorSpace.get(options, options?.space);\n\tlet precision = options?.precision;\n\n\tlet coords;\n\tif (!space || color.space.equals(space)) {\n\t\t// No conversion needed\n\t\tcoords = color.coords.slice();\n\t}\n\telse {\n\t\tcoords = space.from(color);\n\t}\n\n\treturn precision === undefined ? coords : coords.map(coord => toPrecision(coord, precision));\n}\n","import ColorSpace from \"./ColorSpace.js\";\nimport getAll from \"./getAll.js\";\nimport getColor from \"./getColor.js\";\n\n/** @import { ColorTypes, Ref } from \"./types.js\" */\n\n/**\n * @param {ColorTypes} color\n * @param {Ref} prop\n * @returns {number}\n */\nexport default function get (color, prop) {\n\tcolor = getColor(color);\n\n\tif (prop === \"alpha\") {\n\t\treturn color.alpha ?? 1;\n\t}\n\n\tlet { space, index } = ColorSpace.resolveCoord(prop, color.space);\n\tlet coords = getAll(color, space);\n\treturn coords[index];\n}\n","import ColorSpace from \"./C