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 :-
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 applied
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">