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

时间:2011-10-25 20:15:36

标签: javascript html5-canvas

我发现了一个奇怪的问题。下面的代码导致图像逐渐消失,因为它一遍又一遍地被半透明的矩形透支。

但至少在draw();的第10次迭代时,图像应该完全透支,因为那时rect应该是完全不透明的,对吧?但它实际上永远不会完全消失。

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

我还在jsFiddle上发了demo

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

}    
});

人们可能希望实现的效果是,一个物体在整个画布上移动,并且已经绘制的位置仅略微透支,因此后褪色效果的余辉。但这个结果真是太可怕了。

那么有什么解决方案吗?

4 个答案:

答案 0 :(得分:5)

我知道这已经过时但我不认为以前接受的答案是正确的。我认为这是由于像素值从浮点到字节被截断而发生的。在运行Chrome版本39.0.2171.95m的Windows 7中,在运行您的小提琴一段时间后,图像仍然可见,但只是轻微,并且似乎不再发生变化。如果我截取屏幕截图,我会在图像上看到以下像素值:

  

(246,246,246)

使用rgba:

在其上绘制一个矩形时
  

(255,255,255,0.1)

并使用source-over的默认合成模式应用alpha混合,然后转换为您获得的字节:

  

(255 * 0.1 + 246 * 0.9)= 246.9

所以你可以看到,假设浏览器只是将浮点值截断为一个字节,它会写出一个246的值,每次重复绘图操作时,你总会得到相同的值。

此博客文章here对此问题进行了大量讨论。

作为一种解决方法,您可以不断清除画布并使用递减的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);

小提琴是here

答案 1 :(得分:4)

由于矩形只有10%不透明,因此在图像上绘制的结果是图像的90%和10%的白色的复合。每次绘制它都会丢失前一次图像迭代的10%;矩形本身不会变得更不透明。 (要获得该效果,您需要在图像上放置另一个对象并为其不透明度设置动画。)因此,在10次迭代后,您仍然拥有(0.9^10)或原始图像的约35%。请注意,舍入错误可能会在大约30次迭代后设置。

答案 2 :(得分:0)

之前完全说明了原因。如果没有清除它并且像@Sam已经说过那样重新绘制它是不可能摆脱它

你能做些什么来补偿它是设置globalCompositeOperation

有各种各样的操作有所帮助。根据我的测试,我可以说hard-light最适合深色背景,lighter最适合明亮背景。但这取决于你的场景。

在&#34;附近&#34;黑

ctx.globalCompositeOperation = 'hard-light'
ctx.fillStyle = 'rgba(20,20,20,0.2)' // The closer to black the better
ctx.fillRect(0, 0, width, height)

ctx.globalCompositeOperation = 'source-over' // reset to default value

答案 3 :(得分:0)

解决方案是使用ctx.getImageData和ctx.putImageData处理像素数据。

不要将ctx.fillRect与半透明的fillStyle一起使用,而是将每个像素略微设置为每帧的背景色。就我而言,它是黑色的,这使事情变得更简单。

使用这种解决方案,如果考虑浮点精度,您的足迹可以随心所欲。

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

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const w = window.innerWidth;
const h = window.innerHeight;
canvas.width = w;
canvas.height = h;
const cs = createCs(50);
let frame = 0;
function init(){
  ctx.strokeStyle = '#FFFFFF';
  ctx.fillStyle = '#000000';
  ctx.fillRect(0, 0, w, h)
  loop();
}
function createCs(n){
  const cs = [];
  for(let i = 0; i < n; i++){
    cs.push({
      x: Math.random()*w,
      y: Math.random()*h,
      r: Math.random()*5+1
    });
  }
  return cs;
}
function draw(frame){
  //no longer need these:
  //ctx.fillStyle = 'rgba(0,0,0,0.02)'
  //ctx.fillRect(0, 0, w, h)
  ctx.beginPath();
  cs.forEach(({x,y,r}, i) => {
    cs[i].x += 0.5;
    if(cs[i].x > w) cs[i].x = -r;
    ctx.moveTo(x+r+Math.cos((frame+i*4)/30)*r, y+Math.sin((frame+i*4)/30)*r);
    ctx.arc(x+Math.cos((frame+i*4)/30)*r,y+Math.sin((frame+i*4)/30)*r,r,0,Math.PI*2);
  });
  ctx.closePath();
  ctx.stroke();
  //only fade every 4 frames
  if(frame % 4 === 0) postProcess(0,0,w,h*0.5);
  //fade every frame
  postProcess(0,h*0.5,w,h*0.5);
  
}
//fades canvas to black
function postProcess(sx,sy,dw,dh){
  sx = Math.round(sx);
  sy = Math.round(sy);
  dw = Math.round(dw);
  dh = Math.round(dh);
  const fadeAmount = 1-4/256;
  const imageData = ctx.getImageData(sx, sy, dw, dh);
  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, sx, sy);
}
function loop(){
  draw(frame);
  frame ++;
  requestAnimationFrame(loop);
}
init();
canvas {
width: 100%;
height: 100%;
}
<canvas id="canvas"/>