The corners can be rounded by setting the line cap.
ctx.lineCap = "round"
You can also apply a bezier curve to the overall line to create a smoother overall line, by, for each point in the line P'0, …, P'n + 1, applying the equation P'k = (k/(n+1))Pk-1+(1-(k/(n+1)))Pk [NB: You might do well to select which points to which you apply the smoothing of the bezier curve by setting a threshold, perhaps on the angle between Pn and Pn+1]
Combining these two techniques with a standard box blur to the line itself will give you a much smoother appearing line.
Edit
From what I can tell, there's actually a number of ways to do this – which you use is entirely up to you. I'll give you an example, and let you decide: Assume you have a path drawn from a beginning point pm (mousedown) to an endpoint (mouseup) pn. That path is made up of subpaths (the points joined by miters). We can draw the path to the context from p0 to p1 with lineTo() and stroke() as normal. Just from watching console output, the points at which the subpaths join is the mousemove event firing. Record these points in order in an array.
Of course if we draw this to the main context, we have a problem removing it, so this should be done to a buffer context (an additional canvas element, for instance). The buffer is cleared, and we use the points of the miters to calculate the curve. bezierCurveTo prints a cubic function (B(t) = (1-t)3P0+3(1-t)2P1+3(1-t)t2P2+t3P3, t ∈ [0,1]. Step through your array (think for loop) recalculating the line with those points, updating the curve from P0 to Pn-3. (Doing quick head-math. You might need to think over this endpoint. All of this is dependent upon which arcing equation you use).
So let me see if I can do something with this... I'm not testing it so I guarantee bugginess.
// Assume:
// bfr = buffer context.
// ctx = main context.
// md = boolean value for mousedown
// pts = []; <-- already contains lp (below) at pts[0];
// We've also recorded Pm in associative array lp [last point]
// Draw is fired on mousemove. Mousemove records a current point in associative array cp
draw = function() {
if(md) {
bfr.beginPath();
bfr.moveTo(lp.x-.5, lp.y-.5);
bfr.lineTo(cp.x-.5, cp.y-.5);
pts.push({cp.x, cp.y});
bfr.stroke();
}
}
// Optionally, you could make this function recursive.
// This assumes that you want to estimate the curve based on the whole line.
bezier = function(pts) {
ctx.beginPath();
ctx.moveTo(pts[0].x, pts[0].y);
for( var i = 0; i < pts.length - 3; i++ ) {
ctx.bezierCurveTo( pts[i+1].x, pts[i+1].y, pts[i+2].x, pts[i+2].y, pts[i+3].x, pts[i+3].y);
}
ctx.stroke();
}
Again, this is what I see – someone else may have an entirely different and I'm sure better interpretation. I'm trying to tear chunks of things I've done and put them together with some new code quickly to give you some idea.