When making a drawing app with HTML5 canvas, you might come up against the problem of making a smooth, variable width line. Eventually you might find that you can make several calls to ctx.quadraticCurveTo(x,y,xc,yc) and get some kind of effect. If you stroke every 2 or 3 points, then as long as the line width is very small, you won’t notice the jaggedness.

Unfortunately, if you try to do this with semi-transparent lines, the effect is truly horrible. To solve this problem, instead of drawing lines, I simply draw very thin shapes, and fill them with a color. This allows for 1) variable thickness and 2) nice transparency. Variable transparency is a problem I haven’t quite figured out yet. It may require composing the shape from individual pixels, which is simply too slow, especially in IE.

What I am writing is a drawing App that works with the Wacom Web Plugin, and therefore varies line thickness based on pressure. I would like to vary opacity too, but this seems problematic right now. A solution that may be possible is to fill the shape with a complex gradient, if it is possible to create semi-transparent stops.

Here is an image of what the program does so far:

Each line is actually a full shape, with varying degrees of opacity. Thickness and thinness is accomplished with pen pressure from the Wacom Intuous5 tablet.

As the line is drawn, a temporary line is shown to the user, when they lift the pen, that line disappears, and a full rendering of it is done as a shape instead of a line. This is slower, but necessary for simplicity. This is meant to be a quick and easy sketch program for increasing your visual library and practicing gesture drawing.

As the person draws, the points are collected up into an array of vector objects which store the x,y, and pressure of the pen at the time that point was hit. The rendering function is like this:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
$this.renderVectorsAsPath = function (vectors) { if ( ! vectors[0] ) return; var x,y,xc,yc,v,p,w,i; var started = false; var ctx = $this._context; var color = ''; var pType = vectors[0].penType; var index = 0; if (pType == 'pen') { color = $this.options.lineColor; } else { color = $this.options.backgroundColor; w = w + (w * p); } ctx.strokeStyle = color; ctx.fillStyle = color; ctx.globalAlpha = $this.wGetOpacity() / 100; console.log( ctx.globalAlpha ); ctx.lineWidth = 0.1; var lastx,lasty; // First we draw the line for ( i = 0; i < vectors.length - 1; i++) { index++; v = vectors[i]; x = v.x; y = v.y; p = v.pressure; w = v.lineWidth; lastx = x; lasty = y; if ( ! started ) { ctx.beginPath(); ctx.moveTo(x,y); started = true; continue; } xc = ( x + vectors[i + 1].x ) / 2; yc = ( y + vectors[i + 1].y ) / 2; ctx.quadraticCurveTo(x,y,xc,yc); } var cv, nv,dx,dy,xr,yr,fx,fy,t; // Then we backup and draw a mirrored line for ( i = vectors.length - 1; i > 0; i-- ) { cv = vectors[i]; nv = vectors[ i - 1 ]; if ( ! nv ) continue; t = cv.pressure * cv.lineWidth; dx = nv.x - cv.x; dy = nv.y - cv.y fx = dx / ( Math.sqrt( (dx * dx ) + (dy * dy ) ) ); fy = dy / ( Math.sqrt( (dx * dx ) + (dy * dy ) ) ); xr = cv.x + t * fy; yr = cv.y - t * fx; xc = ( xr + nv.x ) / 2; yc = ( yr + nv.y ) / 2; ctx.lineTo(xr,yr); } ctx.closePath(); ctx.fill(); } |

The second part is thanks to my father, a mathematician and physicist who helped me figure out how to calculate the points directly. Math is something I am currently working on.

His “equation” as I call it, goes something like this:

I wouldn’t have been able to come up with this on my own.