0

I am currently calculating the contrast of two foreground colors and a background color to programmatically decide if the foreground color should be white or black.

The formula for the contrast ratio is defined as

(L1 + 0.05) / (L2 + 0.05)

Where L1 is the relative luminance value of the lighter color and L2 is the relative luminance value of the darker color.
WCAG specifies that the optimal ratio for large text is 4.5:1 and for normal text 7:1.

I can't find an explanation as to why both values need to be incremented by 0.05...
Is it simply used to prevent dividing by 0 as the relative luminance for black is exactly 0?

Here are three reference sources:

  1. https://www.w3.org/TR/WCAG/#contrast-ratio (search for '0.05' using CTRL + F)
  2. https://www.accessibility-developer-guide.com/knowledge/colours-and-contrast/how-to-calculate/#the-formula
  3. https://planetcalc.com/7779/

Additionally, here is some code, have fun:

private static readonly Func<Color, double> CalcRelativeLuma = (c) =>
{
    // Convert 8-Bit RGB to sRGB.
    double R = c.R / 255.0;
    double G = c.G / 255.0;
    double B = c.B / 255.0;
    
    // Calculate relative luminance for sRGB values.
    R = R <= 0.03928 ? R / 12.92 : Math.Pow(((R + 0.055) / 1.055), 2.4);
    G = G <= 0.03928 ? G / 12.92 : Math.Pow(((G + 0.055) / 1.055), 2.4);
    B = B <= 0.03928 ? B / 12.92 : Math.Pow(((B + 0.055) / 1.055), 2.4);

    return 0.2126 * R + 0.7152 * G + 0.0722 * B;         
};


private Color GetHighContrastColor(Color background)
{
    // Calculate relative luminance of background.
    double l1 = CalcRelativeLuma(background) + 0.05;
    
    // Set contrast colors and calculate relative 
    // luminance of them.
    Color darkContrast = Color.FromArgb(36, 36, 36);
    Color lightContrast = Color.FromArgb(239, 239, 239);
    double l2 = CalcRelativeLuma(darkContrast) + 0.05;
    double l3 = CalcRelativeLuma(lightContrast) + 0.05;
    
    // Calculate contrast ratio for dark and light contrast
    // in regards to the background color.
    double ratio_dark = Math.Max(l1, l2) / Math.Min(l1, l2);
    double ratio_light = Math.Max(l1, l3) / Math.Min(l1, l3);
    
    // Choose which of the contrast colors to use.
    double highContrastRation = Math.Max(ratio_dark, ratio_light);
    Color contrast = Color.Transparent;
    if (highContrastRation == ratio_dark) {
        contrast = darkContrast;
    }
    else {
        contrast = lightContrast;
    }
    return contrast;
}
  • That's more of a "curiosity" question and not a programming question so is probably not appropriate for stackoverflow. But I found it funny that you were wondering about the 0.05 in the simple contrast formula but didn't ask why dividing each RGB value by 255 then checks against 0.03928. Or why the ratios of 21% for red, 71% for green, and 7% for blue. Or why you take each RGB value and add 0.055 then divide by 1.055 and then raise that to the power of 2.4. – slugolicious Mar 06 '23 at 00:39
  • @slugolicious Dividing the 8-bit RGB values by 255 linearizes them and converts them to sRGB which has a range of 0 to 1. The 0.03928 stems from an exponential curve from the conversion of XYZ to sRGB. The slope of 1/12.92 is roughly 0.03928. Those two values are used when computing the transfer function. The value 0.055 (A) and the exponent 2.4 (Gamma) are used so the curve closely represents a gamma-2.2 curve, which in return gives us 0.03928 for X and 12.9232 for Phi which can be used to describe sRGB conversion. –  Mar 06 '23 at 07:39
  • @slugolicious The part `Math.Pow(((R + 0.055) / 1.055), 2.4)` is a quantization as the instantaneous gamma varies from 1 in the linear section to 2.4 at the maximum intensity. The values of 0.2126, 0.7152 and 0.0722 are the results of the luminous efficiency function for the XYZ color space. Other color spaces use different values. The YIQ color space uses 0.299, 0.587 and 0.114 for example. –  Mar 06 '23 at 07:39
  • Most people ask about the entire formula and I was just wondering why you asked about the final calculation. A simple "I already understand the rest of the formula" answer was all that was needed, but your details will probably help someone in the future. I already understand the formula. This was also just a "heads up" that a moderator might consider your question a non-programming question and off topic for stackoverflow. I think it's an interesting topic but perhaps post to https://webaim.org/discussion/ – slugolicious Mar 06 '23 at 16:13
  • @slugolicious Thanks for the link! You are right, this is off-topic. I'll try the page you provided –  Mar 06 '23 at 19:22

0 Answers0