0

I'm working with HTML Canvas and have noticed that if I create a path, with let's say a 90 degree angle, that corner isn't drawn correctly; it has antialiasing. See the code below:

<!DOCTYPE html>
<html>
<body>

<canvas id="myCanvas" width="600" height="550" style="width: 480px; height: 440x;"></canvas>

<script>
const c = document.getElementById("myCanvas");
const ctx = c.getContext("2d");

ctx.beginPath();
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(200.5, 20.5);
ctx.lineTo(200.5, 100.5);
ctx.lineTo(20.5, 100.5);
ctx.stroke();
</script>

</body>
</html>

Here's a close-up screenshot of the corner:

enter image description here

Notice the corner pixel isn't black as it should be.

Now:

  1. I can draw that path as two separate lines by overshooting the corner slightly, and get the corner rendering properly. But then I can't fill it (to fill, I must have a connected path).
  2. I could overshoot, then go back 180 degrees, and then continue on. This would create a nice corner and have a continuous path. However, if I ever wanted a transparent stroke, that overlap would be noticeable.
  3. lineJoin = "miter" is already set as default - the miter function does not work properly

Is there a way to solve this problem?

1 Answer 1

0

This is (most certainly1) a CSS scaling issue. Your <canvas> intrinsic size and its rendered size don't match a 1/1 scale. So the renderer will upscale your canvas to match the CSS size and introduce antialiasing.

When you check the pixel there, the alpha value is actually 255.

While the best would be to have your canvas match the monitor's resolution2, you could also set the CSS image-rendering to pixelated.

const c = document.getElementById("myCanvas");
const ctx = c.getContext("2d");

ctx.beginPath();
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(200.5, 20.5);
ctx.lineTo(200.5, 100.5);
ctx.lineTo(20.5, 100.5);
ctx.stroke();
console.log(...ctx.getImageData(200, 100, 1, 1).data) // 0 0 0 255
canvas { image-rendering: pixelated; }
<canvas id="myCanvas" width="600" height="550" style="width: 480px; height: 440x;"></canvas>


1. I noticed at least Chrome and Firefox (so probably Skia) actually have a bug in their software rendering, where that pixel would not be opaque. The GPU accelerated ones don't have this issue.

2. This answer contains some code to do it right™ using a ResizeObserver. A simple canvas.width = width * devicePixelRatio; canvas.height = height * devicePixelRatio; ctx.scale(devicePixelRatio, devicePixelRatio) would do though.

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you for your answer. My device pixel ratio is set to 1.25, hence why my css height and width have been amended accordingly. Your code still produces the same antialiased pixel in the corner for me. Do you see the lighter pixel in the corner from your device?
Not in my version, no. What's the output of the console on your side?
Console log gives 0 0 0 191
Ah, so you are hitting the software rendering bug I mentioned. Weird you are not having hardware acceleration. Unfortunately I'm not sure there is any workaround for that one. I'll try to open issues to get it sorted put. But different pathes with the same angle won't necessarily render the same...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.