I want to draw a stack of almost transparent white rectangles that overlap each other.
Each rectangle has an opacity of 0.01
I have 100 rectangles overlaping, I expect the output result to be the sum of all the opacity. In other term, I expect the result to be white without any opacity.
But it's not the case.
Here is a minimalist code to illustrate the problem
let canvas = document.createElement("canvas");
canvas.width = canvas.height = 512;
let ctx = canvas.getContext("2d");
ctx.fillStyle = "#000000";
ctx.fillRect(0,0,512,512);
ctx.fillStyle = "#ffffff";
ctx.globalAlpha = 0.01;
for(let i=0;i<100;i++){
let n = i*3;
ctx.fillRect(n,n,512-n,512-n);
}
document.body.appendChild(canvas);
Here is the result
And here is the jsfiddle https://jsfiddle.net/hsqpno04/
Any help is very welcome !
To do alpha blending of two RGBA colors (assuming normal blending and composite modes), the general formula is
out = alpha * new + (1 - alpha) * old
But out
must be an integer (in the range 0~255), so we have to apply a rounding on top of that (I guess the rounding could depend on implementations and how they store color values).
If we take a less dramatic alpha
of 0.1, and keep white so new
is 255 (white is 255, 255, 255), and old is 0 (transparent),
at first step we'll have
out = round( 0.1 * 255 + (1 - 0.1) * 0 );
// => 26
following steps we'll have
out = round( 0.1 * 255 + (1 - 0.1) * 26 );
// => 49
out = round( 0.1 * 255 + (1 - 0.1) * 49 );
// => 70
out = round( 0.1 * 255 + (1 - 0.1) * 70 );
// => 89
[...] // a few iterations later
// => 250
out = round( 0.1 * 255 + (1 - 0.1) * 250 );
// => 251
out = round( 0.1 * 255 + (1 - 0.1) * 251 );
// => 251
out = round( 0.1 * 255 + (1 - 0.1) * 251 );
// => 251
Once you reach this 251, 251, 251
color value, no matter the number of new layer you'll add, it won't change the color value anymore, and thus you won't ever be able to reach a full opaque color by layering semi-opaque versions of that color if their opacity is less than 0.5.
Your 0.01
value will take more iterations to reach this stabilized position (153 vs 38), but it will achieve it at a lower value (206).
round( 0.01 * 255 + (1 - 0.01) * 206 )
// => 206
Note that colors are generally stored premultiplied by their alpha, which may add some rounding errors in these numbers.