HtmlCanvas + globalAlpha + 重叠 = 不正确的输出颜色

HtmlCanvas + globalAlpha + overlap = incorrect output color

我想画一堆相互重叠的几乎透明的白色矩形。

每个矩形的不透明度为 0.01

我有 100 个重叠的矩形,我希望输出结果是所有不透明度的总和。换句话说,我希望结果是白色的,没有任何不透明度。

但事实并非如此

  1. 为什么?
  2. 如何得到我想要的结果?

下面是一个极简代码来说明问题

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); 

这是结果

这是 jsfiddle https://jsfiddle.net/hsqpno04/

非常欢迎任何帮助!

要对两种 RGBA 颜色进行 alpha 混合(假设是正常混合模式和复合模式),一般公式为

out = alpha * new + (1 - alpha) * old

但是 out 必须是一个整数(在 0~255 范围内),所以我们必须在其上进行舍入(我猜舍入可能取决于实现以及它们如何存储颜色值).

如果我们取一个不那么显眼的 alpha 0.1,并保持白色,那么 new 就是 255(白色是 255, 255, 255),旧的是 0(透明),
第一步我们会有

out = round( 0.1 * 255 + (1 - 0.1) * 0 );
// => 26

我们将执行以下步骤

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

一旦你达到这个251, 251, 251颜色值,无论你添加多少新层,它都不会再改变颜色值,因此你永远无法达到如果不透明度小于 0.5,则通过分层该颜色的半透明版本来获得完全不透明的颜色。

您的 0.01 值将需要更多迭代才能达到此稳定位置(153 对 38),但它会以较低的值 (206) 实现。

round( 0.01 * 255 + (1 - 0.01) * 206 )
// => 206

请注意,颜色通常在存储时预先乘以它们的 alpha,这可能会在这些数字中添加一些舍入误差。