为什么在 HTML5 Canvas 上重复绘制半透明填充不能完全覆盖它?

Why does repeatedly drawing translucent fills on HTML5 Canvas not cover it completely?

这到底是怎么回事?

为什么混合在 255,214,214,255 处突然停止?这是预期的行为吗?

我以为它最终会达到 255,255,255,255,但事实并非如此。它甚至没有接近。从一开始的红色矩形永远不会消失成白色,它永远保持颜色 255,214,214,255

const ctx = document.querySelector('canvas').getContext('2d');

// draw "red"
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 300, 200);

function loop() {
    
    // log new colors
    if (getCurrentColor() !== loop.lastColor) {
        console.log(loop.lastColor = getCurrentColor());
    }
    
    // draw "transparent white" repeatedly
    ctx.fillStyle = 'rgba(255,255,255,0.01)';
    ctx.fillRect(0, 0, 300, 200);
    
    // schedule next iteration
    requestAnimationFrame(loop);
}
loop(); // start loop

function getCurrentColor() {
    return ctx.getImageData(0, 0, 1, 1).data.join(',');
}
<!DOCTYPE html>
<html>
    <head></head>
    <body>
        <canvas width="300" height="200"></canvas>
    </body>
</html>

非常感谢解释和解决方案! :P

最后这不是完全白色的原因很简单:您在某些东西之上添加了一个非完全透明的覆盖层。无论您多久这样做一次,您都永远不会达到 100%,因为这是一种渐近线行为。只需图像如下

我有一张 100% 的红纸。我会用 50% 透明的白色玻璃覆盖它。 然后你会看到 100% 的 50% => 50%。 如果我再这样做,你的逻辑会说我有 50% + 50% = 100% 但事实并非如此。 你有 50% 的红色和 50% 的白色玻璃,并用另一个 50% 的白色玻璃覆盖它。这使得玻璃呈现 25% 的红色和 75% 的白色。

如果重复几次,红色的数量会减少但永远不会达到 0%。

解决方法是再次绘制红色矩形,但使用更高的不透明度。如果你到达 ctx.fillStyle = 'rgba(255,255,255,1.00)'; 它将完全覆盖红色。

示例代码如下:

const ctx = document.querySelector('canvas').getContext('2d');

// draw "red"
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 300, 200);

// draw "transparent white" repeatedly
let lastColor;
let percentDone=0;
render();

function render() {
    if (getCurrentColor() !== lastColor)
        console.log(lastColor = getCurrentColor());

    percentDone += .005;
    if (percentDone == 1) {percentDone = 1;}
    ctx.fillStyle = 'red';
    ctx.fillRect(0, 0, 300, 200);

    ctx.fillStyle = 'rgba(255,255,255,'+percentDone+')';
    ctx.fillRect(0, 0, 300, 200);
    requestAnimationFrame(render);
}

function getCurrentColor() {
    return ctx.getImageData(0, 0, 1, 1).data.join(',');
}
<canvas width="300" height="200"></canvas>

外部:https://jsfiddle.net/Holger50/51h0yLm6/3/

€编辑 1:

将要添加的透明度颜色与该位置的颜色进行比较,仅考虑颜色差异进行计算,并基于整数除法。如果计算值低于 1,则颜色不会改变,这就是您示例中颜色强度 214 时发生的情况。

我已经尝试了几种方法来找出为什么这会在一个奇怪的步骤上停止。也许以下内容有助于理解问题:如果将初始填充颜色设置为 ctx.fillStyle = 'black'; 并发出以下 ctx.fillStyle = 'rgba(255,100,150,0.01)'; 停止输出为 207,43,129,255.

似乎新的透明度颜色是根据已经存在的值以某种方式计算出来的。如果差异太低,则不会添加颜色。 例如:如果您使用 ctx.fillStyle = 'rgba(10,100,150,0.01),红色部分将永远不会增长,因为内部 10*0.01 小于 1,因此不被考虑(四舍五入为下一个整数)。您还可以在颜色值中看到这一点。红色提高 100。控制台输出每一步提高 1。蓝色提高150,控制台提高2,直到达到值44。之后,它只会以步骤1增长,直到完全停止在129。

我附上了第二个fiddle,你可以看到效果。

const ctx = document.querySelector('canvas').getContext('2d');

// draw "red"
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, 300, 200);

// draw "transparent white" repeatedly
let lastColor;
render();
function render() {
    if (getCurrentColor() !== lastColor)
    console.log(lastColor = getCurrentColor());
    ctx.fillStyle = 'rgba(10,100,150,0.01)';
    ctx.fillRect(0, 0, 300, 200);
    requestAnimationFrame(render);
}

function getCurrentColor() {
    return ctx.getImageData(0, 0, 1, 1).data.join(',');
}
<canvas width="300" height="200"></canvas>

HTML/JS 画布的底层 图形缓冲区 位图 ,它只允许 Integer 颜色值从 0255。这意味着每个渲染操作的输出都必须 rounded!

假设您从颜色值 214 开始。如果你然后混合 214 * 0.99 + 255 * 0.01,理论上你会得到 214.41。但这随后被舍入到 214 以存储在 canvas 位图中!
所以,基本上,你被困在 214(或与此相关的任何其他值,只要你的步数小于 0.5)xD

不幸的是,没有办法用简单的 JavaScript 来规避这个问题,因为你不能改变图形缓冲区架构或 blending/compositing 操作。
但我敢肯定,如果你真的愿意,你完全可以破解它。通过 WebGL API 或类似的东西实现 Float32 图形缓冲区。