Javascript 如果多次应用,带有alpha的rgba fillStyle不会完全不透明

Javascript 如果多次应用,带有alpha的rgba fillStyle不会完全不透明,javascript,html5-canvas,Javascript,Html5 Canvas,我突然碰到一个奇怪的问题。下面的代码导致图像褪色,因为它被一个半透明的矩形反复透光 但至少在draw()的第10次迭代中图像应该完全透光,因为到那时矩形应该是完全不透明的,对吗?但它实际上从未完全消失 这种效果在Chrome上比在Firefox上更糟糕。但要小心:糟糕的屏幕可能会隐藏这种错误行为=) 我还对jsFiddle做了一个测试 $(function () { var canvas = $("#mycanvas"), ctx = canvas[0].getCon

我突然碰到一个奇怪的问题。下面的代码导致图像褪色,因为它被一个半透明的矩形反复透光

但至少在
draw()的第10次迭代中图像应该完全透光,因为到那时矩形应该是完全不透明的,对吗?但它实际上从未完全消失

这种效果在Chrome上比在Firefox上更糟糕。但要小心:糟糕的屏幕可能会隐藏这种错误行为=)

我还对jsFiddle做了一个测试

$(function () {
var canvas = $("#mycanvas"),
    ctx = canvas[0].getContext("2d"),
    imgUrl = "http://it-runde.de/dateien/2009/august/14/25.png";


var image = new Image();  
image.src = imgUrl ;  
$(image).load(function() {
    ctx.drawImage(image, 0, 0, canvas.width(), canvas.height());
    draw();
});

function draw() {        
    ctx.fillStyle = "rgba(255, 255, 255, 0.1)";
    ctx.fillRect(0, 0, canvas.width(), canvas.height());
    setTimeout(draw, 100);
    
}    
});
人们可能想要达到的效果是,假设一个对象在画布上到处移动,并且已经绘制的位置在“后淡入淡出”效果的辉光之后会稍微透光。但这一结果令人沮丧


有什么解决办法吗?

因为矩形只有10%不透明,所以在图像上绘制矩形的结果是90%的图像和10%的白色合成。每次你画它,你失去了10%的图像上一次迭代;矩形本身不会变得更加不透明。(要获得这种效果,您需要在图像上放置另一个对象并设置其不透明度的动画。)因此,经过10次迭代后,您仍然拥有
(0.9^10)
或原始图像的35%。请注意,大约30次迭代后,舍入误差可能会出现。

我知道这是一个老问题,但我认为以前接受的答案不正确。我认为这是像素值从浮点值到字节被截断的结果。在运行Chrome 39.0.2171.95m版的Windows 7中,运行一段时间后,图像仍然可见,但只是轻微的,并且看起来不再有任何变化。如果我拍摄屏幕截图,我会在图像上看到以下像素值:

(246246246246)

使用rgba在其上绘制矩形时:

(255,255,255,0.1)

在转换为您得到的字节之前,使用source over的默认合成模式应用alpha混合:

(255*0.1+246*0.9)=246.9

因此,您可以看到,假设浏览器只是将浮点值截断为一个字节,它将写出一个246的值,并且每次重复绘图操作,最终都会得到相同的值

在这篇博文中有一个关于这个问题的大讨论

作为一种解决方法,您可以不断清除画布,并使用递减的globalAlpha值重新绘制图像。例如:

    // Clear the canvas
    ctx.globalAlpha = 1.0;
    ctx.fillStyle = "rgb(255, 255, 255)";
    ctx.fillRect(0,0,canvas.width(),canvas.height());

    // Decrement the alpha and draw the image
    alpha -= 0.1;
    if (alpha < 0) alpha = 0;
    ctx.globalAlpha = alpha;
    console.log(alpha);
    ctx.drawImage(image, 0, 0, 256, 256);
    setTimeout(draw, 100);
//清除画布
ctx.globalAlpha=1.0;
ctx.fillStyle=“rgb(255,255,255)”;
ctx.fillRect(0,0,canvas.width(),canvas.height());
//减小alpha并绘制图像
α-=0.1;
如果(α<0)α=0;
ctx.globalAlpha=α;
控制台日志(alpha);
ctx.drawImage(图像,0,0,256,256);
设置超时(绘图,100);

Fiddle是。

原因在之前已经说得很清楚了。如果不清除它并像@Sam已经说过的那样重新绘制它,就不可能去除它

您可以做的是设置
globalCompositeOperation

有各种各样的操作可以提供帮助。根据我的测试,我可以说,
强光
最适合深色背景,而
打火机
最适合明亮背景。但这完全取决于你的场景

在“接近”黑色上进行跟踪的示例


解决方案是使用ctx.getImageData和ctx.putImageData操作像素数据

不要使用带有半透明fillStyle的ctx.fillRect,而是将每个像素稍微设置为每帧的背景色。在我的例子中,它是黑色的,这使事情更简单

使用此解决方案,如果考虑浮动精度,您的轨迹可以任意长

function postProcess(){
  const fadeAmount = 1-1/256;
  const imageData = ctx.getImageData(0, 0, w, h);
  for (let x = 0; x < w; x++) {
    for (let y = 0; y < h; y++) {
      const i = (x + y * w) * 4;
      imageData.data[i] = Math.floor(imageData.data[i]*fadeAmount);
      imageData.data[i + 1] = Math.floor(imageData.data[i + 1]*fadeAmount);
      imageData.data[i + 2] = Math.floor(imageData.data[i + 2]*fadeAmount);
      imageData.data[i + 3] = 255;
    }
  }
  ctx.putImageData(imageData, 0, 0);
}

请记住,画布工作时不会“记住”以前的状态。因此
0.1
的alpha值并不意味着经过10次完全不透明,而是不透明度将是
(1-0.1)*(1-0.1)*……
,这确实永远不会达到
0
。我仍然不明白。这是内部图像处理计算吗?这对我来说是不合逻辑的。如果我说在Photoshop中放置10层,在某些内容上每10%的不透明度,这些层将完全覆盖它。这也适用于现实生活,不是吗?我的意思是,我理解你的答案,但为什么?根据你的解释,我实际上会放置一个100%不透明度的图像,并且每次迭代都会增加10%的当前不透明度,但这不是这里发生的事情=(并且:不,图像在30次迭代后不会消失。它保持在那里,变灰。可以将其视为每层减少10%的透明度。在一层之后,您将100%减少到90%。第二层将剩余的90%减少10%,因此仅减少9%。现在您的透明度约为81%。第三层将81%减少8.1%%给你留下大约72.9%的折扣等等…@mizwo也许一个类比会有所帮助:如果你买的东西是10%的折扣,并且你有10%的员工折扣,你不能得到原价的20%,你只能得到19%!我也有同样的问题(使用processing.js),如果我在画布上用90%的不透明度覆盖背景层,场景将永远不会被该层完全覆盖。但为什么呢?我的意思是,即使你总是只获得新图像的90%,在无限次(迭代->oo)之后,你仍然应该100%覆盖画布…如何修复这些舍入错误(因此函数实际上在不太多的步骤中收敛到0)?有可能去掉它
function postProcess(){
  const fadeAmount = 1-1/256;
  const imageData = ctx.getImageData(0, 0, w, h);
  for (let x = 0; x < w; x++) {
    for (let y = 0; y < h; y++) {
      const i = (x + y * w) * 4;
      imageData.data[i] = Math.floor(imageData.data[i]*fadeAmount);
      imageData.data[i + 1] = Math.floor(imageData.data[i + 1]*fadeAmount);
      imageData.data[i + 2] = Math.floor(imageData.data[i + 2]*fadeAmount);
      imageData.data[i + 3] = 255;
    }
  }
  ctx.putImageData(imageData, 0, 0);
}