Understanding Quadratic Bézier Curves

August 27, 2011

In a client project I had to draw by hand a quadratic Bézier curve, because the html5 canvas quadraticCurveTo does not return the information about the path drawn and I needed it.

Some basic explanation of a quadratic Bézier curve. You have a start point P1, an end point P2 and a control point C. Both the x and y of each point on the path is dependant on t, which will vary from 0 to 1. t=0 at the begining of the curve and t=1 and the end of the curve.

You draw an imaginary line between P1 and C (P1C line) and another one between C and P2 (CP2 line). For each value of t, you mark an imaginary point on P1C and CP2. The point on P1C is at t of the line, starting at P1, and the point on the CP2 line is at t of the line, starting at C. Let's call those points C1 and C2.

Then, you draw an imaginary line between C1 and C2, and you mark a real point at t of the C1C2 line.

You repeat the procedure for every value of t, where t varies from 0 to 1. All the points you marked on the line C1C2 are showing the quadratic Bézier curve.

Here's a little animation I made to explain it better:

t = 0

You can have a look at the source for a complete understanding, but here's the important part, edited for clarity:

// constants
var CANVAS_WIDTH = 301;
var CANVAS_HEIGHT = 301;
var p1x = 20;
var p1y = 200;
var cx = 140;
var cy = 20;
var p2x = 280;
var p2y = 280;

// basic setup
var $t = $('#bezier-example-1-t span');

var animationCanvas = $('#bezier-example-1 .animation').get(0);
var animationContext = animationCanvas.getContext('2d');
animationCanvas.width = CANVAS_WIDTH;
animationCanvas.height = CANVAS_HEIGHT;

var curveCanvas = $('#bezier-example-1 .curve').get(0);
var curveContext = curveCanvas.getContext('2d');
curveCanvas.width = CANVAS_WIDTH;
curveCanvas.height = CANVAS_HEIGHT;
curveContext.strokeStyle = "#777";
curveContext.lineWidth = 2;
curveContext.beginPath();
curveContext.moveTo(p1x, p1y);
curveContext.stroke();

setInterval(updateDemo, 1000/30);

var t = 0;
var d = 1; // direction

function updateDemo() { // called 30 times/second to animate
if (t > 1 || t < 0) {
d *= -1; // change direction
curveContext.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
curveContext.beginPath();
}
t += 0.01 * d; // continue moving
$t.html(Math.round(t*100)/100);
// update values
var c1x = p1x + (cx - p1x) * t;
var c1y = p1y + (cy - p1y) * t;
var c2x = cx + (p2x - cx) * t;
var c2y = cy + (p2y - cy) * t;
var tx = c1x + (c2x - c1x) * t;
var ty = c1y + (c2y - c1y) * t;

animationContext.save();
// clear old sketch
animationContext.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// draw new line
animationContext.beginPath();
animationContext.strokeStyle = '#aaa';
animationContext.lineWidth = 1;
animationContext.moveTo(c1x, c1y);
animationContext.lineTo(c2x, c2y);
animationContext.stroke();
// draw points on lines
drawPoint(animationContext, c1x, c1y, 2, '#0f0');
drawPoint(animationContext, c2x, c2y, 2, '#0f0');
// draw point on curve
drawPoint(animationContext, tx, ty, 3, '#f0f');
animationContext.restore();

// draw the new Bezier curve segment
curveContext.lineTo(tx, ty);
curveContext.stroke();
}

The code is on Github.