I'm playing with drawing on html canvas and I'm little confused of how different coordinate systems actually works. What I have learned so far is that there are more coordinate systems:
- canvas coordinate system
- css coordinate system
- physical (display) coordinate system
So when I draw a line using CanvasRenderingContext2D
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(3, 1);
ctx.lineTo(3, 5);
ctx.stroke();
before drawing pixels to the display, the path needs to be
- scaled according to the ctx transformation matrix (if any)
- scaled according to the ratio between css canvas element dimensions (
canvas.style.width
andcanvas.style.height
) and canvas drawing dimensions (canvas.width
andcanvas.height
) - scaled according to the
window.devicePixelRatio
(hi-res displays)
Now when I want to draw a crisp line, I found that there are two things to fight with. The first one is that canvas uses antialiasing. So when I draw a line of thikness 1
at integer coordinates, it will be blurred.
To fix this, it needs to be shifted by 0.5 pixels
ctx.moveTo(3.5, 1);
ctx.lineTo(3.5, 5);
The second thing to consider is window.devicePixelRatio
. It is used to map logical css pixels to physical pixels. The snadard way how to adapt canvas to hi-res devices is to scale to the ratio
const ratio = window.devicePixelRatio || 1;
const clientBoundingRectangle = canvas.getBoundingClientRect();
canvas.width = clientBoundingRectangle.width * ratio;
canvas.height = clientBoundingRectangle.height * ratio;
const ctx = canvas.getContext('2d');
ctx.scale(ratio, ratio);
My question is, how is the solution of the antialiasing problem related to the scaling for the hi-res displays?
Let's say my display is hi-res and window.devicePixelRatio
is 2.0
. When I apply context scaling to adapt canvas to the hi-res display and want to draw the line with thickness of 1
, can I just ignore the context scale and draw
ctx.moveTo(3.5, 1);
ctx.lineTo(3.5, 5);
which is in this case effectively
ctx.moveTo(7, 2);
ctx.lineTo(7, 10);
or do I have to consider the scaling ratio and use something like
ctx.moveTo(3.75, 1);
ctx.lineTo(3.75, 5);
to get the crisp line?