Rgba fillStyle with alpha does not become completely opaque if applied multiple times

I ran into some strange problem. The following code causes the image to disappear because it is overloaded with a translucent rectangle again and again.

But at least at the 10th iteration draw(); the image should be completely overloaded because the rectangle should be completely opaque by then, right? But he never really disappears completely.

This effect is worse in Chrome than in Firefox. But be careful: bad screens can hide this erroneous behavior =)

I also did a demo on 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); } }); 

The effect that can be achieved is that, say, the object moves around the canvas, and the already drawn positions are reset only slightly, so that after the effect shines after attenuation. But this result is just ugly.

So is there any solution?

+7
source share
4 answers

Since the rectangle is only 10% opaque, the result of drawing it on the image is a composition of 90% of the image and 10% white. Each time you draw it, you lose 10% of the previous iteration of the image; the rectangle itself does not become more opaque. (To get this effect, you will need to position another object above the image and animate its opacity.) So, after 10 iterations, you still have (0.9^10) or about 35% of the original image. Note that rounding errors are likely to be set after about 30 iterations.

+4
source

I know this is old, but I don't think the previously accepted answer is correct. I think this happens as a result of truncating the pixel values ​​from a floating point number to bytes. In Windows 7 running Chrome version 39.0.2171.95m, after starting the violin for a while, the image is still visible, but only slightly, and it seems to no longer change. If I take a screenshot, I see the following pixel values ​​in the image:

(246, 246, 246)

When you draw a rectangle over it with rgba:

(255, 255, 255, 0.1)

and apply alpha blending using the default layout mode with the overlay of the source code, before converting to the byte you get:

(255 * 0.1 + 246 * 0.9) = 246.9

So you can see that, assuming the browser just truncates the floating point value to a byte, it writes the value 246, and each time you repeat the drawing operation, you will always get the same value.

There is a lot of discussion on this subject on this blog here .

As a workaround, you can constantly clear the canvas and redraw the image with decreasing globalAlpha value. For instance:

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

The violin is here .

+5
source

The reason was fully stated earlier. it's impossible to get rid of it without clearing it and redrawing, as @Sam already said.

What you can do to compensate for this a bit is to set globalCompositeOperation .

There are various operations that help. From my tests, I can say that hard-light works best for dark backgrounds, and lighter works best for bright backgrounds. But it really depends on your scene.

An example of creating tracks on the "near" black

 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 
0
source

The solution is to manipulate the pixel data using ctx.getImageData and ctx.putImageData.

Instead of using ctx.fillRect with a translucent fillStyle, slightly set each pixel to its own background color in each frame. In my case, it is black, which makes things easier.

With this solution, your footprints can be as long as you want, given the accuracy of swimming.

 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"/> 
0
source

Source: https://habr.com/ru/post/900077/


All Articles