Reputation: 314
I have a design system with many, many colors. I have created a table of colors where all colors are mixed with every other color and color contrast ratio (CCR) of each combination is output. The goal is to flag inaccessible color combinations.
I am using Chroma.js to manipulate the colors and output the CCRs. It works brilliantly with the bulk of my HSL-defined colors.
The trouble comes when my design system uses a color with an alpha channel. Determining how a pair of colors performs with CCR when one or both of them are transparent is problematic. I am trying a few different things to mix or blend a HSLA color with white and then run the contrast function on that. Here is a snippet of what I am doing:
// where either foreground or background has an alpha value present less than 1
var background = chroma.mix(background, '#fff', 1, 'lab').css();
var foreground = chroma.mix(foreground, background, 1, 'lab').css();
var ccr = chroma.contrast(foreground, background);
// lab gets the closest but not the same as the way CSS overlays colors
The results are visualized with this graphic I put together. Left are two colors with an overlay of them in the middle. In Adobe Illustrator, I used 25% transparency on the tan color and a "normal" blend mode. I did the same in CSS and then screen-shotted it, then measured the resulting color mix in Photoshop. To the right are the outputs of Chroma color functions:
Having tried what I understand to be the options in Chroma.js, I am wondering what else I can try to get my results closer to the browser output so that my CCR tests will be accurate. Thanks all.
Thanks to the accepted answer from @GrahamRitchie, my output table now looks like this. The little "composite" labels show the colors that were produced by these functions, while the main output still layers the transparent colors over each other and the background.
Upvotes: 1
Views: 1087
Reputation: 24825
I do not know how to do this with the library you mentioned (Chroma.js) but hopefully a vanilla JavaScript function will help.
Please note that the below function always assumes an opaque background colour to work correctly (hence background RGB and foreground RGBA).
If you need to work with 2 colours that both have alpha channels you would run the function on the background colour first (as the foreground colour) with a white background and then combine the two colours.
The function will also combine two RGB colours, simply omit the alpha channel when passing your RGB colour (convertToRGB({r,g,b}, {r,g,b})
)
function convertToRGB(frontRGBA, backgroundRGB){
var rtrn = {};
//allows the function to just accept a front colour and assume the background is a plain white.
backgroundRGB = backgroundRGB || {r:255,g:255,b:255};
//allows a RGB value to be passed in assuming full alpha channel.
frontRGBA.a = frontRGBA.a || 1;
//normalise the alpha channel across the foreground and background.
rtrn.r = ((1 - frontRGBA.a) * backgroundRGB.r) + (frontRGBA.a * frontRGBA.r);
rtrn.g = ((1 - frontRGBA.a) * backgroundRGB.g) + (frontRGBA.a * frontRGBA.g);
rtrn.b = ((1 - frontRGBA.a) * backgroundRGB.b) + (frontRGBA.a * frontRGBA.b);
//just check that we don't end up with a value greater than 255 for any channel.
rtrn.r = (rtrn.r > 255) ? 255 : rtrn.r;
rtrn.g = (rtrn.g > 255) ? 255 : rtrn.g;
rtrn.b = (rtrn.b > 255) ? 255 : rtrn.b;
return rtrn;
}
var backgroundRGB = {r:165,g:193,b:211};
var frontRGBA = {r:210,g:203,b:178,a:0.25};
//used for example
var rgb = convertToRGB(frontRGBA, backgroundRGB);
document.querySelector(".output").style.background = "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")";
document.querySelector(".output").innerHTML = "Output<br/>R:" + rgb.r + "<br/>G:" + rgb.g + "<br/>B:" + rgb.b;
.container div{
width: 200px;
height: 200px;
float: left;
}
.div1{
background: rgba(165,193,211,1);
}
.div2{
background: rgba(210,203,178,0.25);
}
<div class="container">
<div class="div1">Background<br/>R:165<br/>G:193<br/>B:211<br/>A:1</div>
<div class="output">Output</div>
<div class="div2">Foreground<br/>R:210<br/>G:203<br/>B:178<br/>A:0.25</div>
</div>
Upvotes: 2