Skip to content

Instantly share code, notes, and snippets.

@hail2u
Last active September 11, 2023 05:18
Show Gist options
  • Save hail2u/a1fb620d4826c5b476180ee6285618a5 to your computer and use it in GitHub Desktop.
Save hail2u/a1fb620d4826c5b476180ee6285618a5 to your computer and use it in GitHub Desktop.
Get contrast of colors using APCA (Advanced Perceptual Contrast Algorithm)
// https://github.com/Myndex/SAPC-APCA#the-plain-english-steps-are
// Example:
// const contrast = getAPCAContrast("rgb(255, 255, 255)", "rgb(136, 136, 136)");
// This returns `66.89346308821438` (66.893%)
// SAPC-APCA README says:
// > #888 vs #fff • 66.89346308821438
// 80% means 7:1 in WCAG 2.0
// 60% means 4.5:1 in WCAG 2.0
// Web UI: https://hail2u.net/pub/test/702.html
const linearize = (val) => (val / 255.0) ** 2.4;
const clampLuminance = (luminance) => {
const blkThrs = 0.03;
const blkClmp = 1.45;
if (luminance > blkThrs) {
return luminance;
}
return Math.abs(blkThrs - luminance) ** blkClmp + luminance;
};
const getLuminance = (color) => {
const [red, green, blue] = color.match(/\d+/gu);
const y =
0.2126729 * linearize(red) +
0.7151522 * linearize(green) +
0.072175 * linearize(blue);
return clampLuminance(y);
};
const getContrast = (background, foreground) => {
const deltaYmin = 0.0005;
const scale = 1.25;
const backgroundLuminance = getLuminance(background);
const foregroundLuminance = getLuminance(foreground);
if (Math.abs(backgroundLuminance - foregroundLuminance) < deltaYmin) {
return 0.0;
}
if (backgroundLuminance > foregroundLuminance) {
return (backgroundLuminance ** 0.55 - foregroundLuminance ** 0.58) * scale;
}
if (backgroundLuminance < foregroundLuminance) {
return (backgroundLuminance ** 0.62 - foregroundLuminance ** 0.57) * scale;
}
return 0.0;
};
const scaleContrast = (contrast) => {
const loClip = 0.001;
const loConThresh = 0.078;
const loConFactor = 1 / loConThresh;
const loConOffset = 0.06;
const absContrast = Math.abs(contrast);
if (absContrast < loClip) {
return 0.0;
}
if (absContrast <= loConThresh) {
return contrast - contrast * loConFactor * loConOffset;
}
if (contrast > loConThresh) {
return contrast - loConOffset;
}
if (contrast < -loConThresh) {
return contrast + loConOffset;
}
return 0.0;
};
const getAPCAContrast = (background, foreground) => {
const contrast = getContrast(background, foreground);
const scaledContrast = scaleContrast(contrast);
return scaledContrast * 100;
};
@Myndex
Copy link

Myndex commented Jan 9, 2022

Hello @hail2u

I just wanted to let you know there are newer constants than those shown here.

New Constants (March 2021)

        const blkThrs = 0.022;
	const blkClmp = 1.414;

	const scale = 1.14;

	const loClip = 0.001;
	const loConThresh = 0.035991;
	const loConFactor = 27.7847239587675;
	const loConOffset = 0.027;

// And the exponents for the main math:

       if (backgroundLuminance > foregroundLuminance) {
		return (backgroundLuminance ** 0.56 - foregroundLuminance ** 0.57) * scale;
	}

	if (backgroundLuminance < foregroundLuminance) {
		return (backgroundLuminance ** 0.65 - foregroundLuminance ** 0.62) * scale;
	}

If you prefer, here is the current set as objects:

// 0.98G-4g full range version constants:
    
  Exponents =  { mainTRC: 2.4,       normBG: 0.56,       normTXT: 0.57,     revTXT: 0.62,     revBG: 0.65, };
  
  ColorSpace = { sRco: 0.2126729,    sGco: 0.7151522,    sBco: 0.0721750, };
    
  Clamps =     { blkThrs: 0.022,     blkClmp: 1.414,     loClip: 0.001,     deltaYmin: 0.0005, };
        
  Scalers =    { scaleBoW: 1.14,     loBoWthresh: 0.035991,  loBoWfactor: 27.7847239587675,  loBoWoffset: 0.027, 
                 scaleWoB: 1.14,     loWoBthresh: 0.035991,  loWoBfactor: 27.7847239587675,  loWoBoffset: 0.027, };	

NPM Package & More

Also, we now have an NPM package available: npm i apca-w3

And if you have a tool up an running, please feel free to add it to the list here:

Myndex/SAPC-APCA#51

Thank you!

Andy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment