colorjs.io
Version:
Let’s get serious about color
213 lines (187 loc) • 4.86 kB
JavaScript
/**
* 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;
}