How to Draw 1px Crisp Lines in HTML5 Canvas

javascript
Published on December 2, 2016

Drawing a 1px wide straight line on the canvas element is not as direct as it should be. If you try to draw a 1px straight line, you will end up having a straight line that looks blurry and certainly not 1px wide — infact it is 2px in width.

Even-width straight lines such as 2px, 4px, 6px etc don't suffer from this problem. They look like normal sharp lines.

The subsequent odd-width lines suffer from the same blurriness problem. The reality is that the 3px line is actually a 4px line, the 5px line is actually a 6px line. And so on.

So why does the browser adds an extra pixel to odd-width lines ? And even if it adds the extra pixel, why does it look blurry ?

How the Browser Renders 1px / Odd-Pixel Wide Lines

Suppose you are drawing a straight line horizontally in the canvas. The line is drawn from (50, 10) to (200, 10). Your code would be something like :

var canvas = document.getElementById("my-canvas"); var ctx = canvas.getContext("2d"); ctx.strokeStyle = "#000000"; ctx.lineWidth = 1; ctx.moveTo(50, 10); ctx.lineTo(200, 10); ctx.stroke();

To draw this line, the browser first goes to the initial starting point (50, 10). The line is 1px wide so it has to leave 0.5px on either side. So basically the initial starting point is extending from (50, 9.5) to (50, 10.5). Now browsers cannot show 0.5px in the screen - the minimum threshold is 1px. The browser has no option but to stretch the boundary of the starting point to the actual pixel boundaries on the screen. It adds 0.5px again on either side and fills them with garbage. So now the initial starting point is extending from (50, 9) to (50, 11) — 2px wide. The situation is illustrated below :

Now assume that the width of the line is set to 2px. In this case the initial starting point is extending from (50, 9) to (50, 11). The boundary of the initial starting point falls on the pixel boundaries. So it looks crisp and sharp. The situation is illustrated below :

Now assume the width to set to 3px. In this case the initial starting point is extending from (50, 8.5) to (50, 11.5) - again there is boundary mismatch and the browser adds 0.5px on either side and fills them with garbage. The final result is a 4px blurry line. The situation is illustrated below :

Solving the Problem

The problem is easy to solve actually. The root of the problem is boundary mismatch. If the boundary of the line coincides with the actual pixel boundaries, the line will appear crisp and sharp. To do this, we set the initial starting point to be (50, 10.5) — as compared to the previous (50, 10). So now the initial starting point extends from (50, 10) to (50, 11). The situation is illustrated below :

So to draw a straight line from (50, 10) to (200, 10), we must change the starting and end points to be (50, 10.5) to (200, 10.5).

var canvas = document.getElementById("my-canvas"); var ctx = canvas.getContext("2d"); ctx.strokeStyle = "#000000"; ctx.lineWidth = 1; ctx.moveTo(50, 10.5); ctx.lineTo(200, 10.5); ctx.stroke();

Demo

Drawing 1px line without the 0.5 adjustment:

Drawing 1px line with the 0.5 adjustment:

In this Tutorial