Canvas Quirks

While using Canvas 2D context for drawing stuff I discovered that the drawing line API can surprise you a bit especially when drawing horizontal or vertical lines. Here is a screenshot with a Canvas element and 5 lines drawn using lineTo() calls:

In case you haven’t noticed, let me tell you what’s wrong with this: the lines are suposed to be 1 pixel width and black. Clearly what you see on the screen is not 1 pixel and the lines are somehow grayish. It looks more like 2 pixels. The code for drawing this looks like this:


<input onclick="draw()" type="button" value="draw" />

<script type="text/javascript">// <![CDATA[
function draw() {
    var context, i, y;

    context = document.getElementById('canvas').getContext('2d');
    y = 20;
    context.lineWidth = 1;
    context.strokeStyle = '#000000';
    for (i = 0; i < 5; i++) {
       context.moveTo(0, y);
       context.lineTo(450, y);
       y += 10;
    }
    context.stroke();
}
// ]]></script>

Let’s change the line width to 2 (line 10 in the above code snippet) and check the result:


Interesting, isn’t it? So the lines width is basically the same, but the color now is really black. Now, let’s try something else: change the line width back to 1 and adjust the y property of the moveTo/lineTo functions with o.5 (line 13/14):

context.moveTo(0, y + 0.5);
context.lineTo(450, y + 0.5);

And surprise, surprise the lines are now exactly 1 pixel and black:

So what’s happening? After some research I think that this is what is happening:

  • When you use integer coordinates like 10 or 15 the drawing algorithm is actually trying to draw a line in between two pixels (for example between the 9th and 10th pixels). As a result it will actually draw two lines.
  • I think the line is slightly lighter than the color set because of the antialiasing algorithm.
  • When you offset the coordinates by 0.5 then you “end” up with drawing the line exactly on one pixel.
  • If you draw a 1 pixel vertical line from (0,0) to (0,200) you will see that this time the line is exactly one pixel wide but the issue of lighter than defined color remains. As there is no other pixel to the left of the 0 pixel on the X axis on the screen you will see only one line.

Using fillRect() function instead of lineTo()

If you don’t like adding those 0.5 to any coordinate when using the lineT0() API then you can actually use the drawing rectangle API. As you probably already guessed, the trick is to draw a rectangle of one pixel for one dimension and the length you need for the other one. So here is the script for drawing 5 horizontal lines:

function draw() {
    var context, i, y;

    context = document.getElementById('canvas').getContext('2d');
    y = 20;

    for (i = 0; i < 5; i++) {
       context.fillRect(0, 10 + y, 450, 1);
       y += 10;
    }
}

And here is the result:

If you are wondering about performance differences between lineTo() and fillRect() then you shouldn’t. fillRect() is probably even faster than lineTo().

You can see here a page that illustrates the differences between lineTo() and fillRect() when using integer coordinates.

One thought on “Canvas Quirks

  1. Pingback: Why does lineTo() change interior pixels? - feed99

Leave a Reply

Your email address will not be published. Required fields are marked *