1

I'm using HTML5 lineTo but any stroke greater than 1 creates squarish looking corners on the lines (the stroke extends perpendicular to the path of the line you draw). I want to create a circular brush tip, similar to http://muro.deviantart.com.

Any ideas?

Shi
  • 4,178
  • 1
  • 26
  • 31
Homan
  • 25,618
  • 22
  • 70
  • 107

1 Answers1

3

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.

stslavik
  • 2,920
  • 2
  • 19
  • 31
  • Tip about the lineCap works. Thanks. Regarding the bezier curve... how do I go about "applying" the equation? Do I just use lineTo after I compute each point P ? I have found quadraticCurveTo function and bezier version but those only take 3 or 4 input points. How can I apply bezier curve to the entire brush stroke? – Homan Aug 13 '11 at 00:18
  • I would write a custom function. Select sample points recorded to an array, apply the curve. You could sample the points at which the `mousemove` fires, determine the angle between three points to check if it's of a significant difference for a sample point, flag it if it is, then use it to apply a curve. I would pass the array of flagged points to the function to perform the evaluation. Always start with the `mousedown` and `mouseup` as 0 and n+1. – stslavik Aug 13 '11 at 02:55
  • Let's say the user draws a fast circle, but the sample points are distributed more like the corners of an octagon. You're suggesting to "curvy" up the line using some kind of custom function bezierLine(sample_points) right? I'm not understanding what happens in that function? Does the function draw every single pixel by computing each and every pixel coordinate? Or does the function take a t-step parameter for the Bezier(t) traversal, in which case the function still ultimately draws a line using lineTo, right? – Homan Aug 13 '11 at 19:56
  • Actually, you do have a good point – you could apply it as quadraticCurveTo, bezierCurveTo, or arcTo... I think I may have stumbled onto something, so let me check something out and get back to you shortly. – stslavik Aug 16 '11 at 16:40
  • Thanks. I did end up finding a solution to making smoother lines that I posted here: http://stackoverflow.com/questions/7054272/how-to-draw-smooth-curve-through-n-points-using-javascript-html5-canvas/7058606#7058606 So far I am satisfied with it. – Homan Aug 21 '11 at 18:24