Variable width lines in html5 canvas

varLineRoundedHi 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 :

varLineExpl

example of a line with variable width

varLineRoundedExpl

example of rounded line with variable width

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 :

varLineScheme

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 :

Little sample of some variable width segments.

Little sample of some variable width segments.

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 :

varLineRoundedScheme

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 :

varLineRounded

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 !

Advertisements
This entry was posted in Uncategorized and tagged , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s