减去不透明度而不是乘法

时间:2017-08-07 14:30:39

标签: javascript html5 canvas alpha alphablending

我试图让我看起来好像<canvas>上的动作会创建动作轨迹。为了做到这一点,我不是在帧之间清除画布,而是通过用以下内容替换clearRect调用来降低现有内容的不透明度:

// Redraw the canvas's contents at lower opacity. The 'copy' blend
// mode keeps only the new content, discarding what was previously
// there. That way we don't have to use a second canvas when copying 
// data
ctx.globalCompositeOperation = 'copy';
ctx.globalAlpha = 0.98;
ctx.drawImage(canvas, 0, 0);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';

但是,由于设置globalAlpha会使alpha值相乘,因此路径的alpha值可能会接近零,但实际上永远不会达到它。这意味着图形永远不会褪色,在画布上留下这样的痕迹,即使经过几分钟的数千帧后也不会褪色:

traces

为了解决这个问题,我一直在逐像素地减去alpha值,而不是使用globalAlpha。减法可确保像素不透明度达到零。

// Reduce opacity of each pixel in canvas
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// Iterates, hitting only the alpha values of each pixel.
for (let i = 3; i < data.length; i += 4) {
  // Use 0 if the result of subtraction would be less than zero.
  data[i] = Math.max(data[i] - (0.02 * 255), 0);
}
ctx.putImageData(imageData, 0, 0);

这解决了这个问题,但由于我手动更改每个像素值,然后使用昂贵的putImageData()方法,因此速度非常慢。

是否有更高效的方法来减去而不是乘以画布上绘制的像素的不透明度?

1 个答案:

答案 0 :(得分:1)

不幸的是,除了手动迭代像素以清除低值alpha像素之外,我们无能为力。

问题与integer math and rounding有关(详情请见this link)。

有混合模式,如&#34;发光度&#34; (并且在一定程度上&#34;乘以&#34;)可以用来减去亮度,问题是它在整个表面上工作,而复合模式只适用于alpha - 在复合操作中没有等价物。所以这在这里没有帮助。

还有一个新的亮度掩模通过CSS,但问题是图像源(理论上可以使用例如对比度操纵)必须每帧更新,基本上,性能会非常坏。

解决方法

一种解决方法是使用&#34;粒子&#34;。也就是说,不是使用反馈循环而是记录并存储路径点,而是每帧重绘所有记录的点。使用最大值并重复使用它来设置alpha可以在许多情况下正常工作。

这个简单的例子只是一个概念验证,可以通过各种方式实现,可能是预先填充的数组,绘图顺序,alpha值计算等等。但我认为你会明白这一点。

&#13;
&#13;
var ctx = c.getContext("2d");
var cx = c.width>>1, cy = c.height>>1, r = c.width>>2, o=c.width>>3;
var particles = [], max = 50;

ctx.fillStyle = "#fff";

(function anim(t) {
  var d = t * 0.002, x = cx + r * Math.cos(d), y = cy + r * Math.sin(d);
  
  // store point and trim array when reached max
  particles.push({x: x, y: y});
  if (particles.length > max) particles.shift();
  
  // clear frame as usual
  ctx.clearRect(0,0,c.width,c.height);
  
  // redraw all particles at a log. alpha, except last which is drawn full
  for(var i = 0, p, a; p = particles[i++];) {
    a = i / max * 0.6;
    ctx.globalAlpha = i === max ? 1 : a*a*a;
    ctx.fillRect(p.x-o, p.y-o, r, r);  // or image etc.
  }

  requestAnimationFrame(anim);
})();
&#13;
body {background:#037}
&#13;
<canvas id=c width=400 height=400></canvas>
&#13;
&#13;
&#13;