草庐IT

javascript - 随时间褪色的 Canvas 绘画 |奇怪的阿尔法分层行为

coder 2025-03-05 原文

我正在绘制一个未被清除的 Canvas ,并使 Canvas 随着时间的推移逐渐变成纯色,或者在 alpha 中逐渐消失以显示后面的图层。

我的第一直觉是简单地用每帧的低 alpha 在绘图上填充一个矩形,这样填充颜色就会逐渐累积并淡出绘图。

但我发现了一些奇怪的行为(至少对我来说,我确信这是有原因的)。 填充颜​​色永远不会完全累积。结果会根据油漆和填充颜色是否比彼此更亮/更暗而变化。

我发现这个问题有人和我做同样的事情:fade out lines after drawing canvas?

顶部的答案看起来不错,和我试过的一样。但它只适用于白底黑字。这是同一个 fiddle 的不同颜色的另一个版本,你会看到绘图永远不会消失,它留下了一个幽灵:http://jsfiddle.net/R4V97/92/

var canvas = document.getElementById("canvas"),
    ctx = canvas.getContext("2d"),
    painting = false,
    lastX = 0,
    lastY = 0;

canvas.width = canvas.height = 600;

canvas.onmousedown = function (e) {
    if (!painting) {
        painting = true;
    } else {
        painting = false;
    }

    lastX = e.pageX - this.offsetLeft;
    lastY = e.pageY - this.offsetTop;
};

canvas.onmousemove = function (e) {
    if (painting) {
        mouseX = e.pageX - this.offsetLeft;
        mouseY = e.pageY - this.offsetTop;

        ctx.strokeStyle = "rgba(255,255,255,1)";
        ctx.beginPath();
        ctx.moveTo(lastX, lastY);
        ctx.lineTo(mouseX, mouseY);
        ctx.stroke();

        lastX = mouseX;
        lastY = mouseY;
    }
}

function fadeOut() {
    ctx.fillStyle = "rgba(60,30,50,0.2)";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    setTimeout(fadeOut,100);
}

fadeOut();

此外,如果您将填充不透明度更改为 0.01,并将时间更改为 20 毫秒,它甚至不会填充正确的颜色,而是保持灰色。

我尝试过的其他事情都存在同样的根本问题。我试过在两个 Canvas 之间弹跳,使用 Canvas A 并使用减少的 alpha 将其绘制到 Canvas B,然后再将 Canvas B 画回 Canvas A - 同样的问题,有一个阈值不会消失。

作为测试,我什至尝试了获取图像数据的超慢操作,遍历所有像素 alpha channel 并乘以 0.95,然后再将数据放回原处。它仍然留下了阴影,我必须在循环中做这样的事情(由于某种原因它甚至从未低于 10):

if (alpha<25) {
    alpha = 0;
}

我在想我也许可以将 Canvas 分成一个网格或几行,并在每帧一个单元格中执行 imageData 操作,这在低淡入淡出时间时可能不会引人注意。

但是,如果有人知道更好的方法或者我没有得到的核心内容是什么,我将不胜感激!

  • 哦还应该注意,我让它在 Canvas 上栅格化,因为我正在用粒子/算法绘画,所以我不是在寻找意味着我不断刷新和重新绘制相同点的解决方案。谢谢!

最佳答案

RGB 和 8 位整数数学!

您需要避免接触 RGB channel ,因为当您对 8 位值进行数学运算时,结果会出现巨大错误。例如(8 位整数数学)14 * 0.1 = 1、8 * 0.1 = 1 因此,当您在现有像素上绘制时,您将得到一个舍入误差,该误差对于每个 channel 都不同,具体取决于您在顶部绘制的颜色。

没有完美的解决方案,但您可以通过使用全局复合操作“destination-out”来避免颜色 channel 并仅淡化 alpha channel 这将通过减少像素 alpha 来淡出渲染。

适用于低至 globalAlpha = 0.01 甚至更低一点的 0.006 的淡入淡出率,但低于该值可能会很麻烦。然后,如果您需要更慢的淡入淡出,只需每第 2 或第 3 帧进行一次淡入淡出。

ctx.globalAlpha = 0.01;           // fade rate
ctx.globalCompositeOperation = "destination-out"  // fade out destination pixels
ctx.fillRect(0,0,w,h)
ctx.globalCompositeOperation = "source-over"
ctx.globalAlpha = 1;           // reset alpha

请注意,这会将 Canvas 淡化为透明。如果您希望渐变朝着特定颜色发展,您需要将渐变 Canvas 保持为单独的屏幕外 Canvas ,并将其绘制在具有要渐变到的所需背景的 Canvas 上。

演示带有淡入淡出的彩色背景上的彩色粒子。

var canvas = document.createElement("canvas");
canvas.width = 1024;
canvas.height = 1024;
var ctx = canvas.getContext("2d");
var w = canvas.width;
var h = canvas.height;
document.body.appendChild(canvas);

var fadCan = document.createElement("canvas");
fadCan.width = canvas.width;
fadCan.height = canvas.height;
var fCtx = fadCan.getContext("2d");

var cw = w / 2;  // center 
var ch = h / 2;
var globalTime;

function randColour(){
    return "hsl("+(Math.floor(Math.random()*360))+",100%,50%)";
}
var pps = [];
for(var i = 0; i < 100; i ++){
    pps.push({
        x : Math.random() * canvas.width,
        y : Math.random() * canvas.height,
        d : Math.random() * Math.PI * 2,
        sp : Math.random() * 2 + 0.41,
        col : randColour(),
        s : Math.random() * 5 + 2,
        t : (Math.random() * 6 -3)/10,
        
    });
}
function doDots(){
    for(var i = 0; i < 100; i ++){
        var d = pps[i];
        d.d += d.t * Math.sin(globalTime / (d.t+d.sp+d.s)*1000);
        d.x += Math.cos(d.d) * d.sp;
        d.y += Math.sin(d.d) * d.sp;
        d.x = (d.x + w)%w;
        d.y = (d.y + w)%w;
        fCtx.fillStyle = d.col;
        fCtx.beginPath();
        fCtx.arc(d.x,d.y,d.s,0,Math.PI * 2);
        fCtx.fill();
        
    }
}


var frameCount = 0;
// main update function
function update(timer){
    globalTime = timer;
    frameCount += 1;
    ctx.setTransform(1,0,0,1,0,0); // reset transform
    ctx.globalAlpha = 1;           // reset alpha
    ctx.fillStyle = "hsl("+(Math.floor((timer/50000)*360))+",100%,50%)";
    ctx.fillRect(0,0,w,h);
    doDots();
    if(frameCount%2){
        fCtx.globalCompositeOperation = "destination-out";
        fCtx.fillStyle = "black";
        var r = Math.random() * 0.04
        fCtx.globalAlpha = (frameCount & 2 ? 0.16:0.08)+r;
        fCtx.fillRect(0,0,w,h);
        fCtx.globalAlpha = 1;
        fCtx.globalCompositeOperation = "source-over"
    }
    ctx.drawImage(fadCan,0,0)
    requestAnimationFrame(update);
}
requestAnimationFrame(update);

在带淡入淡出的彩色背景上绘制演示。

点击拖动鼠标进行绘制。

var canvas = document.createElement("canvas");
canvas.width = 1024;
canvas.height = 1024;
var ctx = canvas.getContext("2d");
var w = canvas.width;
var h = canvas.height;
document.body.appendChild(canvas);

var fadCan = document.createElement("canvas");
fadCan.width = canvas.width;
fadCan.height = canvas.height;
var fCtx = fadCan.getContext("2d");

var cw = w / 2;  // center 
var ch = h / 2;
var globalTime;

function randColour(){
    return "hsl("+(Math.floor(Math.random()*360))+",100%,50%)";
}



// main update function
function update(timer){
    globalTime = timer;
    ctx.setTransform(1,0,0,1,0,0); // reset transform
    ctx.globalAlpha = 1;           // reset alpha
    ctx.fillStyle = "hsl("+(Math.floor((timer/150000)*360))+",100%,50%)";
    ctx.fillRect(0,0,w,h);
    if(mouse.buttonRaw === 1){
        fCtx.strokeStyle = "White";
        fCtx.lineWidth = 3;
        fCtx.lineCap = "round";
        fCtx.beginPath();
        fCtx.moveTo(mouse.lx,mouse.ly);
        fCtx.lineTo(mouse.x,mouse.y);
        fCtx.stroke();
    }


    mouse.lx = mouse.x;
    mouse.ly = mouse.y;
    fCtx.globalCompositeOperation = "destination-out";
    fCtx.fillStyle = "black";
    fCtx.globalAlpha = 0.1;
    fCtx.fillRect(0,0,w,h);
    fCtx.globalAlpha = 1;
    fCtx.globalCompositeOperation = "source-over"
    ctx.drawImage(fadCan,0,0)
    requestAnimationFrame(update);
}


var mouse = (function () {
    function preventDefault(e) {
        e.preventDefault();
    }
    var mouse = {
        x : 0,
        y : 0,
        w : 0,
        alt : false,
        shift : false,
        ctrl : false,
        buttonRaw : 0,
        over : false,
        bm : [1, 2, 4, 6, 5, 3],
        active : false,
        bounds : null,
        crashRecover : null,
        mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
    };
    var m = mouse;
    function mouseMove(e) {
        var t = e.type;
        m.bounds = m.element.getBoundingClientRect();
        m.x = e.pageX - m.bounds.left + scrollX;
        m.y = e.pageY - m.bounds.top + scrollY;
        m.alt = e.altKey;
        m.shift = e.shiftKey;
        m.ctrl = e.ctrlKey;
        if (t === "mousedown") {
            m.buttonRaw |= m.bm[e.which - 1];
        } else if (t === "mouseup") {
            m.buttonRaw &= m.bm[e.which + 2];
        } else if (t === "mouseout") {
            m.buttonRaw = 0;
            m.over = false;
        } else if (t === "mouseover") {
            m.over = true;
        } else if (t === "mousewheel") {
            m.w = e.wheelDelta;
        } else if (t === "DOMMouseScroll") {
            m.w = -e.detail;
        }
        if (m.callbacks) {
            m.callbacks.forEach(c => c(e));
        }
        if ((m.buttonRaw & 2) && m.crashRecover !== null) {
            if (typeof m.crashRecover === "function") {
                setTimeout(m.crashRecover, 0);
            }
        }
        e.preventDefault();
    }
    m.addCallback = function (callback) {
        if (typeof callback === "function") {
            if (m.callbacks === undefined) {
                m.callbacks = [callback];
            } else {
                m.callbacks.push(callback);
            }
        }
    }
    m.start = function (element) {
        if (m.element !== undefined) {
            m.removeMouse();
        }
        m.element = element === undefined ? document : element;
        m.mouseEvents.forEach(n => {
            m.element.addEventListener(n, mouseMove);
        });
        m.element.addEventListener("contextmenu", preventDefault, false);
        m.active = true;
    }
    m.remove = function () {
        if (m.element !== undefined) {
            m.mouseEvents.forEach(n => {
                m.element.removeEventListener(n, mouseMove);
            });
            m.element.removeEventListener("contextmenu", preventDefault);
            m.element = m.callbacks = undefined;
            m.active = false;
        }
    }
    return mouse;
})();

mouse.start(canvas);
requestAnimationFrame(update);

关于javascript - 随时间褪色的 Canvas 绘画 |奇怪的阿尔法分层行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41483806/

有关javascript - 随时间褪色的 Canvas 绘画 |奇怪的阿尔法分层行为的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. ruby-on-rails - Ruby 检查日期时间是否为 iso8601 并保存 - 2

    我需要检查DateTime是否采用有效的ISO8601格式。喜欢:#iso8601?我检查了ruby​​是否有特定方法,但没有找到。目前我正在使用date.iso8601==date来检查这个。有什么好的方法吗?编辑解释我的环境,并改变问题的范围。因此,我的项目将使用jsapiFullCalendar,这就是我需要iso8601字符串格式的原因。我想知道更好或正确的方法是什么,以正确的格式将日期保存在数据库中,或者让ActiveRecord完成它们的工作并在我需要时间信息时对其进行操作。 最佳答案 我不太明白你的问题。我假设您想检查

  3. ruby-on-rails - 将 Ruby 中的日期/时间格式化为 YYYY-MM-DD HH :MM:SS - 2

    这个问题在这里已经有了答案:Railsformattingdate(4个答案)关闭4年前。我想格式化Time.Now函数以显示YYYY-MM-DDHH:MM:SS而不是:“2018-03-0909:47:19+0000”该函数需要放在时间中.现在功能。require‘roo’require‘roo-xls’require‘byebug’file_name=ARGV.first||“Template.xlsx”excel_file=Roo::Spreadsheet.open(“./#{file_name}“,extension::xlsx)xml=Nokogiri::XML::Build

  4. ruby - 查找字符串中的内容类型(数字、日期、时间、字符串等) - 2

    我正在尝试解析一个CSV文件并使用SQL命令自动为其创建一个表。CSV中的第一行给出了列标题。但我需要推断每个列的类型。Ruby中是否有任何函数可以找到每个字段中内容的类型。例如,CSV行:"12012","Test","1233.22","12:21:22","10/10/2009"应该产生像这样的类型['integer','string','float','time','date']谢谢! 最佳答案 require'time'defto_something(str)if(num=Integer(str)rescueFloat(s

  5. sql - 查询忽略时间戳日期的时间范围 - 2

    我正在尝试查询我的Rails数据库(Postgres)中的购买表,我想查询时间范围。例如,我想知道在所有日期的下午2点到3点之间进行了多少次购买。此表中有一个created_at列,但我不知道如何在不搜索特定日期的情况下完成此操作。我试过:Purchases.where("created_atBETWEEN?and?",Time.now-1.hour,Time.now)但这最终只会搜索今天与那些时间的日期。 最佳答案 您需要使用PostgreSQL'sdate_part/extractfunction从created_at中提取小时

  6. ruby - Ruby gsub 替换中的行为不一致? - 2

    两个gsub产生不同的结果。谁能解释一下为什么?代码也可在https://gist.github.com/franklsf95/6c0f8938f28706b5644d获得.ver=9999str="\tCFBundleDevelopmentRegion\n\ten\n\tCFBundleVersion\n\t0.1.190\n\tAppID\n\t000000000000000"putsstr.gsub/(CFBundleVersion\n\t.*\.).*()/,"#{$1}#{ver}#{$2}"puts'--------'putsstr.gsub/(CFBundleVersio

  7. ruby-on-rails - Ruby 中意外的大小写行为 - 2

    我在一段非常简单的代码(如我所想)中得到了一个错误的值:org=4caseorgwhenorg=4val='H'endputsval=>nil请不要生气,我希望我错过了一些非常明显的东西,但我真的想不通。谢谢。 最佳答案 这是典型的Ruby错误。case有两种被调用的方法,一种是你传递一个东西作为分支的基础,另一种是你不传递的东西。如果您确实在case中指定了一个表达式语句然后评估所有其他条件并与===进行比较.在这种情况下org评估为false和org===false显然不是真的。所有其他情况也是如此,它们要么是真的,要么是假的。

  8. ruby - 使对象的行为类似于 ruby​​ 中并行分配的数组 - 2

    假设您在Ruby中执行此操作:ar=[1,2]x,y=ar然后,x==1和y==2。是否有一种方法可以在我自己的类中定义,从而产生相同的效果?例如rb=AllYourCode.newx,y=rb到目前为止,对于这样的赋值,我所能做的就是使x==rb和y=nil。Python有这样一个特性:>>>classFoo:...def__iter__(self):...returniter([1,2])...>>>x,y=Foo()>>>x1>>>y2 最佳答案 是的。定义#to_ary。这将使您的对象被视为要分配的数组。irb>o=Obje

  9. ruby - 在没有基准或时间的情况下用 Ruby 测量用户时间或系统时间 - 2

    因为我现在正在做一些时间测量,我想知道是否可以在不使用Benchmark类或命令行实用程序time的情况下测量用户时间或系统时间。使用Time类只显示挂钟时间,而不显示系统和用户时间,但是我正在寻找具有相同灵active的解决方案,例如time=TimeUtility.now#somecodeuser,system,real=TimeUtility.now-time原因是我有点不喜欢Benchmark,因为它不能只返回数字(编辑:我错了-它可以。请参阅下面的答案。)。当然,我可以解析输出,但感觉不对。*NIX系统的time实用程序也应该可以解决我的问题,但我想知道是否已经在Ruby中实

  10. ruby-on-rails - 使用 javascript 更改数据方法不会更改 ajax 调用用户的什么方法? - 2

    我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的

随机推荐