My idea was to save imageData to array (toDataUrl give some artifacts with canvas shadows). But then i've seen that storing it this way makes drawing really slow. Especially when use canvas filters like blur. It's obvious that they slow down drawing, but difference between having variables in memory, or not, is huge, and also bigger when I use those canvas things like blur. Also, effect when i add for example 100 steps to array doesn't seem to be much different from 2 steps
Is it because those canvas things like filters and blur require a lot of memory? My question is: How can i solve this problem the right way, so that it won't lag the canvas?
I've created demo in codesandbox: https://codesandbox.io/s/black-voice-v5oxhw?file=/src/index.js.
Here is also the code. When you press 'fill history' button, drawing should become very slow.
import "./styles.css";
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
canvas.width = 1000;
canvas.height = 1000;
canvas.style.width = "1000px";
canvas.style.height = "1000px";
const drawButton = document.querySelector(".draw");
const fillHistoryButton = document.querySelector(".fillHistory");
const history = [];
const draw = () => {
ctx.filter = "blur(5px)";
for (let i = 0; i < 200; i++) {
ctx.beginPath();
console.log(i);
ctx.arc(
canvas.width * Math.random(),
canvas.height * Math.random(),
4,
0,
2 * Math.PI
);
ctx.fill();
}
};
const fillHistory = () => {
for (let i = 0; i < 2; i++) {
console.log(i, "adding to history");
history.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
}
};
drawButton.addEventListener("click", draw);
fillHistoryButton.addEventListener("click", fillHistory);
UPDATE I found the solution. It's to use offscreen canvas. The problem is that getImageData method makes canvas use CPU instead of GPU (you can't then manually set it back, it's totally up to browser). And ofc drawing with CPU is slower, but MUCH slower with effects like blur. For some reason offscreen canvas doesn't swap to gpu, so it works well. So the problem is not in big variables, and not in the slowness of getImageData method, but in fact that this method swaps canvas to use CPU
It's not because the memory is filled, it's because calling getImageData
deaccelerates your canvas: it's buffer is moved from the GPU to the CPU.
You can try to keep it always in the CPU instead, which will accelerate the call to getImageData
, by initializing it as
ctx = canvas.getContext("2d", { willReadFrequently: true });
But your filter
would still be slowed down a lot since it wouldn't benefit from the GPU anymore.
But anyway, to store history steps the best is to save the drawing operations, when possible, instead of storing the bitmap.
If you really need to store the bitmap, then use createImageBitmap(canvas)
. This will store only the bitmap and won't require switching back to the CPU.