黑色调整大小的画布不会随着时间的推移将绘图完全褪色为黑色

Black resized canvas not completely fading drawings to black over time

本文关键字:黑色 绘图 褪色 调整 布不会 时间      更新时间:2023-11-04

我有一个黑色画布,里面画着一些东西。我希望随着时间的推移,里面画的东西按照绘制顺序(FIFO)逐渐变黑。如果我使用未调整大小的画布,这会起作用。调整画布的大小时,元素会逐渐变为灰白色。

问题:调整画布大小后,为什么白色斑点不完全变黑?如何让它们像我没有调整画布大小时那样褪色为黑色?

下面是一些演示代码。http://jsfiddle.net/6VvbQ/35/

var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.fillRect(0, 0, 300, 150);
// Comment this out and it works as intended, why?
canvas.width = canvas.height = 300;
window.draw = function () {
    context.fillStyle = 'rgba(255,255,255,1)';
    context.fillRect(
        Math.floor(Math.random() * 300),
        Math.floor(Math.random() * 150),
        2, 2);
    context.fillStyle = 'rgba(0,0,0,.02)';
    context.fillRect(0, 0, 300, 150);
    setTimeout('draw()', 1000 / 20);
}
setTimeout('draw()', 1000 / 20);

问题分为两部分:

  1. 当使用低alpha值绘制时,会出现(相当已知的)舍入误差。浏览器将永远无法获得等于0的颜色和alpha通道的结果混合,因为混合后的结果浮点值将在绘制时转换为整数,这意味着该值永远不会低于1。下一次混合时(值1,因为alpha内部是0到255之间的值)将再次使用该值,并将其四舍五入到1,然后永远保持不变。

  2. 为什么当你有一个调整大小的画布时它会起作用——在这种情况下,这是因为你只画了大画布的一半到小画布,这导致像素被插值。由于该值非常低,这意味着在这种情况下;黑色";(完全透明),因为周围像素之间的平均值将导致该值四舍五入为0——与#1相反。

为了解决这个问题,你必须手动清除预期为黑色的规格。这将涉及到您自己跟踪每个粒子/规格,或者使用直接像素操作更改alpha。

更新

关键是要使用跟踪。您可以通过将每个等级库创建为一个自更新点来实现这一点,该点可以跟踪alpha并清除。

在线演示

一个简单的规范对象可以是这样的:

function Spec(ctx, speed) {
    
    var me = this;
        
    reset();                             /// initialize object
    
    this.update = function() {
    
        ctx.clearRect(me.x, me.y, 1, 1); /// clear previous drawing
        this.alpha -= speed;             /// update alpha
        
        if (this.alpha <= 0) reset();    /// if black then reset again
        
        /// draw the spec
        ctx.fillStyle = 'rgba(255,255,255,' + me.alpha + ')';
        ctx.fillRect(me.x, me.y, 1, 1);
    }
    
    function reset() {
        me.x = (ctx.canvas.width * Math.random())|0;  /// random x rounded to int
        me.y = (ctx.canvas.height * Math.random())|0; /// random y rounded to int
        if (me.alpha) {                               /// reset alpha
            me.alpha = 1.0;                           /// set to 1 if existed
        } else {
            me.alpha = Math.random();                 /// use random if not
        }
    }
}

当我们需要清除规范时,将x和y四舍五入为整数值可以节省一点时间,因为我们不会遇到子像素。否则,您还需要清理规范周围的区域。

下一步是生成多个点:

/// create 100 specs with random speed
var i = 100, specs = [];
while(i--) {
    specs.push(new Spec(ctx, Math.random() * 0.015 + 0.005));
}

你只需使用可以根据规格单独设置的速度,而不是干扰FPS。

现在只需要更新循环中的每个对象:

function loop() {
    /// iterate each object
    var i = specs.length - 1;    
    while(i--) {
        specs[i].update();        /// update each object
    }
    requestAnimationFrame(loop);  /// loop synced to monitor
}

正如您所看到的,性能不是一个问题,也没有残留。希望这能有所帮助。

我不知道我是否能很好地理解你,但看着你,我认为,对于你想要的东西,你需要在循环的任何迭代中提供画布的大小。如果没有,那么你只是取初始值:

编辑

如果将阈值过滤器应用于画布,则可以执行此操作。你可以每秒钟运行一次过滤器,只是为了让前缀不会受到太大的打击。

var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.fillRect(0,0,300,150);
//context.globalAlpha=1;
//context.globalCompositeOperation = "source-over";

var canvas2 = document.getElementById('canvas2');
var context2 = canvas2.getContext('2d');
canvas2.width=canvas2.height=canvas.width;
window.draw = function(){
    var W = canvas2.width;
    var H = canvas2.height;
    context2.fillStyle='rgba(255,255,255,1)';
    context2.fillRect(
        Math.floor(Math.random()*W),
        Math.floor(Math.random()*H),
        2,2);
    context2.fillStyle='rgba(0,0,0,.02)';
    context2.fillRect(0,0,W,H);
    context.fillStyle='rgba(0,0,0,1)';
    context.fillRect(0,0,300,150);
    context.drawImage(canvas2,0,0,300,150);
    setTimeout('draw()', 1000/20);
}
setTimeout('draw()', 1000/20);
window.thresholdFilter = function () {
    var W = canvas2.width;
    var H = canvas2.height;
    var i, j, threshold = 30, rgb = []
    ,   imgData=context2.getImageData(0,0,W,H), Npixels = imgData.data.length;
    for (i = 0; i < Npixels; i += 4) {
        rgb[0] = imgData.data[i];
        rgb[1] = imgData.data[i+1];
        rgb[2] = imgData.data[i+2];
        if (    rgb[0] < threshold &&
                rgb[1] < threshold &&
                rgb[2] < threshold
        ) {
           imgData.data[i] = 0;
           imgData.data[i+1] = 0;
           imgData.data[i+2] = 0;
        }
    }
    context2.putImageData(imgData,0,0);
};
setInterval("thresholdFilter()", 1000);

这是小提琴:http://jsfiddle.net/siliconball/2VaLb/4/

为了避免舍入问题,您可以使用更长的刷新间隔和更大的alpha值,将渐变效果提取到一个具有自己定时器的单独函数中。

var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.fillRect(0, 0, 300, 150);
// Comment this out and it works as intended, why?
canvas.width = canvas.height = 300;
window.draw = function () {
    context.fillStyle = 'rgba(255,255,255,1)';
    context.fillRect(
        Math.floor(Math.random() * 300),
        Math.floor(Math.random() * 300),
        2, 2);
    setTimeout('draw()', 1000 / 20);
}
window.fadeToBlack = function () {
    context.fillStyle = 'rgba(0,0,0,.1)';
    context.fillRect(0, 0, 300, 300);
    setTimeout('fadeToBlack()', 1000 / 4);    
}
draw();
fadeToBlack();

Fiddle演示:http://jsfiddle.net/6VvbQ/37/