Last active
April 22, 2024 02:41
-
-
Save mnpenner/70ab4f0836bbee548c71947021f93607 to your computer and use it in GitHub Desktop.
RGB color hex to lightness
This file contains hidden or 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
// based on https://stackoverflow.com/a/56678483/65387 | |
type RGB = [r: number, g: number, b: number] | |
const UNK = 255 / 2 | |
/** | |
* @param hex RGB hex string like "#CCCFDB" | |
* @returns RGB tuple in [0-255] | |
*/ | |
function hexToRgb(hex: string): RGB { | |
const h = String(hex).replace(/^#/, '') | |
if(h.length === 3) { | |
return [ | |
parseRadix(h[0] + h[0], 16, UNK), | |
parseRadix(h[1] + h[1], 16, UNK), | |
parseRadix(h[2] + h[2], 16, UNK), | |
] | |
} | |
if(h.length === 6) { | |
return [ | |
parseRadix(h.slice(0, 2), 16, UNK), | |
parseRadix(h.slice(2, 4), 16, UNK), | |
parseRadix(h.slice(4, 6), 16, UNK), | |
] | |
} | |
return [UNK, UNK, UNK] | |
} | |
function sRGBtoLin(colorChannel: number) { | |
// Send this function a decimal sRGB gamma encoded color value | |
// between 0.0 and 1.0, and it returns a linearized value. | |
if(colorChannel <= 0.04045) { | |
return colorChannel / 12.92 | |
} | |
return Math.pow((colorChannel + 0.055) / 1.055, 2.4) | |
} | |
/** | |
* @param r Red, [0-1] | |
* @param g Green, [0-1] | |
* @param b Blue, [0-1] | |
* @returns Luminance, [0-1] | |
*/ | |
function rgbToY(r: number, g: number, b: number) { | |
return 0.2126 * sRGBtoLin(r) + 0.7152 * sRGBtoLin(g) + 0.0722 * sRGBtoLin(b) | |
} | |
/** | |
* Luminance to perceived lightness. | |
* | |
* @param Y Luminance, [0-1] | |
*/ | |
function YtoLstar(Y: number) { | |
// Send this function a luminance value between 0.0 and 1.0, | |
// and it returns L* which is "perceptual lightness" | |
if(Y <= (216 / 24389)) { // The CIE standard states 0.008856 but 216/24389 is the intent for 0.008856451679036 | |
return Y * (24389 / 27) // The CIE standard states 903.3, but 24389/27 is the intent, making 903.296296296296296 | |
} | |
return Math.pow(Y, (1 / 3)) * 116 - 16 | |
} | |
/** | |
* Calculate perceived lightness from RGB hex string. | |
* | |
* @param rgb RGB hex string like "#CCCFDB" | |
* @returns Lightness value, [0-100]. | |
*/ | |
export function rgbHexToLightness(rgb: string) { | |
const [r, g, b] = hexToRgb(rgb) | |
return YtoLstar(rgbToY(r / 255, g / 255, b / 255)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nice!
You might be interested in my contrast extension for L*, the DeltaPhiStar Method which is a general purpose contrast metric for high spatial frequency stimuli (i.e. text against a background).
deltaPhiStar is part of the research for APCA so the scaling of
* 1.414 - 40
is designed to roughly align deltaPhiStar with APCA Lc values.Thank you for reading,
Andy
Andrew Somers
Color Scientist @ Myndex Research
Inventor of APCA, deltaPhiStar, SACAM