Painting in Canvas which fades with time | Strange alpha layering behaviour

前端 未结 4 2132
旧时难觅i
旧时难觅i 2020-12-18 05:58

I\'m painting to a canvas which isn\'t being cleared and making it so that the canvas either fades to a solid colour over time, or fades in alpha revealing the layer behind.

4条回答
  •  无人及你
    2020-12-18 06:48

    RGB and 8bit integer math!

    You need to avoid touching the RGB channels because when you do math on 8 bit values the results will have a huge error. Eg (8bit integer math) 14 * 0.1 = 1, 8 * 0.1 = 1 Thus when you draw over the existing pixels you will get a rounding error that will be different for each channel depending on the colour you are drawing on top.

    There is not perfect solution but you can avoid the colour channels and fade only the alpha channel by using the global composite operation "destination-out" This will fade out the rendering by reducing the pixels alpha.

    Works well for fade rates down to globalAlpha = 0.01 and even a little lower 0.006 but it can be troublesome below that. Then if you need even slower fade just do the fade every 2nd or 3rd frame.

    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
    

    Please note that this fade the canvas to transparent. If you want the fade to progress towards a particular colour you need to keep the fading canvas as a separate offscreen canvas and draw it over a canvas with the desired background to fade to.

    Demo coloured particles on coloured background with fade.

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

    Demo drawing on coloured background with fade.

    Click drag mouse to draw.

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

提交回复
热议问题