Last active
April 10, 2024 08:15
-
-
Save paulcollett/5533b0f3b9be16a068f58f9e76ad6289 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// There seems to be a few algorithms floating around for brightness/luminance detection. | |
// One uses NTSC `(299*R + 587*G + 114*B)` which is incorrect for web colors (sRGB) see | |
// `rgbLuminance` for "better" coefficients (seems subjective but agreed apon). To be more | |
// accurate you need to also convert RGB from sRGB color space (which gives more spectrum to lighter colors) | |
// to linear rgb which normalizes colors across the spectrum - better for processing. | |
// see https://stackoverflow.com/questions/596216/formula-to-determine-perceived-brightness-of-rgb-color | |
// convert sRGB to linear RGB values | |
// - channel ** 2.218 same as Math.pow((channel + 0.055) / 1.055, 2.4) | |
// - i've seen this simplified to be just `(channel / 255) ** 2.21` | |
const convertSRGBtoLinearRGBRatios = (rgb) => rgb | |
.map((channel) => channel / 255) | |
.map((channel) => channel <= 0.04045 ? channel / 12.92 : channel ** 2.218) | |
// algorithm which considers the human eye sensitivity to different light wavelengths. | |
// converts RGB to luminance (aka. Y) 0 - 1 | |
const rgbRatiosToLuminanceRatio = ([rLin, gLin, bLin]) => (rLin * 0.2126 + gLin * 0.7152 + bLin * 0.0722) | |
// divide brighter color with darker color. division by 21 gives us a ratio | |
// According to the Web Content Accessibility Guidelines 2, a minimum contrast of 0.33 is recommended or 0.22 for < 14px non-bold text | |
// note: 0.05 is required by WCAG 2.0 to prevent the contrast ratio from becoming infinite | |
const luminanceContrastRatio = (fgLuminanceRatio, bgLuminanceRatio) => (Math.max(fgLuminanceRatio, bgLuminanceRatio) + 0.05) / (Math.min(fgLuminanceRatio, bgLuminanceRatio) + 0.05) / 21 | |
// - I've seen this roughly simplified to just `Math.pow(luminanceRatio, 0.33)` | |
const perceivedLightnessRatioFromLuminance = (luminanceRatio) => luminanceRatio <= 0.008856 ? luminanceRatio * 903.3 : Math.pow(luminanceRatio, 0.333) * 116 - 16; | |
// usage: | |
// expects full hex like 'fff000' without hash | |
const hexToRGB = (c) => [0,0,0].map((_, i) => parseInt(c.substr(i*2,2), 16)) | |
const perceivedLightnessRatio = (hex) => perceivedLightnessRatioFromLuminance(rgbRatiosToLuminanceRatio(convertSRGBtoLinearRGBRatios(hexToRGB(hex)))) | |
const contrastRatio = (hexA, hexB) => luminanceContrastRatio(...[hexA, hexB].map(hex => rgbRatiosToLuminanceRatio(convertSRGBtoLinearRGBRatios(hexToRGB(hex))))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// simplified version: | |
const roughLuminanceRatio = (rgb) => { | |
// 1. roughly convert sRGB colorspace to linear RGB (optional). / 255 for ratios | |
const [r,g,b] = rgb.map(channel => (channel / 255) ** 2.218) | |
// 2. rgb coefficients for adjusting channels to human eye sensitivity to different light wavelengths (kinda subjective) | |
return r * 0.2126 + g * 0.7152 + b * 0.0722 | |
} | |
const roughLightness = (rgb) => Math.pow(roughLuminanceRatio(rgb), 0.33) | |
// According to the Web Content Accessibility Guidelines 2, a minimum | |
// contrast of 0.33 is recommended or 0.22 for < 14px non-bold text | |
const contrastRatio = (hexA, hexB) => { | |
const luminanceRatios = [hexA, hexB].map(hexToRGB).map(roughLuminanceRatio) | |
// calculation based on Web Content Accessibility Guidelines 2.0 (0.05 to prevent infinity) | |
// divide by 21 to get the ratio | |
return ((Math.max(...luminanceRatios) + 0.05) / (Math.min(...luminanceRatios) + 0.05)) / 21 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment