Hi to all,

A quite simple topic today : how to draw a line that has a variable width, one that would look like one of those lines :

Those lines can be used to give more strength to a cartoonish drawing, or to draw more interesting lazer shoots, for instance.

### 1) Let us first look at the non-rounded case :

In fact drawing such a line is quite easy once we realize that what we need to draw is not a line : in fact it is a polygon.

If the line segment we want to draw is AB, the situation looks like this :

What we want to draw in fact is the A1,A2,B2,B1 polygon.

If we call N the normal vector (drawn on the scheme), and w1 and w2 the width in A and B respectively, we will have :

A1 = A + N * w1/2

A2 = A – N * w1/2

B1 = B + N * w2/2

B2 = B – N * w2/2

So how do we find this normal vector N ?

Maths says that if (x,y) defines a vector V , its normal vector coordinates are (-y, x).

N, the vector normal to AB will hence have ( – ( yB – yA ) , ( xB – xA ) ) as coordinates.

But there is an annoying thing about this vector : it depends on AB length, which is not

what we want : we need to *normalize* this vector, i.e. have it to a standard length of 1, so when we later multiply this vector by w1/2, we get the right length vector added.

Vector normalisation is done by dividing the x and y of the vector by the vector length.

Since the length is found using phytagore’s theorem, that makes 2 squares, one square root, and finally 2 divides to find the normalized vector N :

// computing the normalized vector normal to AB length = Math.sqrt( sq (xB-xA) + sq (yB - yA) ) ; Nx = - (yB - yA) / length ; Ny = (xB - xA) / length ;

So now that we can compute the four points, let us link them by a poly-line, and fill the resulting shape : here comes our variable width segment !

Here is the javascript code :

// varLine : draws a line from A(x1,y1) to B(x2,y2) // that starts with a w1 width and ends with a w2 width. // relies on fillStyle for its color. // ctx is a valid canvas's context2d. function varLine(ctx, x1, y1, x2, y2, w1, w2) { var dx = (x2 - x1); var dy = (y2 - y1); w1 /= 2; w2 /= 2; // we only use w1/2 and w2/2 for computations. // length of the AB vector var length = Math.sqrt(sq(dx) + sq(dy)); if (!length) return; // exit if zero length dx /= length ; dy /= length ; var shiftx = - dy * w1 // compute AA1 vector's x var shifty = dx * w1 // compute AA1 vector's y ctx.beginPath(); ctx.moveTo(x1 + shiftx, y1 + shifty); ctx.lineTo(x1 - shiftx, y1 - shifty); // draw A1A2 shiftx = - dy * w2 ; // compute BB1 vector's x shifty = dx * w2 ; // compute BB1 vector's y ctx.lineTo(x2 - shiftx, y2 - shifty); // draw A2B1 ctx.lineTo(x2 + shiftx, y2 + shifty); // draw B1B2 ctx.closePath(); // draw B2A1 ctx.fill(); }

So let us see the result on a small example : drawing variable width segments within a circle with nice hsl colors :

### 2) Let us round things up.

We might want some round ending for our lines, ones just like the canvas allows us to draw by changing the line joins :

http://www.html5canvastutorials.com/tutorials/html5-canvas-line-joins/

So now the scheme looks like this :

It is almost the same case as before, except that we’ll have to draw a half-circle from A1 to A2, and from B2 to B1.

To draw a circle in a canvas, you have to use arc. If you look how it works, you’ll see that it expects a start angle, and an end angle.

The start angle will be the angle between AA1 and the horizontal line, computed using the Math.atan2 function :

var angle = Math.atan2( yA1 - yA, xA1 - xA);

Notice that javascript’s atan2 expects the y as the first argument, and x as the second.

The end angle is just the opposite of the start angle : it is equal to the start angle + PI.

So the javascript code is :

// varLineRounded : draws a line from A(x1,y1) to B(x2,y2) // that starts with a w1 width and ends with a w2 width. // relies on fillStyle for its color. // ctx is a valid canvas's context2d. function varLineRounded(ctx, x1, y1, x2, y2, w1, w2) { var dx = (x2 - x1), shiftx = 0; var dy = (y2 - y1), shifty = 0; w1 /= 2; w2 /= 2; // we only use w1/2 and w2/2 for computations. // length of the AB vector var length = Math.sqrt(sq(dx) + sq(dy)); if (!length) return; // exit if zero length dx /= length ; dy /= length ; shiftx = - dy * w1 ; // compute AA1 vector's x shifty = dx * w1 ; // compute AA1 vector's y var angle = Math.atan2(shifty, shiftx); ctx.beginPath(); ctx.moveTo(x1 + shiftx, y1 + shifty); ctx.arc(x1,y1, w1, angle, angle+Math.PI); // draw A1A2 shiftx = - dy * w2 ; // compute BB1 vector's x shifty = dx * w2 ; // compute BB1 vector's y ctx.lineTo(x2 - shiftx, y2 - shifty); // draw A2B1 ctx.arc(x2,y2, w2, angle+Math.PI, angle); // draw A1A2 ctx.closePath(); // draw B2A1 ctx.stroke(); }

Here’s another circle filled with rounded segments :

In fact i added some other functions to the canvas, i made a github containing all those helpfull functions that you might find here :

https://github.com/gamealchemist/CanvasLib

So that’s all, happy coding to you !