HTML5 Canvas Drawing Lines with Smooth Edges

In the previous post we kicked off with our painting application using HTML5 canvas. If you remember well, we ended up with a basic app where one can easily draw lines or something else with a “pencil” tool. But there was a problem. When drawing different shapes like circles, the lines/curves had jagged edges. They were not smooth or anti-aliased, whatever you want to call them. This article will aim towards solving those issues.

The Simple Way – Using moveTo() more

If we save the mouse.x and mouse.y in a temporary variable and use that to “move to” everytime onmousemove (inside onPaint function) – then that can improve the result substantially.

This is how you would store the “last” mouse positions:

var mouse = {x: 0, y: 0};
var last_mouse = {x: 0, y: 0};

/* Mouse Capturing Work */
canvas.addEventListener('mousemove', function(e) {
	last_mouse.x = mouse.x;
	last_mouse.y = mouse.y;

	mouse.x = e.pageX - this.offsetLeft;
	mouse.y = e.pageY - this.offsetTop;
}, false);

onPaint() will also need some changes:

var onPaint = function() {
	ctx.beginPath();
	ctx.moveTo(last_mouse.x, last_mouse.y);
	ctx.lineTo(mouse.x, mouse.y);
	ctx.closePath();
	ctx.stroke();
};

Quite straightforward.

Problems

Take a look at the demo first (drag to sketch):


Although improvements in the edges can be noticed, it is still not entirely smooth. Plus, if you look closely, you’ll find some undesirable dots that are probably the joints. Even worse, I realized that it started to lag chrome when I kept on sketching for sometime.

The More Accurate Way – Using quadraticCurveTo()

I am not claiming this to be the best or the most accurate way. On experimenting I just found this to be much more better than any other approach that I tried.

When using lineTo, we basically join all the sample points that we have (mouse positions). In this case, the points where the lines meet do not have a smooth curve, which is why our final sketch does not looks smooth enough.

Now one solution to solve that is to create a curve till the mid point of the next 2 subsequent sample points. When these curves are joined (using the new interpolated points as start/end points) and the sample points as the control points, then the entire transition becomes smooth. No jagged edges anymore! Consider this piece of code that’ll do the trick:

tmp_ctx.beginPath();
tmp_ctx.moveTo(ppts[0].x, ppts[0].y);

for (var i = 1; i < ppts.length - 2; i++) {
	var c = (ppts[i].x + ppts[i + 1].x) / 2;
	var d = (ppts[i].y + ppts[i + 1].y) / 2;

	tmp_ctx.quadraticCurveTo(ppts[i].x, ppts[i].y, c, d);
}

// For the last 2 points
tmp_ctx.quadraticCurveTo(
	ppts[i].x,
	ppts[i].y,
	ppts[i + 1].x,
	ppts[i + 1].y
);
tmp_ctx.stroke();

tmp_ctx is the context of a temporary canvas (we’ll discuss this in more details in a bit). ppts is an array that contains objects with x/y mouse co-ordinates, i.e., the sample points.

Thanks to this SO answer that helped out. It might be a bit hard to grasp but you can try plotting sample points on a graph, based on the formula above. Might make things a bit clearer.

Demo

Drag and Draw!


Not only did we replace lineTo with quadraticCurveTo, but there has been a complete change in the logic now.

As soon as one clicks on the canvas (mousedown) and then drags (mousemove) all the mouse co-ordinates are stored inside an array called ppts (pencil points). At the same time, the array is looped over, using a for loop and using the formula shown above, we draw curves. Ofcourse, clearing canvas using clearRect is also required before drawing the curves.

All this happens on a temporary canvas, which is kinda really interesting. When the mouse click is released (onmouseup), we do 3 things:

  • Copy contents from the Temporary Canvas to the original one using drawImage.
  • Clear the temporary canvas using clearRect.
  • Empty the array.

You might be wondering why did we use a temporary canvas ? On experimenting quite a bit and with the help of that SO answer, I figured out that for a smooth drawing, it is important to store all sample points inside an array and then draw it. This process of storing subsequent points, looping over it and drawing must be repeated as the mouse is clicked and dragged/moved, which means it’ll require clearing up that part of canvas else we’ll end up drawing curves over curves.

Now, we cannot clear our main canvas. Why not ? Because we might have drawn something earlier ? Hence a temporary canvas is essential. Later on you’ll also realize how much useful this temp. canvas will be when implementing other tools like the rectangle, circle and line ones.

temporary canvas

Precisely, whenever you are drawing something at one go (mousedown + mousemove, without mouseup), it’s best to do it on a temporary canvas, because we arn’t finalized yet. As soon as the mouse click is released, i.e., we’re finalized, move everything from the temp. canvas to the main canvas and clear up the temp. canvas.

It is important to note that this temporary canvas takes the width and height of the main canvas and is positioned absolutely. Also note, now we use e.offsetX (chrome, opera) or e.layerX (firefox) to capture the mouse co-ordinates relative to the canvas. I haven’t tested Internet Explorer 9 or above, but it must be working fine with those too.

Phew! :-)

Next Up

It’s been quite a bit of read. Go and experiment a bit if you would like to. Hopefully, I didn’t miss anything. In the next tutorial we’ll cover how to implement more tools like rectangle, circle, and lines.

Share:

7 thoughts on “HTML5 Canvas Drawing Lines with Smooth Edges

  1. ajs

    Hii…Your tutorial is absolutely working fine for me on iPhone5 and iPad.But I am not able to draw lines on my iPhone4 with iOS 6.0 installed. And also I have a scrolling feature for my canvas to be scrolled so it is 1024*1024 size. And also the shadowblur property does not work fine for iOS 5.x

    Reply
  2. Mu

    hello, thank you very much for the open source, it’s very great!
    But jagged edges still exists´╝î if not tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height); in line 82
    I do not know why this is.

    Reply
  3. Vinay Raghavendra

    Hi Rishabh

    your application working fine in desktop but have done this for tabs which has touch events i tried modifying your code with touch events but its not working can you help me with that.

    regards
    vinay

    Reply
  4. walkingp

    Hi, I noticed that if I comment this below line:
    // Tmp canvas is always cleared up before drawing.
    //tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);

    The line will be not so smooth as it was. I wonder why. Thanks.

    Reply

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>