I would like to know what the difference between scaling with drawImage
and CSS is. I have tried both and CSS seems to give slightly smoother results, while drawImage
tends to be a little distorted - especially if I am panning the image, blitting from different source x and y over the course of several animation frames. I have drawn this conclusion after testing at many different scale factors. I have a simple example below.
var img = new Image();
var ctx = document.querySelector("canvas").getContext("2d");
img.src = "img1000x1000.png";// width and height are 1000
ctx.canvas.width = 100;
ctx.canvas.height = 100;
ctx.imageSmoothingEnabled = false;
This runs after img fully loads.
// method 1 uses drawImage to scale
ctx.drawImage(img, 0, 0, 100, 100, 0, 0, 200, 200);
// method 2 draws the img with no scaling and then uses css to scale
ctx.drawImage(img, 0, 0, 100, 100, 0, 0, 100, 100);
ctx.canvas.style.width = 200;
ctx.canvas.style.height = 200;
Keep in mind, I am not always scaling exactly 2x the size and I am blitting from different areas of the img. I only hand in whole numbers to drawImage to keep things as crisp as possible. I also have image-rendering:pixelated
and the other variants of that defined on the canvas in CSS for the CSS scaling to keep things crisp.
I know for my specific example I could just scale the img up once and pan around the final scaled img, but that is not the question. The question is which is more performant. For my application I must scale up an animated tile map on every frame, so I am blitting from multiple sources to a canvas in 1:1 space and then scaling up the final canvas 30 to 60 times per second using requestAnimationFrame.
In the end, they give very similar results. CSS is barely more crisp and true to the original image. Is CSS faster / more efficient? Or is scaling with drawImage more performant?
NOTE / EDIT:
I found that if I'm using standards mode with <!DOCTYPE html>
I must pass string values into style.width
and style.height
for the CSS method. So if you are using standards mode, make sure to set those to "200px"
instead of just 200
or your canvas will not scale.
For the best results you should keep the canvas size the same as its resolution. If the canvas does not have any transparent pixels you should turn off alpha.
Turn off canvas alpha using const ctx = canvas.getContext("2d",{alpha: false})
For most devices scaling is no more expensive an operation than drawing an unscaled image.
The cost of rendering comes downs to the number of pixels that are drawn, not the number of pixels in the source.
drawImage
If the canvas resolution is 200 by 200 and you draw an image ctx.drawImage
that fills it that is 40,000 pixels that need to be rendered.
If that canvas is scaled via CSS rule, even if alpha is off, the canvas element must be composited to be displayed. The cost will be the number of pixels that the canvas size occupies.
If the canvas has alpha as true then it must be composited with the background and thus the rendering cost is the display size of the canvas in pixels.
If the canvas size (CSS) matches the resolution and alpha is off then the canvas does not need to be composited for the final presentation, and there is no additional compositing cost.
canvas
is a visible canvas elementimage
is an image that can fit in GPU RAMBest with alpha off and no CSS scaling
canvas.width = canvas.height = 100;
canvas.style.width = canvas.style.height = "100px";
const ctx = canvas.getContext("2d", {alpha: false});
ctx.drawImage(image, 100, 100);
// Total rendering cost 100 * 100 = 10,000 pixels
Scaling via CSS and alpha off
canvas.width = canvas.height = 200;
canvas.style.width = canvas.style.height = "100px";
const ctx = canvas.getContext("2d", {alpha: false});
ctx.drawImage(image, 200, 200);
// Total rendering cost 200 * 200 + 100 * 100 = 50,000 pixels
Rendering with alpha on, no CSS scaling
canvas.width = canvas.height = 100;
canvas.style.width = canvas.style.height = "100px";
const ctx = canvas.getContext("2d"); // alpha is on
// same as const ctx = canvas.getContext("2d", {alpha: true});
ctx.drawImage(image, 100, 100);
// Total rendering cost 100 * 100 + 100 * 100 = 20,000 pixels