I have a bit of code that copies from an off-screen source to the on-screen destination.
let destCanvas = document.getElementById("render");
let ctx = destCanvas.getContext('2d');
let srcCanvas = calcCanvas();
ctx.drawImage(srcCanvas,
0,0,srcCanvas.width,srcCanvas.height,
0,destCanvas.height,destCanvas.width,destCanvas.height);
The problem is that, if the user zooms in the browser (Ctrl +), the result is blurry. Each px gets larger and the source pixels have to be stretched over more physical pixels (but the same number of px) in the destination. If anti-aliasing is turned off, then the result is blocky, and that's not what's desired either.
What feels like the correct solution is to render the off-screen canvas at a higher resolution when the browser is zoomed in, but keep the px at a one-to-one ratio, so that devicePixelRatio
is always 1. Of course, the on-screen canvas would need to be enlarged too. Zoom events can be detected as resize
events, but there seems to be no way to replace them with the desired behavior. If this were possible, then the user could zoom in and the result would be large and crisp instead of large and blurry.
Since it's impossible (?) to disable browser zoom, is there a way to do drawImage() so that it works with physical pixels instead of px? It wouldn't be difficult to render the off-screen canvas based on devicePixelRatio
, but it seems that the destination canvas is always treated as though it's made up of abstract px instead of physical pixels. Higher resolution source data doesn't help.
Some follow-up... It turns out that canvas.style.width
and canvas.width
can be used to get close to a solution. style.width
sets the width on the monitor, in px
as a unit of measurement, while width
specifies the number of px
(in the sense of abstract pixels) to use for the canvas. For a fixed style.width
, you can have an arbitrary number of abstract pixels. So, as the user zooms in, increase the number of abstract pixels, while keeping the size in measurement px
constant:
function adjustCanvas() {
let canvas = document.getElementById("render");
// Fix the measured size, to fill the window in this example.
canvas.style.width = document.documentElement.clientWidth + "px";
canvas.style.height = document.documentElement.clientHeight + "px";
// Adjust the number of pixels so that the number of pixels per unit
// length (actual physical length) remains constant.
canvas.width = document.documentElement.clientWidth * window.devicePixelRatio;
canvas.height = document.documentElement.clientHeight * window.devicePixelRatio;
}
This isn't quite pixel-perfect rendering, but it's closer than letting the browser manage it. It is still slightly blurry, probably
because there is some remaining mismatch between physical pixels and the CSS notion of an abstract px
.