javascriptcolorscontrast

Why is my contrast function calculating the contrast incorrectly?


I am trying to create a module that returns the contrast ratio. The intended use is to get a color, and determine whether the contrast against white and black so that I can pick whichever one contrasts more strongly with the color.

I referred to this stack overflow question to help me with it. My version seems to work at first, but when I test it, my results seem to be way off. I compared my output to coolors contrast checker and pasted the result in the comment next to my own result so you can see how far off it is:

// Measure the relative luminance of each RGB value
const reLum = function relativeLuminance(RGB) {
    let newRGB = RGB;
    newRGB /= 255;
    return newRGB <= 0.03928 ?
    newRGB / 12.92 :
    (newRGB + 0.055) / 1.055 ** 2.4;
}

// Measure the luminance of the color
const lum = function luminance(r, g, b) {
    const a = [r, g, b].map(reLum);
    return Number(a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722).toFixed(3);
}

// Convert hex values to RGB
const hexToRGB = function convertHexToRGB(hex) {
    const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result ?
    [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] :
    null;
}

// Measure the contrast between two colors
const contrast = function findContrast(hex1, hex2) {
    const rgb1 = hexToRGB(hex1);
    const rgb2 = hexToRGB(hex2);
    const lum1 = lum(rgb1[0], rgb1[1], rgb1[2]);
    const lum2 = lum(rgb2[0], rgb2[1], rgb2[2]);
    const brightest = Math.max(lum1, lum2);
    const darkest = Math.min(lum1, lum2);
    return (brightest + 0.05) / (darkest + 0.05);
}

// Contrast is 3.16 against white VS coolors 8.31
console.log(`#922626 contrast against white: ${contrast('#922626', '#FFFFFF')}`);  
// Contrast is 6.18 against black VS coolors 2.53
console.log(`#922626 contrast against black: ${contrast('#922626', '#000000')}`);  

// Contrast is 4.23 against white VS coolors 1.24
console.log(`#102a47 contrast against white: ${contrast('#102a47', '#FFFFFF')}`);  
// Contrast is 4.61 against black VS coolors 14.54
console.log(`#102a47 contrast against black: ${contrast('#102a47', '#000000')}`); 

In my second example, white is clearly supposed to have a bigger contrast with #102a47 since black is nearly indistinguishable from it, but my calculation seems to think that it contrasts equally with white & black.

Obviously, there is some kind of flaw in my calculation, but despite checking the math on W3 here and here, I can't find my mistake.


Solution

  • Missing () for exponent in reLum on this line:

    (newRGB + 0.055) / 1.055 ** 2.4;

    should be

    ((newRGB + 0.055) / 1.055) ** 2.4;

    // Measure the relative luminance of each RGB value
    const reLum = function relativeLuminance(RGB) {
        let newRGB = RGB;
        newRGB /= 255;
        return newRGB <= 0.03928 ?
        newRGB / 12.92 :
        ((newRGB + 0.055) / 1.055) ** 2.4;
    }
    
    // Measure the luminance of the color
    const lum = function luminance(r, g, b) {
        const a = [r, g, b].map(reLum);
        return Number(a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722).toFixed(3);
    }
    
    // Convert hex values to RGB
    const hexToRGB = function convertHexToRGB(hex) {
        const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
        return result ?
        [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] :
        null;
    }
    
    // Measure the contrast between two colors
    const contrast = function findContrast(hex1, hex2) {
        const rgb1 = hexToRGB(hex1);
        const rgb2 = hexToRGB(hex2);
        const lum1 = lum(rgb1[0], rgb1[1], rgb1[2]);
        const lum2 = lum(rgb2[0], rgb2[1], rgb2[2]);
        const brightest = Math.max(lum1, lum2);
        const darkest = Math.min(lum1, lum2);
        return (brightest + 0.05) / (darkest + 0.05);
    }
    
    // Contrast is 3.16 against white VS coolors 8.31
    console.log(`#922626 contrast against white: ${contrast('#922626', '#FFFFFF')}`);  
    // Contrast is 6.18 against black VS coolors 2.53
    console.log(`#922626 contrast against black: ${contrast('#922626', '#000000')}`);  
    
    // Contrast is 4.23 against white VS coolors 14.54
    console.log(`#102a47 contrast against white: ${contrast('#102a47', '#FFFFFF')}`);  
    // Contrast is 4.61 against black VS coolors 1.24
    console.log(`#102a47 contrast against black: ${contrast('#102a47', '#000000')}`);