Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggested Contrast Colors Appear to Have Rounding Errors #153

Open
mrwweb opened this issue Jun 10, 2019 · 2 comments
Open

Suggested Contrast Colors Appear to Have Rounding Errors #153

mrwweb opened this issue Jun 10, 2019 · 2 comments

Comments

@mrwweb
Copy link

mrwweb commented Jun 10, 2019

I just used tota11y to find a couple contrast errors. I liked the suggested fix colors so I used them.

Those suggested combinations were:

As the linked tests from contrast-ratio.com show, those ratios both are 4.49:1, .01 shy of AA-level accessibility for normal text.

Notably, WAVE also rejects this as sufficient and there's even a note about it in their article about contrast:

Important
WCAG requires "at least 4.5:1" contrast, so you cannot round a contrast ratio up to 4.5:1. For example, #777777 is a commonly-used shade of gray with a 4.48:1 contrast ratio. It does not meet the WCAG contrast threshold. [emphasis added]

In order to help tota11y help people strictly pass a11y checks, tota11y should make sure to not round ratios (or round down) when calculating a suggested alternative color.

@Skeletonxf
Copy link

I believe this would be a bug with Accessibility Developer Tools as the suggested colours are generated from there:

https://github.com/Khan/tota11y/blob/42118757fe53605a87e23140db4cd60920b896d4/plugins/contrast/index.js#L33

@Skeletonxf
Copy link

Skeletonxf commented Jun 12, 2019

I believe the problem lies in converting between color formats in Accessibility Developer Tools:

https://github.com/GoogleChrome/accessibility-developer-tools/blob/3d7c96bf34b3146f40aeb2720e0927f221ad8725/src/js/Color.js#L275-L300

for (var desiredLabel in desiredContrastRatios) {
        var desiredContrast = desiredContrastRatios[desiredLabel];

        var desiredFgLuminance = axs.color.luminanceFromContrastRatio(bgLuminance, desiredContrast + 0.02, fgLuminanceIsHigher);
        if (desiredFgLuminance <= 1 && desiredFgLuminance >= 0) {
            var newFgColor = axs.color.translateColor(fgYCbCr, desiredFgLuminance);
            var newContrastRatio = axs.color.calculateContrastRatio(newFgColor, bgColor);
            var suggestedColors = {};
            suggestedColors.fg = /** @type {!string} */ (axs.color.colorToString(newFgColor));
            suggestedColors.bg = /** @type {!string} */ (axs.color.colorToString(bgColor));
            suggestedColors.contrast = /** @type {!string} */ (newContrastRatio.toFixed(2));
            colors[desiredLabel] = /** @type {axs.color.SuggestedColors} */ (suggestedColors);
            continue;
        }

        var desiredBgLuminance = axs.color.luminanceFromContrastRatio(fgLuminance, desiredContrast + 0.02, !fgLuminanceIsHigher);
        if (desiredBgLuminance <= 1 && desiredBgLuminance >= 0) {
            var newBgColor = axs.color.translateColor(bgYCbCr, desiredBgLuminance);
            var newContrastRatio = axs.color.calculateContrastRatio(fgColor, newBgColor);
            var suggestedColors = {};
            suggestedColors.bg = /** @type {!string} */ (axs.color.colorToString(newBgColor));
            suggestedColors.fg = /** @type {!string} */ (axs.color.colorToString(fgColor));
            suggestedColors.contrast = /** @type {!string} */ (newContrastRatio.toFixed(2));
            colors[desiredLabel] = /** @type {axs.color.SuggestedColors} */ (suggestedColors);
        }
}

These lines called by axs.color.suggestColors convert the suggested color back to RGB with axs.color.translateColor which uses axs.color.fromYCbCrArray

And in this function we have rounding

https://github.com/GoogleChrome/accessibility-developer-tools/blob/3d7c96bf34b3146f40aeb2720e0927f221ad8725/src/js/Color.js#L378-L393

axs.color.fromYCbCrArray = function(yccArray) {
    var rgb = axs.color.multiplyMatrixVector(axs.color.INVERTED_YCC_MATRIX, yccArray);

    var r = rgb[0];
    var g = rgb[1];
    var b = rgb[2];
    var rSRGB = r <= 0.00303949 ? (r * 12.92) : (Math.pow(r, (1/2.4)) * 1.055) - 0.055;
    var gSRGB = g <= 0.00303949 ? (g * 12.92) : (Math.pow(g, (1/2.4)) * 1.055) - 0.055;
    var bSRGB = b <= 0.00303949 ? (b * 12.92) : (Math.pow(b, (1/2.4)) * 1.055) - 0.055;

    var red = Math.min(Math.max(Math.round(rSRGB * 255), 0), 255);
    var green = Math.min(Math.max(Math.round(gSRGB * 255), 0), 255);
    var blue = Math.min(Math.max(Math.round(bSRGB * 255), 0), 255);

    return new axs.color.Color(red, green, blue, 1);
};

axs.color.translateColor is supposed to pick the closest color per its documentation so what it does is not really wrong, but its use in axs.color.suggestColors is probably introducing that rounding error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants