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

feat(color-contrast): add support for CSS mix-blend-mode #3226

Merged
merged 26 commits into from
Dec 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
cf98099
add mix-blend-mode functions
Zidious Oct 20, 2021
ea849ab
implement some blend functions and tests
Zidious Oct 20, 2021
fe9d0fb
fix color-dodge and add test
Zidious Oct 20, 2021
162a801
fix color-burn and add test
Zidious Oct 20, 2021
01c45cc
clean up
Zidious Oct 20, 2021
9dceb16
fix hard-light add unit test
Zidious Oct 21, 2021
1b7f57c
add overlay blend-mode and unit test
Zidious Oct 21, 2021
80a172c
tests
Zidious Oct 22, 2021
3ec244b
typo
Zidious Oct 22, 2021
bdf8068
fix tests
Zidious Oct 22, 2021
71281c0
fix flattenColor function
straker Nov 1, 2021
06ea998
better output for blending integration test
straker Nov 1, 2021
1f48a30
better output for blending integration test
straker Nov 1, 2021
ddb0153
Merge branch 'develop' of https://github.com/dequelabs/axe-core into …
Zidious Nov 1, 2021
801cd00
Merge branch 'mix-blend-mode' of https://github.com/dequelabs/axe-cor…
Zidious Nov 1, 2021
70788b5
position each group so they don't affect one another
straker Nov 1, 2021
5befafe
fix blending background white
straker Nov 1, 2021
2d0723e
revert playground
straker Nov 1, 2021
3375f8c
Merge branch 'develop' of https://github.com/dequelabs/axe-core into …
Zidious Nov 12, 2021
ded0c04
Zidious Nov 12, 2021
447348d
revert playground
Zidious Nov 12, 2021
616b6d8
Merge branch 'mix-blend-mode' of https://github.com/dequelabs/axe-cor…
Zidious Nov 12, 2021
6df3918
fix backgroundColor
Zidious Nov 12, 2021
67ca073
fix blending integration tests
Zidious Nov 12, 2021
12198fd
add skip for IE11 as blending is not supported
Zidious Nov 12, 2021
0ca0b4f
typo
Zidious Nov 12, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 75 additions & 10 deletions lib/commons/color/flatten-colors.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,66 @@
import Color from './color';

// clamp a value between two numbers (inclusive)
function clamp(value, min, max) {
return Math.min(Math.max(min, value), max);
}

// how to combine background and foreground colors together when using
// the CSS property `mix-blend-mode`. Defaults to `normal`
// @see https://www.w3.org/TR/compositing-1/#blendingseparable
const blendFunctions = {
normal(Cb, Cs) {
return Cs;
},
multiply(Cb, Cs) {
// @see https://www.w3.org/TR/compositing-1/#blendingmultiply
return Cs * Cb;
},
screen(Cb, Cs) {
// @see https://www.w3.org/TR/compositing-1/#blendingscreen
return Cb + Cs - Cb * Cs;
},
overlay(Cb, Cs) {
// @see https://www.w3.org/TR/compositing-1/#blendingoverlay
return this['hard-light'](Cs, Cb);
},
darken(Cb, Cs) {
// @see https://www.w3.org/TR/compositing-1/#blendingdarken
return Math.min(Cb, Cs);
},
lighten(Cb, Cs) {
// @see https://www.w3.org/TR/compositing-1/#blendinglighten
return Math.max(Cb, Cs);
},
'color-dodge'(Cb, Cs) {
// @see https://www.w3.org/TR/compositing-1/#blendingcolordodge
return Cb === 0 ? 0 : Cs === 1 ? 1 : Math.min(1, Cb / (1 - Cs));
},
'color-burn'(Cb, Cs) {
// @see https://www.w3.org/TR/compositing-1/#blendingcolorburn
return Cb === 1 ? 1 : Cs === 0 ? 0 : 1 - Math.min(1, (1 - Cb) / Cs);
},
'hard-light'(Cb, Cs) {
// @see https://www.w3.org/TR/compositing-1/#blendinghardlight

return Cs <= 0.5 ? this.multiply(Cb, 2 * Cs) : this.screen(Cb, 2 * Cs - 1);
},
'soft-light'(Cb, Cs) {
// @see https://www.w3.org/TR/compositing-1/#blendingsoftlight
if (Cs <= 0.5) {
return Cb - (1 - 2 * Cs) * Cb * (1 - Cb);
} else {
const D = Cb <= 0.25 ? ((16 * Cb - 12) * Cb + 4) * Cb : Math.sqrt(Cb);
return Cb + (2 * Cs - 1) * (D - Cb);
}
},
difference(Cb, Cs) {
// @see https://www.w3.org/TR/compositing-1/#blendingdifference
return Math.abs(Cb - Cs);
},
exclusion(Cb, Cs) {
// @see https://www.w3.org/TR/compositing-1/#blendingexclusion
return Cb + Cs - 2 * Cb * Cs;
}
};

Expand All @@ -19,12 +74,12 @@ const blendFunctions = {
// @see https://www.w3.org/TR/compositing-1/#blending
// @see https://ciechanow.ski/alpha-compositing/
function simpleAlphaCompositing(Cs, αs, Cb, αb, blendMode) {
// RGB color space doesn't have decimal values so we will follow what browsers do and round
// e.g. rgb(255.2, 127.5, 127.8) === rgb(255, 128, 128)
return Math.round(
return (
αs * (1 - αb) * Cs +
αs * αb * blendFunctions[blendMode](Cb, Cs) +
(1 - αs) * αb * Cb
// Note: Cs and Cb values need to be between 0 and 1 inclusive for the blend function
// @see https://www.w3.org/TR/compositing-1/#simplealphacompositing
αs * αb * blendFunctions[blendMode](Cb / 255, Cs / 255) * 255 +
(1 - αs) * αb * Cb
);
}

Expand Down Expand Up @@ -63,12 +118,22 @@ function flattenColors(fgColor, bgColor, blendMode = 'normal') {

// formula: αo = αs + αb x (1 - αs)
// clamp alpha between 0 and 1
const a = Math.max(
0,
Math.min(fgColor.alpha + bgColor.alpha * (1 - fgColor.alpha), 1)
);
const αo = clamp(fgColor.alpha + bgColor.alpha * (1 - fgColor.alpha), 0, 1);

// simple alpha compositing gives premultiplied values, but our Color
// constructor takes unpremultiplied values. So we need to divide the
// final color values by the final alpha
// formula: Co = co / αo
// @see https://www.w3.org/TR/compositing-1/#simplealphacompositing
// @see https://github.com/w3c/fxtf-drafts/issues/440#issuecomment-956418953
//
// RGB color space doesn't have decimal values so we will follow what browsers do and round
// e.g. rgb(255.2, 127.5, 127.8) === rgb(255, 128, 128)
const Cr = Math.round(r / αo);
const Cg = Math.round(g / αo);
const Cb = Math.round(b / αo);

return new Color(r, g, b, a);
return new Color(Cr, Cg, Cb, αo);
}

export default flattenColors;
50 changes: 39 additions & 11 deletions lib/commons/color/get-background-color.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ export default function getBackgroundColor(
) {
let bgColors = getTextShadowColors(elm, { minRatio: shadowOutlineEmMax });
if (bgColors.length) {
bgColors = [bgColors.reduce(flattenShadowColors)];
bgColors = [{ color: bgColors.reduce(flattenShadowColors) }];
}

const elmStack = getBackgroundStack(elm);

// Search the stack until we have an alpha === 1 background
Expand All @@ -53,7 +54,11 @@ export default function getBackgroundColor(
if (bgColor.alpha !== 0) {
// store elements contributing to the br color.
bgElms.push(bgElm);
bgColors.unshift(bgColor);
const blendMode = bgElmStyle.getPropertyValue('mix-blend-mode');
bgColors.unshift({
color: bgColor,
blendMode: normalizeBlendMode(blendMode)
});

// Exit if the background is opaque
return bgColor.alpha === 1;
Expand All @@ -72,14 +77,28 @@ export default function getBackgroundColor(
);
bgColors.unshift(...pageBgs);

// default to white if bgColors is empty
if (bgColors.length === 0) {
return new Color(255, 255, 255, 1);
}
// Mix the colors together. Colors must be mixed in bottom up
// order (background to foreground order) to produce the correct
// result.
// @see https://github.com/dequelabs/axe-core/issues/2924
var colors = bgColors.reduce((bgColor, fgColor) => {
return flattenColors(fgColor, bgColor);
const blendedColor = bgColors.reduce((bgColor, fgColor) => {
return flattenColors(
fgColor.color,
bgColor.color instanceof Color ? bgColor.color : bgColor,
fgColor.blendMode
);
});
return colors;

// default page background is white which must be mixed last
// @see https://www.w3.org/TR/compositing-1/#pagebackdrop
return flattenColors(
blendedColor.color instanceof Color ? blendedColor.color : blendedColor,
new Color(255, 255, 255, 1)
);
}

/**
Expand All @@ -99,6 +118,9 @@ function elmPartiallyObscured(elm, bgElm, bgColor) {
return obscured;
}

function normalizeBlendMode(blendmode) {
return !!blendmode ? blendmode : undefined;
}
/**
* Get the page background color.
* @private
Expand Down Expand Up @@ -131,24 +153,30 @@ function getPageBackgroundColors(elm, stackContainsBody) {
const bodyBgColor = getOwnBackgroundColor(bodyStyle);
const bodyBgColorApplies =
bodyBgColor.alpha !== 0 && visuallyContains(elm, body);

if (
(bodyBgColor.alpha !== 0 && htmlBgColor.alpha === 0) ||
(bodyBgColorApplies && bodyBgColor.alpha !== 1)
) {
pageColors.unshift(bodyBgColor);
pageColors.unshift({
color: bodyBgColor,
blendMode: normalizeBlendMode(
bodyStyle.getPropertyValue('mix-blend-mode')
)
});
}

if (
htmlBgColor.alpha !== 0 &&
(!bodyBgColorApplies || (bodyBgColorApplies && bodyBgColor.alpha !== 1))
) {
pageColors.unshift(htmlBgColor);
pageColors.unshift({
color: htmlBgColor,
blendMode: normalizeBlendMode(
htmlStyle.getPropertyValue('mix-blend-mode')
)
});
}
}

// default page background is white
pageColors.unshift(new Color(255, 255, 255, 1));

return pageColors;
}
Loading