{"id":523,"date":"2024-01-23T20:23:11","date_gmt":"2024-01-23T14:53:11","guid":{"rendered":"http:\/\/codetheory.in\/?p=523---2405b1bb-1bf5-4c8e-93ee-49548741cfab"},"modified":"2024-01-23T20:23:11","modified_gmt":"2024-01-23T14:53:11","slug":"html5-canvas-drawing-lines-with-smooth-edges","status":"publish","type":"post","link":"https:\/\/codetheory.in\/html5-canvas-drawing-lines-with-smooth-edges\/","title":{"rendered":"HTML5 Canvas Drawing Lines with Smooth Edges"},"content":{"rendered":"

In the previous post<\/a> 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.<\/p>\n

<\/p>\n

The Simple Way – Using moveTo() more<\/h2>\n

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

This is how you would store the “last” mouse positions:<\/p>\n

\r\nvar mouse = {x: 0, y: 0};\r\nvar last_mouse = {x: 0, y: 0};\r\n\r\n\/* Mouse Capturing Work *\/\r\ncanvas.addEventListener('mousemove', function(e) {\r\n\tlast_mouse.x = mouse.x;\r\n\tlast_mouse.y = mouse.y;\r\n\r\n\tmouse.x = e.pageX - this.offsetLeft;\r\n\tmouse.y = e.pageY - this.offsetTop;\r\n}, false);\r\n<\/pre>\n

onPaint()<\/code> will also need some changes:<\/p>\n

\r\nvar onPaint = function() {\r\n\tctx.beginPath();\r\n\tctx.moveTo(last_mouse.x, last_mouse.y);\r\n\tctx.lineTo(mouse.x, mouse.y);\r\n\tctx.closePath();\r\n\tctx.stroke();\r\n};\r\n<\/pre>\n

Quite straightforward.<\/p>\n

Problems<\/h3>\n

Take a look at the demo first (drag to sketch):<\/p>\n

<\/pre>\n

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.<\/p>\n

The More Accurate Way – Using quadraticCurveTo()<\/h2>\n

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.<\/p>\n

When using lineTo<\/code>, 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.<\/p>\n

Now one solution to solve that is to create a curve till the mid point<\/em> of the next 2 subsequent sample points<\/em>. 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:<\/p>\n

\r\ntmp_ctx.beginPath();\r\ntmp_ctx.moveTo(ppts[0].x, ppts[0].y);\r\n\r\nfor (var i = 1; i < ppts.length - 2; i++) {\r\n\tvar c = (ppts[i].x + ppts[i + 1].x) \/ 2;\r\n\tvar d = (ppts[i].y + ppts[i + 1].y) \/ 2;\r\n\r\n\ttmp_ctx.quadraticCurveTo(ppts[i].x, ppts[i].y, c, d);\r\n}\r\n\r\n\/\/ For the last 2 points\r\ntmp_ctx.quadraticCurveTo(\r\n\tppts[i].x,\r\n\tppts[i].y,\r\n\tppts[i + 1].x,\r\n\tppts[i + 1].y\r\n);\r\ntmp_ctx.stroke();\r\n<\/pre>\n

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

Thanks to this SO answer<\/a> 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.<\/p>\n

Demo<\/h2>\n

Drag and Draw!<\/p>\n

<\/pre>\n

Not only did we replace lineTo<\/code> with quadraticCurveTo<\/code>, but there has been a complete change in the logic now.<\/p>\n

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<\/code> (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<\/code> is also required before drawing the curves.<\/p>\n

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