HTML5 Canvas - Line Width and its effect on quality

I was experimenting with some HTML5 canvas features to create some animation with lines. I ran into some very interesting effect of rendering on canvas. Let me first describe what I was trying to do and what I ended up with.

  • Create a canvas element on page with white background.
  • Draw a straight line of thinckness T from point (x1,y1) to point (x2,y2) with red color.
  • Draw a straight line of thinckness T from point (x1,y1) to point (x2,y2) with white color.

If you look at description, the end result should be that there is no line visible to user because white line will be drawn over the red line. But the result was not what I expected. I ended up with following view (two parallel red lines).

canvas lines

My first reaction was that it may be some bug in IE. Then I tried on all browsers and I saw same behaviour. So definitely not a bug. Then I looked at image and I noticed that color is not sharp red that I had set in first step, it was little lighter shade of red. Next thought came to mind that may be it has something to do with opacity. After looking at HTML5 specification, I noticed that default value of opacity is 1. So that is not the case. If it was case of opacity then whole line will be visible with a lighter shade.

Next thought was to try some different value of line thinckness so I could see the behaviour closely in bigger width. I noticed that when I had line width as an ODD number, I was left with two parallel lines. And when the line width was an EVEN number, the result was as expected that is no line visible to user. This now started to make sense that it has to do with how rendering of lines is done in graphics engine. It was back to Computer Graphics 101. And then I dug up some documentation on HTML5 canvas rendering and found the answer to this puzzle.

Let me first show you the code that I was using for my experiment.

function drawLines(canvasId) {
 var canvasElem = $('#' + canvasId);
 if (null != canvasElem) {
   var width = canvasElem.width();
   var height = canvasElem.height();
   if (canvasElem[0].getContext) {
      var drawCtx = canvasElem[0].getContext('2d');
      if (null != drawCtx) {
      drawCtx.lineWidth = 15;

      drawCtx.strokeStyle = "#f00";
      drawCtx.moveTo(0, 50);
      drawCtx.lineTo(200, 50);

      drawCtx.strokeStyle = "#fff";
      drawCtx.moveTo(0, 50);
      drawCtx.lineTo(200, 50);

Effect of line width in Canvas drawing

You can set width of line in canvas using lineWidth property on drawing context as shown in bold in code above. By setting a value of line width (e.g. 15), you are telling the rendering engine as follows:

Draw a line from point (x1,y1) with a pencil of width 15. The center of the pencil should be places at (x1,y1) when drawing the line.

What this means is that your line width will be divided by 2 and each half will be placed on top and above and center line. This means if I have line width of 15, the line will have thickness of 7.5 at top and below the center (x1,y1). Well now we have a problem that pixels can't be split into half or fractions. To overcome this, canvas rendering engine compensates by padding extra 0.5 thickness with a lighter color. So at the end you end up with a line which is 16 units wide and not 15. When I put white line on top of red line, there is no color lighter than white so canvas rendering engine decided not to compensate with extra 0.5 at top and bottom. So I ended up with two parallel lines at a distance of width of original line width that I intnded to use. Following image shows the effect of compensation with a lighter shade of red.

canvas line with compensation

Compensate for odd width

If you have to draw line with odd number width, then you will have to offset the center of your line by 0.5 up or down. That way rendering will happen at boundary of pixel and not in middle. And you will always have a sharp line with no residue at end edges.

comments powered by Disqus




Monthly Posts