UNPKG

allc

Version:

A small typescript package for color conversion.

188 lines (187 loc) 10.7 kB
/** * Calculates the 3D Euclidean distance between two HSV colors. * * This function first calculates the 3D position of the HSV parameters * **in the HSV cone**, and then it calculates the Euclidean distance. * This is different and more accurate than the Euclidean distance * of just the HSV components. * * @param h1 The hue component of the first HSV color, unbounded, in radians. * @param s1 The saturation component of the first HSV color, typically in the range [0, 1]. * @param v1 The value component of the first HSV color, typically in the range [0, 1]. * @param h2 The hue component of the second HSV color, unbounded, in radians. * @param s2 The saturation component of the second HSV color, typically in the range [0, 1]. * @param v2 The value component of the second HSV color, typically in the range [0, 1]. * * @returns The color distance. */ export const distance3DHSV = (h1, s1, v1, h2, s2, v2) => Math.hypot(s1 * v1 * Math.cos(h1) - s2 * v2 * Math.cos(h2), s1 * v1 * Math.sin(h1) - s2 * v2 * Math.sin(h2), v1 - v2); /** * Calculates the 3D Euclidean distance between two HSL colors. * * This function first calculates the 3D position of the HSL parameters * **in the HSL bicone**, and then it calculates the Euclidean distance. * This is different and more accurate than the Euclidean distance * of just the HSL components. * * @param h1 The hue component of the first HSL color, unbounded, in radians. * @param s1 The saturation component of the first HSL color, typically in the range [0, 1]. * @param l1 The lightness component of the first HSL color, typically in the range [0, 1]. * @param h2 The hue component of the second HSL color, unbounded, in radians. * @param s2 The saturation component of the second HSL color, typically in the range [0, 1]. * @param l2 The lightness component of the second HSL color, typically in the range [0, 1]. * * @returns The color distance. */ export const distance3DHSL = (h1, s1, l1, h2, s2, l2) => { const r1 = s1 * (1 - Math.abs(2 * l1 - 1)), r2 = s2 * (1 - Math.abs(2 * l2 - 1)); return Math.hypot(r1 * Math.cos(h1) - r2 * Math.cos(h2), r1 * Math.sin(h1) - r2 * Math.sin(h2), l1 - l2); }; /** * Calculates the 3D Euclidean distance between two HSI colors. * * This function first calculates the 3D position of the HSI parameters, * and then it calculates the Euclidean distance. * This is different and more accurate than the Euclidean distance * of just the HSI components. * * @param h1 The hue component of the first HSI color, unbounded, in radians. * @param s1 The saturation component of the first HSI color, typically in the range [0, 1]. * @param i1 The intensity component of the first HSI color, typically in the range [0, 1]. * @param h2 The hue component of the second HSI color, unbounded, in radians. * @param s2 The saturation component of the second HSI color, typically in the range [0, 1]. * @param i2 The intensity component of the second HSI color, typically in the range [0, 1]. * * @returns The color distance. */ export const distance3DHSI = (h1, s1, i1, h2, s2, i2) => Math.hypot(i1 - i2, Math.cos(h1) * s1 - Math.cos(h2) * s2, Math.sin(h1) * s1 - Math.sin(h2) * s2); /** * Calculates the CMC l:c (1984) color difference "ΔE*" between two CIELCH colors. * * @param l1 The luminance component of the first CIELCH color, range [0, 1]. * @param c1 The chroma component of the first CIELCH color, unbounded, non-negative. * @param h1 The hue component of the first CIELCH color, unbounded, in radians. * @param l2 The luminance component of the second CIELCH color, range [0, 1]. * @param c2 The chroma component of the second CIELCH color, unbounded, non-negative. * @param h2 The hue component of the second CIELCH color, unbounded, in radians. * @param l An optional lightness weight, defaults to 2. * @param c An optional chroma weight, defaults to 1. * * @returns The color difference. * @see https://en.wikipedia.org/wiki/Color_difference#CMC_l:c_(1984) */ export const distanceCMClc = (l1, c1, h1, l2, c2, h2, l = 2, c = 1) => { const h1Normalized = betterMod(h1, Math.PI / 2), h2Normalized = betterMod(h2, Math.PI / 2); const sl = l1 < 16 ? 0.511 : (0.040975 * l1) / (1 + 0.01765 * l1); const sc = 0.0638 * c1 / (1 + 0.0131 * c1) + 0.638; const f = Math.sqrt(c1 ** 4 / (c1 ** 4 + 1900)); // Constant values `164 / 180 * Math.PI` and `345 / 180 * Math.PI`. const t = 2.8623399732707004 <= h1Normalized && h1Normalized <= 6.021385919380437 ? 0.56 + Math.abs(0.2 * Math.cos(h1Normalized + 2.9321531433504737)) // Constant value `Math.PI * 168 / 180` : 0.36 + Math.abs(0.4 * Math.cos(h1Normalized + 0.6108652381980153)); // Constant value `Math.PI * 35 / 180` const sh = sc * (f * t + 1 - f); const dh = Math.abs(h2Normalized - h1Normalized); return Math.hypot((l2 - l1) / (l * sl), (c2 - c1) / (c * sc), // Calculate Delta H ab, the smallest angle in degrees between h1 and h2. (Math.min(dh, Math.PI * 2 * dh) / Math.PI * 180) / sh); }; export const CIE94_GRAPHICS_KL = 1; export const CIE94_GRAPHICS_KC = 1; export const CIE94_GRAPHICS_KH = 1; export const CIE94_GRAPHICS_K1 = 0.045; export const CIE94_GRAPHICS_K2 = 0.015; export const CIE94_TEXTILE_KL = 2; export const CIE94_TEXTILE_KC = 1; export const CIE94_TEXTILE_KH = 1; export const CIE94_TEXTILE_K1 = 0.048; export const CIE94_TEXTILE_K2 = 0.015; /** * Calculates the CIE94 color difference "ΔE*" between two CIELAB colors. * Below, there is a table for the application-dependent parameters. * * | Parameter | Graphics (default) | Textile | * |-----------|-----------------------------------------------|---------------------------------------------| * | `kl` | {@link CIE94_GRAPHICS_KL `CIE94_GRAPHICS_KL`} | {@link CIE94_TEXTILE_KL `CIE94_TEXTILE_KL`} | * | `kc` | {@link CIE94_GRAPHICS_KC `CIE94_GRAPHICS_KC`} | {@link CIE94_TEXTILE_KC `CIE94_TEXTILE_KC`} | * | `kh` | {@link CIE94_GRAPHICS_KH `CIE94_GRAPHICS_KH`} | {@link CIE94_TEXTILE_KH `CIE94_TEXTILE_KH`} | * | `K1` | {@link CIE94_GRAPHICS_K1 `CIE94_GRAPHICS_K1`} | {@link CIE94_TEXTILE_K1 `CIE94_TEXTILE_K1`} | * | `K2` | {@link CIE94_GRAPHICS_K2 `CIE94_GRAPHICS_K2`} | {@link CIE94_TEXTILE_K2 `CIE94_TEXTILE_K2`} | * * @param l1 The luminance component of the first CIELAB color, range [0, 1]. * @param a1 The a component of the first CIELAB color, unbounded. * @param b1 The b component of the first CIELAB color, unbounded. * @param l2 The luminance component of the second CIELAB color, range [0, 1]. * @param a2 The a component of the second CIELAB color, unbounded. * @param b2 The b component of the second CIELAB color, unbounded. * @param kl An optional, application-dependent luminance weight. * @param kc An optional, application-dependent chroma weight. * @param kh An optional, application-dependent hue weight. * @param K1 An optional, application-dependent value. * @param K2 An optional, application-dependent value. * * @returns The color difference. * @see https://en.wikipedia.org/wiki/Color_difference#CIE94 */ export const distanceCIE94 = (l1, a1, b1, l2, a2, b2, kl = CIE94_GRAPHICS_KL, kc = CIE94_GRAPHICS_KC, kh = CIE94_GRAPHICS_KH, K1 = CIE94_GRAPHICS_K1, K2 = CIE94_GRAPHICS_K2) => { const c1 = Math.hypot(a1, b1), c2 = Math.hypot(a2, b2); return Math.hypot((l2 - l1) / kl, (c2 - c1) / (kc * (1 + K1 * c1)), Math.hypot(a2 - a1, b2 - b1, c2 - c1) / (kh * (1 + K2 * c1))); }; /** * Calculates the CIEDE2000 color difference "ΔE*" between two CIELAB colors. * * @param l1 The luminance component of the first CIELAB color, range [0, 1]. * @param a1 The a component of the first CIELAB color, unbounded. * @param b1 The b component of the first CIELAB color, unbounded. * @param l2 The luminance component of the second CIELAB color, range [0, 1]. * @param a2 The a component of the second CIELAB color, unbounded. * @param b2 The b component of the second CIELAB color, unbounded. * @param kl An optional, application-dependent luminance weight, defaults to 1. * @param kc An optional, application-dependent chroma weight, defaults to 1. * @param kh An optional, application-dependent hue weight, defaults to 1. * * @returns The color difference. * @see https://en.wikipedia.org/wiki/Color_difference#CIEDE2000 */ export const distanceCIEDE2000 = (l1, a1, b1, l2, a2, b2, kl = 1, kc = 1, kh = 1) => { const c1 = Math.hypot(a1, b1), c2 = Math.hypot(a2, b2); const cAverage = (c1 + c2) / 2; const aDashTerm = (1 - Math.sqrt(cAverage ** 7 / (cAverage ** 7 + 25 ** 7))); const a1Dash = a1 + a1 / 2 * aDashTerm, a2Dash = a2 + a2 / 2 * aDashTerm; const c1Dash = Math.hypot(a2Dash, b1), c2Dash = Math.hypot(a2Dash, b2); const lAverage = (l1 + l2) / 2; const lAverage50 = (lAverage - 50) * (lAverage - 50); const sl = 1 + 0.015 * lAverage50 / Math.sqrt(20 + lAverage50); const cDashAverage = (c1Dash + c2Dash) / 2; const h1Dash = Math.atan2(b1, a1Dash) % (2 * Math.PI), h2Dash = Math.atan2(b2, a2Dash) % (2 * Math.PI); const hDashDiff = Math.abs(h2Dash - h1Dash); /* const deltaHDashSmall = hDashDiff <= Math.PI ? h2Dash - h1Dash : h2Dash <= h1Dash ? h2Dash - h1Dash + 2 * Math.PI : h2Dash - h1Dash - 2 * Math.PI; */ const deltaHDash = 2 * Math.sqrt(c1Dash * c2Dash) * Math.sin(Math.PI * (a1Dash - a2Dash) / 360); const hDashAverage = hDashDiff <= Math.PI ? (h1Dash + h2Dash) / 2 : h1Dash + h2Dash < Math.PI * 2 ? (h1Dash + h2Dash + Math.PI * 2) / 2 : (h2Dash - h1Dash - Math.PI * 2) / 2; const t = 1 - 0.17 * Math.cos(hDashAverage - Math.PI / 6) + 0.24 * Math.cos(2 * hDashAverage) + 0.32 * Math.cos(3 * hDashAverage + Math.PI / 30) // Constant value `63 * Math.PI / 180` - 0.2 * Math.cos(4 * hDashAverage - 1.0995574287564271); const sc = 1 + 0.045 * cDashAverage, sh = 1 + 0.015 * cDashAverage * cDashAverage * t; const rt = -2 * Math.sqrt(cAverage ** 7 / (cAverage ** 7 + 25 ** 7)) // Constant values `275 * Math.PI / 180` and `25 * Math.PI / 180` * Math.sin(Math.PI / 3 * Math.exp(-(((hDashAverage - 4.799655442984406) / 0.4363323129985824) ** 2))); return Math.sqrt((l2 - l1) * (l2 - l1) / (kl * sl * kl * sl) + (c2Dash - c1Dash) * (c2Dash - c1Dash) / (kc * sc * kc * sc) + deltaHDash * deltaHDash / (kh * sh * kh * sh) + rt * (c2Dash - c1Dash) / (kc * sc) * deltaHDash * (kh * sh)); }; const betterMod = (x, m) => ((x % m) + m) % m;