culori
Version:
A general-purpose color library for JavaScript
316 lines (260 loc) • 8.13 kB
JavaScript
import { getMode } from './modes.js';
import converter from './converter.js';
import normalizeHue from './util/normalizeHue.js';
const differenceHueSaturation = (std, smp) => {
if (std.h === undefined || smp.h === undefined || !std.s || !smp.s) {
return 0;
}
let std_h = normalizeHue(std.h);
let smp_h = normalizeHue(smp.h);
let dH = Math.sin((((smp_h - std_h + 360) / 2) * Math.PI) / 180);
return 2 * Math.sqrt(std.s * smp.s) * dH;
};
const differenceHueNaive = (std, smp) => {
if (std.h === undefined || smp.h === undefined) {
return 0;
}
let std_h = normalizeHue(std.h);
let smp_h = normalizeHue(smp.h);
if (Math.abs(smp_h - std_h) > 180) {
// todo should this be normalized once again?
return std_h - (smp_h - 360 * Math.sign(smp_h - std_h));
}
return smp_h - std_h;
};
const differenceHueChroma = (std, smp) => {
if (std.h === undefined || smp.h === undefined || !std.c || !smp.c) {
return 0;
}
let std_h = normalizeHue(std.h);
let smp_h = normalizeHue(smp.h);
let dH = Math.sin((((smp_h - std_h + 360) / 2) * Math.PI) / 180);
return 2 * Math.sqrt(std.c * smp.c) * dH;
};
const differenceEuclidean = (mode = 'rgb', weights = [1, 1, 1, 0]) => {
let def = getMode(mode);
let channels = def.channels;
let diffs = def.difference;
let conv = converter(mode);
return (std, smp) => {
let ConvStd = conv(std);
let ConvSmp = conv(smp);
return Math.sqrt(
channels.reduce((sum, k, idx) => {
let delta = diffs[k]
? diffs[k](ConvStd, ConvSmp)
: ConvStd[k] - ConvSmp[k];
return (
sum +
(weights[idx] || 0) * Math.pow(isNaN(delta) ? 0 : delta, 2)
);
}, 0)
);
};
};
const differenceCie76 = () => differenceEuclidean('lab65');
const differenceCie94 = (kL = 1, K1 = 0.045, K2 = 0.015) => {
let lab = converter('lab65');
return (std, smp) => {
let LabStd = lab(std);
let LabSmp = lab(smp);
// Extract Lab values, and compute Chroma
let lStd = LabStd.l;
let aStd = LabStd.a;
let bStd = LabStd.b;
let cStd = Math.sqrt(aStd * aStd + bStd * bStd);
let lSmp = LabSmp.l;
let aSmp = LabSmp.a;
let bSmp = LabSmp.b;
let cSmp = Math.sqrt(aSmp * aSmp + bSmp * bSmp);
let dL2 = Math.pow(lStd - lSmp, 2);
let dC2 = Math.pow(cStd - cSmp, 2);
let dH2 = Math.pow(aStd - aSmp, 2) + Math.pow(bStd - bSmp, 2) - dC2;
return Math.sqrt(
dL2 / Math.pow(kL, 2) +
dC2 / Math.pow(1 + K1 * cStd, 2) +
dH2 / Math.pow(1 + K2 * cStd, 2)
);
};
};
/*
CIEDE2000 color difference, original Matlab implementation by Gaurav Sharma
Based on "The CIEDE2000 Color-Difference Formula: Implementation Notes, Supplementary Test Data, and Mathematical Observations"
by Gaurav Sharma, Wencheng Wu, Edul N. Dalal in Color Research and Application, vol. 30. No. 1, pp. 21-30, February 2005.
http://www2.ece.rochester.edu/~gsharma/ciede2000/
*/
const differenceCiede2000 = (Kl = 1, Kc = 1, Kh = 1) => {
let lab = converter('lab65');
return (std, smp) => {
let LabStd = lab(std);
let LabSmp = lab(smp);
let lStd = LabStd.l;
let aStd = LabStd.a;
let bStd = LabStd.b;
let cStd = Math.sqrt(aStd * aStd + bStd * bStd);
let lSmp = LabSmp.l;
let aSmp = LabSmp.a;
let bSmp = LabSmp.b;
let cSmp = Math.sqrt(aSmp * aSmp + bSmp * bSmp);
let cAvg = (cStd + cSmp) / 2;
let G =
0.5 *
(1 -
Math.sqrt(
Math.pow(cAvg, 7) / (Math.pow(cAvg, 7) + Math.pow(25, 7))
));
let apStd = aStd * (1 + G);
let apSmp = aSmp * (1 + G);
let cpStd = Math.sqrt(apStd * apStd + bStd * bStd);
let cpSmp = Math.sqrt(apSmp * apSmp + bSmp * bSmp);
let hpStd =
Math.abs(apStd) + Math.abs(bStd) === 0
? 0
: Math.atan2(bStd, apStd);
hpStd += (hpStd < 0) * 2 * Math.PI;
let hpSmp =
Math.abs(apSmp) + Math.abs(bSmp) === 0
? 0
: Math.atan2(bSmp, apSmp);
hpSmp += (hpSmp < 0) * 2 * Math.PI;
let dL = lSmp - lStd;
let dC = cpSmp - cpStd;
let dhp = cpStd * cpSmp === 0 ? 0 : hpSmp - hpStd;
dhp -= (dhp > Math.PI) * 2 * Math.PI;
dhp += (dhp < -Math.PI) * 2 * Math.PI;
let dH = 2 * Math.sqrt(cpStd * cpSmp) * Math.sin(dhp / 2);
let Lp = (lStd + lSmp) / 2;
let Cp = (cpStd + cpSmp) / 2;
let hp;
if (cpStd * cpSmp === 0) {
hp = hpStd + hpSmp;
} else {
hp = (hpStd + hpSmp) / 2;
hp -= (Math.abs(hpStd - hpSmp) > Math.PI) * Math.PI;
hp += (hp < 0) * 2 * Math.PI;
}
let Lpm50 = Math.pow(Lp - 50, 2);
let T =
1 -
0.17 * Math.cos(hp - Math.PI / 6) +
0.24 * Math.cos(2 * hp) +
0.32 * Math.cos(3 * hp + Math.PI / 30) -
0.2 * Math.cos(4 * hp - (63 * Math.PI) / 180);
let Sl = 1 + (0.015 * Lpm50) / Math.sqrt(20 + Lpm50);
let Sc = 1 + 0.045 * Cp;
let Sh = 1 + 0.015 * Cp * T;
let deltaTheta =
((30 * Math.PI) / 180) *
Math.exp(-1 * Math.pow(((180 / Math.PI) * hp - 275) / 25, 2));
let Rc =
2 *
Math.sqrt(Math.pow(Cp, 7) / (Math.pow(Cp, 7) + Math.pow(25, 7)));
let Rt = -1 * Math.sin(2 * deltaTheta) * Rc;
return Math.sqrt(
Math.pow(dL / (Kl * Sl), 2) +
Math.pow(dC / (Kc * Sc), 2) +
Math.pow(dH / (Kh * Sh), 2) +
(((Rt * dC) / (Kc * Sc)) * dH) / (Kh * Sh)
);
};
};
/*
CMC (l:c) difference formula
References:
https://en.wikipedia.org/wiki/Color_difference#CMC_l:c_(1984)
http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CMC.html
*/
const differenceCmc = (l = 1, c = 1) => {
let lab = converter('lab65');
/*
Comparte two colors:
std - standard (first) color
smp - sample (second) color
*/
return (std, smp) => {
// convert standard color to Lab
let LabStd = lab(std);
let lStd = LabStd.l;
let aStd = LabStd.a;
let bStd = LabStd.b;
// Obtain hue/chroma
let cStd = Math.sqrt(aStd * aStd + bStd * bStd);
let hStd = Math.atan2(bStd, aStd);
hStd = hStd + 2 * Math.PI * (hStd < 0);
// convert sample color to Lab, obtain LCh
let LabSmp = lab(smp);
let lSmp = LabSmp.l;
let aSmp = LabSmp.a;
let bSmp = LabSmp.b;
// Obtain chroma
let cSmp = Math.sqrt(aSmp * aSmp + bSmp * bSmp);
// lightness delta squared
let dL2 = Math.pow(lStd - lSmp, 2);
// chroma delta squared
let dC2 = Math.pow(cStd - cSmp, 2);
// hue delta squared
let dH2 = Math.pow(aStd - aSmp, 2) + Math.pow(bStd - bSmp, 2) - dC2;
let F = Math.sqrt(Math.pow(cStd, 4) / (Math.pow(cStd, 4) + 1900));
let T =
hStd >= (164 / 180) * Math.PI && hStd <= (345 / 180) * Math.PI
? 0.56 + Math.abs(0.2 * Math.cos(hStd + (168 / 180) * Math.PI))
: 0.36 + Math.abs(0.4 * Math.cos(hStd + (35 / 180) * Math.PI));
let Sl = lStd < 16 ? 0.511 : (0.040975 * lStd) / (1 + 0.01765 * lStd);
let Sc = (0.0638 * cStd) / (1 + 0.0131 * cStd) + 0.638;
let Sh = Sc * (F * T + 1 - F);
return Math.sqrt(
dL2 / Math.pow(l * Sl, 2) +
dC2 / Math.pow(c * Sc, 2) +
dH2 / Math.pow(Sh, 2)
);
};
};
/*
HyAB color difference formula, introduced in:
Abasi S, Amani Tehran M, Fairchild MD.
"Distance metrics for very large color differences."
Color Res Appl. 2019; 1–16.
https://doi.org/10.1002/col.22451
PDF available at:
http://markfairchild.org/PDFs/PAP40.pdf
*/
const differenceHyab = () => {
let lab = converter('lab65');
return (std, smp) => {
let LabStd = lab(std);
let LabSmp = lab(smp);
let dL = LabStd.l - LabSmp.l;
let dA = LabStd.a - LabSmp.a;
let dB = LabStd.b - LabSmp.b;
return Math.abs(dL) + Math.sqrt(dA * dA + dB * dB);
};
};
/*
"Measuring perceived color difference using YIQ NTSC
transmission color space in mobile applications"
by Yuriy Kotsarenko, Fernando Ramos in:
Programación Matemática y Software (2010)
Available at:
http://www.progmat.uaem.mx:8080/artVol2Num2/Articulo3Vol2Num2.pdf
*/
const differenceKotsarenkoRamos = () =>
differenceEuclidean('yiq', [0.5053, 0.299, 0.1957]);
/*
ΔE_ITP, as defined in Rec. ITU-R BT.2124:
https://www.itu.int/rec/R-REC-BT.2124/en
*/
const differenceItp = () =>
differenceEuclidean('itp', [518400, 129600, 518400]);
export {
differenceHueChroma,
differenceHueSaturation,
differenceHueNaive,
differenceEuclidean,
differenceCie76,
differenceCie94,
differenceCiede2000,
differenceCmc,
differenceHyab,
differenceKotsarenkoRamos,
differenceItp
};