chroma-js
Version:
JavaScript library for color conversions
69 lines (62 loc) • 2.1 kB
JavaScript
import Color from '../Color.js';
import mix from '../generator/mix.js';
/**
* @license
*
* The APCA contrast prediction algorithm is based of the formulas published
* in the APCA-1.0.98G specification by Myndex. The specification is available at:
* https://raw.githubusercontent.com/Myndex/apca-w3/master/images/APCAw3_0.1.17_APCA0.0.98G.svg
*
* Note that the APCA implementation is still beta, so please update to
* future versions of chroma.js when they become available.
*
* You can read more about the APCA Readability Criterion at
* https://readtech.org/ARC/
*/
// constants
const W_offset = 0.027;
const P_in = 0.0005;
const P_out = 0.1;
const R_scale = 1.14;
const B_threshold = 0.022;
const B_exp = 1.414;
export default (text, bg) => {
// parse input colors
text = new Color(text);
bg = new Color(bg);
// if text color has alpha, blend against background
if (text.alpha() < 1) {
text = mix(bg, text, text.alpha(), 'rgb');
}
const l_text = lum(...text.rgb());
const l_bg = lum(...bg.rgb());
// soft clamp black levels
const Y_text =
l_text >= B_threshold
? l_text
: l_text + Math.pow(B_threshold - l_text, B_exp);
const Y_bg =
l_bg >= B_threshold ? l_bg : l_bg + Math.pow(B_threshold - l_bg, B_exp);
// normal polarity (dark text on light background)
const S_norm = Math.pow(Y_bg, 0.56) - Math.pow(Y_text, 0.57);
// reverse polarity (light text on dark background)
const S_rev = Math.pow(Y_bg, 0.65) - Math.pow(Y_text, 0.62);
// clamp noise then scale
const C =
Math.abs(Y_bg - Y_text) < P_in
? 0
: Y_text < Y_bg
? S_norm * R_scale
: S_rev * R_scale;
// clamp minimum contrast then offset
const S_apc = Math.abs(C) < P_out ? 0 : C > 0 ? C - W_offset : C + W_offset;
// scale to 100
return S_apc * 100;
};
function lum(r, g, b) {
return (
0.2126729 * Math.pow(r / 255, 2.4) +
0.7151522 * Math.pow(g / 255, 2.4) +
0.072175 * Math.pow(b / 255, 2.4)
);
}