UNPKG

colorjs.io

Version:

Let’s get serious about color

213 lines (187 loc) 4.86 kB
/** * Various utility functions */ export { default as multiplyMatrices, multiply_v3_m3x3 } from "./multiply-matrices.js"; /** * Check if a value is a string (including a String object) * @param {any} str - Value to check * @returns {str is string} */ export function isString (str) { return type(str) === "string"; } /** * Determine the internal JavaScript [[Class]] of an object. * @param {any} o - Value to check * @returns {string} */ export function type (o) { let str = Object.prototype.toString.call(o); return (str.match(/^\[object\s+(.*?)\]$/)[1] || "").toLowerCase(); } /** * @param {number} n * @param {{ precision?: number | undefined, unit?: string | undefined }} options * @returns {string} */ export function serializeNumber (n, { precision = 16, unit }) { if (isNone(n)) { return "none"; } n = +toPrecision(n, precision); return n + (unit ?? ""); } /** * Check if a value corresponds to a none argument * @param {any} n - Value to check * @returns {n is null} */ export function isNone (n) { return n === null; } /** * Replace none values with 0 * @param {number | null} n * @returns {number} */ export function skipNone (n) { return isNone(n) ? 0 : n; } /** * Round a number to a certain number of significant digits * @param {number} n - The number to round * @param {number} precision - Number of significant digits */ export function toPrecision (n, precision) { if (n === 0) { return 0; } let integer = ~~n; let digits = 0; if (integer && precision) { digits = ~~Math.log10(Math.abs(integer)) + 1; } const multiplier = 10.0 ** (precision - digits); return Math.floor(n * multiplier + 0.5) / multiplier; } /** * @param {number} start * @param {number} end * @param {number} p */ export function interpolate (start, end, p) { if (isNaN(start)) { return end; } if (isNaN(end)) { return start; } return start + (end - start) * p; } /** * @param {number} start * @param {number} end * @param {number} value */ export function interpolateInv (start, end, value) { return (value - start) / (end - start); } /** * @param {[number, number]} from * @param {[number, number]} to * @param {number} value */ export function mapRange (from, to, value) { if ( !from || !to || from === to || (from[0] === to[0] && from[1] === to[1]) || isNaN(value) || value === null ) { // Ranges missing or the same return value; } return interpolate(to[0], to[1], interpolateInv(from[0], from[1], value)); } /** * Clamp value between the minimum and maximum * @param {number} min minimum value to return * @param {number} val the value to return if it is between min and max * @param {number} max maximum value to return */ export function clamp (min, val, max) { return Math.max(Math.min(max, val), min); } /** * Copy sign of one value to another. * @param {number} to - Number to copy sign to * @param {number} from - Number to copy sign from */ export function copySign (to, from) { return Math.sign(to) === Math.sign(from) ? to : -to; } /** * Perform pow on a signed number and copy sign to result * @param {number} base The base number * @param {number} exp The exponent */ export function spow (base, exp) { return copySign(Math.abs(base) ** exp, base); } /** * Perform a divide, but return zero if the denominator is zero * @param {number} n The numerator * @param {number} d The denominator */ export function zdiv (n, d) { return d === 0 ? 0 : n / d; } /** * Perform a bisect on a sorted list and locate the insertion point for * a value in arr to maintain sorted order. * @param {number[]} arr - array of sorted numbers * @param {number} value - value to find insertion point for * @param {number} lo - used to specify a the low end of a subset of the list * @param {number} hi - used to specify a the high end of a subset of the list */ export function bisectLeft (arr, value, lo = 0, hi = arr.length) { while (lo < hi) { const mid = (lo + hi) >> 1; if (arr[mid] < value) { lo = mid + 1; } else { hi = mid; } } return lo; } /** * Determines whether an argument is an instance of a constructor, including subclasses. * This is done by first just checking `instanceof`, * and then comparing the string names of the constructors if that fails. * @param {any} arg * @param {C} constructor * @template {new (...args: any) => any} C * @returns {arg is InstanceType<C>} */ export function isInstance (arg, constructor) { if (arg instanceof constructor) { return true; } const targetName = constructor.name; while (arg) { const proto = Object.getPrototypeOf(arg); const constructorName = proto?.constructor?.name; if (constructorName === targetName) { return true; } if (!constructorName || constructorName === "Object") { return false; } arg = proto; } return false; }