You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Canvas循环绘制时阴影过度叠加问题求助

Canvas循环绘制时阴影过度叠加异常

单次绘制带阴影效果的圆角矩形时显示正常,但在循环处理视频帧的场景下,背景会出现阴影过度叠加的问题。

单次绘制正常代码示例

export const titleRender = async () => {
    const canvas = createCanvas(1920, 1080);
    const ctx = canvas.getContext('2d');
    const img = await loadImage('blueBackground.png')
    ctx.drawImage(img, 0, 0, 1920, 1920);
    return canvas;
};

const drawEffects = async () => {
    const effectsCanvas = createCanvas(1920, 1080) 
    const ctx = effectsCanvas.getContext('2d');
  
    ctx.beginPath();
    ctx.roundRect(77 , 234 , 1766 , 846,[20, 20, 0, 0]);
    ctx.closePath()
  
    // 应用阴影效果
    ctx.shadowColor = '#00ff44';
    ctx.shadowBlur = 68;
    ctx.shadowOffsetX = 5;
    ctx.shadowOffsetY = 20;
    
    // 设置背景色并填充矩形
    ctx.fillStyle = '#ffaadd';
    ctx.fill();
  
    ctx.strokeStyle = 'rgba(26, 26, 26, 0.46)';
    ctx.lineWidth = 2;
    ctx.stroke();
    // 重置阴影设置
    ctx.shadowColor = 'transparent';
    ctx.shadowBlur = 0;
    ctx.shadowOffsetX = 0;
    ctx.shadowOffsetY = 0;
  
    ctx.clip()
    return effectsCanvas;
}

async function test() {
    const img = await loadImage('overlayimage.jpg')
    const backgroundCanvas= createCanvas(1920, 1080)
    
    const titleComposition = await titleRender();
    const effectsCanvas = await drawEffects()

    const backgroundCTX = backgroundCanvas.getContext('2d');
    const effectsCTX = effectsCanvas.getContext('2d');

    backgroundCTX.drawImage(titleComposition, 0, 0, titleComposition.width, titleComposition.height)
    
    effectsCTX.drawImage(img, 77 , 234 , 1766 , 846 )
    backgroundCTX.drawImage(effectsCanvas, 0, 0, effectsCanvas.width, effectsCanvas.height)

    const out = fs.createWriteStream(`comp/ren${1}.png`);
    const stream = backgroundCanvas.createPNGStream();
    stream.pipe(out);

    out.on('finish', () => console.log('Wows'))
    return true;
}
test()

运行效果:圆角矩形后方有平滑绿色阴影,显示正常。

循环绘制异常代码示例

async function test() {
    const img = await loadImage('green/overlay.jpg')
    const backgroundCanvas= createCanvas(1920, 1080)
    
    const titleComposition = await titleRender();
    const effectsCanvas = await drawEffects()

    const backgroundCTX = backgroundCanvas.getContext('2d');
    const effectsCTX = effectsCanvas.getContext('2d');

    backgroundCTX.drawImage(titleComposition, 0, 0, titleComposition.width, titleComposition.height)
    
    for (let i = 0; i<=20; i++) {
        effectsCTX.drawImage(img, 77 , 234 , 1766 , 846 )
        backgroundCTX.drawImage(effectsCanvas, 0, 0, effectsCanvas.width, effectsCanvas.height)
    
        const out = fs.createWriteStream(`comp/ren${1}.png`);
        const stream = backgroundCanvas.createPNGStream();
        stream.pipe(out);
    
        out.on('finish', () => console.log('Wows'))
    }
    return true;
}
test()

运行效果:阴影出现过度叠加的异常。

问题原因

  • effectsCanvas重复绘制未清空:循环中每次调用effectsCTX.drawImage时,都是在同一个effectsCanvas上叠加绘制,之前的阴影和图像会保留,导致每一轮循环都叠加一次阴影。
  • backgroundCanvas未重置:循环中每次向backgroundCanvas绘制effectsCanvas时,没有先清空画布,导致每帧的阴影都叠加在之前的内容上。
  • 文件名未使用循环变量:原代码中写入的文件都是ren${1}.png,会重复覆盖同一文件,无法观察到每帧的变化。

解决方案

方案1:每次循环重新创建画布

在循环内部重新创建背景画布和效果画布,确保每帧都是全新的绘制环境:

async function test() {
    const img = await loadImage('green/overlay.jpg')
    const titleComposition = await titleRender();
    
    for (let i = 0; i<=20; i++) {
        // 每帧重新创建画布实例
        const backgroundCanvas= createCanvas(1920, 1080)
        const effectsCanvas = await drawEffects()
        
        const backgroundCTX = backgroundCanvas.getContext('2d');
        const effectsCTX = effectsCanvas.getContext('2d');

        // 重新绘制基础背景
        backgroundCTX.drawImage(titleComposition, 0, 0, titleComposition.width, titleComposition.height)
        
        effectsCTX.drawImage(img, 77 , 234 , 1766 , 846 )
        backgroundCTX.drawImage(effectsCanvas, 0, 0, effectsCanvas.width, effectsCanvas.height)
    
        // 使用循环变量命名文件,避免覆盖
        const out = fs.createWriteStream(`comp/ren${i}.png`);
        const stream = backgroundCanvas.createPNGStream();
        stream.pipe(out);
    
        out.on('finish', () => console.log(`第${i}帧处理完成`))
    }
    return true;
}
test()

方案2:循环内清空画布(性能更优)

如果不想重复创建画布实例,可以在每帧绘制前清空已有画布内容:

async function test() {
    const img = await loadImage('green/overlay.jpg')
    const backgroundCanvas= createCanvas(1920, 1080)
    const titleComposition = await titleRender();
    
    for (let i = 0; i<=20; i++) {
        // 重新创建效果画布,或用clearRect清空已有内容
        const effectsCanvas = await drawEffects()
        const backgroundCTX = backgroundCanvas.getContext('2d');
        const effectsCTX = effectsCanvas.getContext('2d');

        // 清空背景画布
        backgroundCTX.clearRect(0, 0, backgroundCanvas.width, backgroundCanvas.height);
        // 重新绘制基础背景
        backgroundCTX.drawImage(titleComposition, 0, 0, titleComposition.width, titleComposition.height)
        
        effectsCTX.drawImage(img, 77 , 234 , 1766 , 846 )
        backgroundCTX.drawImage(effectsCanvas, 0, 0, effectsCanvas.width, effectsCanvas.height)
    
        const out = fs.createWriteStream(`comp/ren${i}.png`);
        const stream = backgroundCanvas.createPNGStream();
        stream.pipe(out);
    
        out.on('finish', () => console.log(`第${i}帧处理完成`))
    }
    return true;
}
test()

额外优化点

如果drawEffects的绘制逻辑固定,可以将阴影矩形的绘制结果缓存为静态画布,循环中只需要复用该画布并叠加图像,减少重复绘制阴影的开销。

内容的提问来源于stack exchange,提问作者Aviato

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 02:23:21