javascriptcanvashtml5-canvasnode-canvas

Too much shadow when drawing on canvas in loop


I have been trying to make a shadow effect in canvas by first creating a rounded rectangle and adding a shadow to it, everything seems to be working fine if I do this once without a loop. However when I tried doing this in loop for video frames, I am getting too much shadow applied on the background :-

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()
  
    // Apply drop shadow effect
    ctx.shadowColor = '#00ff44';
    ctx.shadowBlur = 68;
    ctx.shadowOffsetX = 5;
    ctx.shadowOffsetY = 20;
    
  
    // Set the background color and fill the rectangle
    ctx.fillStyle = '#ffaadd';
    ctx.fill();
  
    ctx.strokeStyle = 'rgba(26, 26, 26, 0.46)';
    ctx.lineWidth = 2;
    ctx.stroke();
    // Reset shadow settings
    ctx.shadowColor = 'transparent';
    ctx.shadowBlur = 0;
    ctx.shadowOffsetX = 0;
    ctx.shadowOffsetY = 0;
  
    ctx.clip()
  
    //ctx.drawImage(cropedFrame as any, downscaleX, downscaleY, scaledWidth, scaledHeight);
  
    return effectsCanvas;
  
    
  }

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

    console.log('hii')
    
    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()

This is the final result I got :- A smooth green shadow behind the rounded ractangle

However, I tried doing something similar for video frames in loop :-

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

    console.log('hii')
    
    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()

When I tested the code, I am getting too much shadow appliedenter image description here


Solution

  • You'll need to clear the destination canvas every frame if you don't want to accumulate the glow on it.

    Try the buttons rendered by the snippet below to see the difference between drawing the blue "title" on every frame or only once. I added a bit of animation to the overlay layer too, so it's easier to see how the trails accumulate.

    function createCanvas(width, height) {
      const c = document.createElement("canvas");
      return Object.assign(c, { width, height });
    }
    
    const titleRender = async () => {
      const canvas = createCanvas(1920, 1080);
      const ctx = canvas.getContext("2d");
      ctx.fillStyle = "blue";
      ctx.fillRect(0, 0, 1920, 1080);
      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();
    
      // Apply drop shadow effect
      ctx.shadowColor = "#00ff44";
      ctx.shadowBlur = 68;
      ctx.shadowOffsetX = 5;
      ctx.shadowOffsetY = 20;
    
      // Set the background color and fill the rectangle
      ctx.fillStyle = "#ffaadd";
      ctx.fill();
    
      ctx.strokeStyle = "rgba(26, 26, 26, 0.46)";
      ctx.lineWidth = 2;
      ctx.stroke();
      // Reset shadow settings
      ctx.shadowColor = "transparent";
      ctx.shadowBlur = 0;
      ctx.shadowOffsetX = 0;
      ctx.shadowOffsetY = 0;
    
      ctx.clip();
    
      return effectsCanvas;
    };
    
    async function sleep(number) {
      return new Promise((resolve) => setTimeout(() => resolve(), number));
    }
    
    async function test(drawBgEveryFrame) {
      const backgroundCanvas = createCanvas(1920, 1080);
    
      const titleComposition = await titleRender();
      const effectsCanvas = await drawEffects();
    
      const backgroundCTX = backgroundCanvas.getContext("2d");
      const effectsCTX = effectsCanvas.getContext("2d");
    
      for (let i = 0; i <= 20; i++) {
        if (i === 0 || drawBgEveryFrame) {
          backgroundCTX.drawImage(
            titleComposition,
            0,
            0,
            titleComposition.width,
            titleComposition.height,
          );
        }
        backgroundCTX.drawImage(
          effectsCanvas,
          0,
          Math.cos((i / 20) * 6.283) * 50,
          effectsCanvas.width,
          effectsCanvas.height,
        );
    
        document.getElementById("img").src = backgroundCanvas.toDataURL();
        await sleep(100);
      }
    }
    <button onclick="test(false)">Go, without clearing the frames</button> <button onclick="test(true)">Go, but draw background every frame</button>
    <img id="img" style="width:100%;border:1px solid black">