I am trying to draw a rectangle on an HTML canvas with pixel-perfect positioning, irregardless of the translation and scale applied to the canvas transform (assume rotation isn't used in this scenario).
In this case, I always want the border of the rectangle to be 1px wide on the screen, irregardless of how zoomed in the rectangle is with the scale applied (which I have working in the demo below), however I also want it drawn at exact pixel coordinates so no antialiasing is applied.
In the code below, I'm pretty sure I have to manipulate the arguments of ctx.rect to add offsets to each position in order to round it to display exactly on the nearest pixel on screen. But I'm not sure what math to use to get there?
As you can see in this demo, with 1.5 scaling applied the rectangle is no longer drawn on pixel perfect coordinates.
const canvases = [
{
ctx: document.getElementById('canvasOriginal').getContext('2d'),
scale: 1,
translateX: 0,
translateY: 0
},
{
ctx: document.getElementById('canvasZoomed').getContext('2d'),
scale: 1.5,
translateX: -0.5,
translateY: -0.5
}
];
for (const { ctx, scale, translateX, translateY } of canvases) {
ctx.translate(translateX, translateY);
ctx.scale(scale, scale);
ctx.beginPath();
ctx.rect(1.5, 1.5, 4, 4);
ctx.lineWidth = 1 / scale;
ctx.strokeStyle = 'red';
ctx.stroke();
}
canvas {
border: 1px solid #ccc;
image-rendering: pixelated;
image-rendering: crisp-edges;
width: 100px;
height: 100px;
}
<canvas id="canvasOriginal" width="10" height="10"></canvas>
<canvas id="canvasZoomed" width="10" height="10"></canvas>
This is my desired result of the scaled image in the snippet above:
EDIT: Please do not ignore translation.