javascript 如果多次应用,带有 alpha 的 rgba fillStyle 不会变得完全不透明
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7895306/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
rgba fillStyle with alpha does not get fully opaque if applied multiple times
提问by owzim
I stubled upon a weird problem. Tthe following code results in making the image fade away because it's overdrawn by a semi-opaque rect over and over again.
我偶然发现了一个奇怪的问题。下面的代码导致图像逐渐消失,因为它被半透明的矩形一遍又一遍地透支。
But at least at the 10th iteration of draw();
the image should be completely overdrawn, because the rect should be fully opaque by then, right? But it actually never disappears completely.
但至少在draw();
图像的第 10 次迭代应该完全透支,因为到那时矩形应该是完全不透明的,对吧?但它实际上永远不会完全消失。
This effect is worse on Chrome than it is on Firefox. But beware: bad screens may hide this faulty behaviour =)
这种效果在 Chrome 上比在 Firefox 上更糟糕。但要注意:糟糕的屏幕可能会隐藏这种错误行为 =)
I also made a demoon jsFiddle.
我也在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 one may want to achieve is that, say an object is moving all over the canvas, and the already drawn positions get overdrawn only slightly so after-glow of after-fade effect. But this result is just fugly.
人们可能想要达到的效果是,假设一个对象在整个画布上移动,并且已经绘制的位置只是稍微过度绘制,因此出现了后退效果的余辉。但这个结果只是模糊的。
So is there any solution to this?
那么有没有办法解决这个问题?
采纳答案by Neil
Since the rectangle is only 10% opaque, the result of drawing it over the image is a composite 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 that effect, you would need to position another object over 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 will probably set in after about 30 iterations.
由于矩形只有 10% 不透明,因此在图像上绘制它的结果是图像的 90% 和 10% 的白色的合成。每次绘制它时,都会损失图像上一次迭代的 10%;矩形本身不会变得更不透明。(要获得这种效果,您需要在图像上放置另一个对象并为其设置不透明度动画。)因此,经过 10 次迭代后,您仍然拥有(0.9^10)
原始图像的 35% 左右。请注意,大约 30 次迭代后可能会出现舍入误差。
回答by Sam
I know this is old but I don't think the previously accepted answer is correct. I think this is happening as a result of pixel values being truncated from float to byte. In Windows 7 running Chrome version 39.0.2171.95m, after running your fiddle for a while, the image is still visible but only lightly, and doesn't appear to be changing any more. If I take a screenshot I see the following pixel values on the image:
我知道这是旧的,但我认为以前接受的答案不正确。我认为这是由于像素值从浮点数被截断为字节而发生的。在运行 Chrome 版本 39.0.2171.95m 的 Windows 7 中,运行一段时间后,图像仍然可见,但只是轻微的,并且似乎不再发生变化。如果我截取屏幕截图,我会在图像上看到以下像素值:
(246, 246, 246)
(246, 246, 246)
When you draw a rectangle over it with rgba of:
当您使用 rgba 在其上绘制一个矩形时:
(255, 255, 255, 0.1)
(255, 255, 255, 0.1)
and apply alpha blending using the default compositing mode of source-over, before converting to a byte you get:
并使用默认的 source-over 合成模式应用 alpha 混合,然后转换为您得到的字节:
(255 * 0.1 + 246 * 0.9) = 246.9
(255 * 0.1 + 246 * 0.9) = 246.9
So you can see that, assuming the browser simply truncates the floating point value to a byte, it will write out a value of 246, and every time you repeat the drawing operation you'll always end up with the same value.
所以你可以看到,假设浏览器简单地将浮点值截断为一个字节,它会写出一个 246 的值,并且每次重复绘制操作时,最终都会得到相同的值。
There is a big discussion on the issue at this blog post here.
有一个在这个博客帖子上的问题的大讨论在这里。
As a workaround you could continually clear the canvas and redraw the image with a decreasing globalAlpha value. For example:
作为一种解决方法,您可以不断清除画布并使用递减的 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);
Fiddle is here.
小提琴在这里。
回答by arc
The reason was perfectly stated before. It is not possible to get rid of itwithout clearing it and redrawing it like @Sam already said.
原因之前已经说得很清楚了。如果不清除它并像@Sam 已经说过的那样重新绘制它,就不可能摆脱它。
What you can you do to compensate it a bit is to set globalCompositeOperation
.
你可以做的稍微补偿一下是设置globalCompositeOperation
.
There are various operations that help. From my tests I can say that hard-light
works best for dark backgrounds and lighter
work best for bright backgrounds. But this very depends on your scene.
有各种操作可以提供帮助。从我的测试中,我可以说hard-light
最适合深色背景,lighter
最适合明亮背景。但这非常取决于您的场景。
An example making trails on "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
回答by George
The solution is to manipulate the pixel data with ctx.getImageData and ctx.putImageData.
解决办法是用ctx.getImageData和ctx.putImageData来操作像素数据。
Instead of using ctx.fillRect with a translucent fillStyle, set each pixel slightly to your background colour each frame. In my case it is black, which makes things simpler.
不要将 ctx.fillRect 与半透明的 fillStyle 一起使用,而是将每个像素稍微设置为每帧的背景颜色。就我而言,它是黑色的,这让事情变得更简单。
With this solution, your trails can be as long as you want, if float precision is taken into account.
使用此解决方案,如果考虑到浮点精度,您的轨迹可以随心所欲。
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"/>