Canvas 2D Context

Part 2. Path Construction

A path to hell is paved with bad primitives.
Rewritten proverb

Path Concepts

In 2D graphics documentation the term path is usually described as a sequence of segments (subpaths) which are rendered as geometric primitives (straight lines, elliptical arcs, polynomial Bézier curves). A special path segment can denote a move to a certain point on the plane. CanvasRenderingContext2D objects implement CanvasPathMethods API and has a set of auxiliary methods and properties for path construction and painting. Combination of path techniques provides the mechanism for creating any arbitrary geometric shape. Here's an example of drawing a triangle:

var canvas = document.getElementsByTagName("canvas").item(0);
var context = canvas.getContext("2d");
context.beginPath();
context.moveTo(10, 10);
context.lineTo(410, 10);
context.lineTo(200, 200);
context.closePath();
context.stroke();

The first stage of path construction in the example above is the invoking of the beginPath() method on the given instance of CanvasRenderingContext2D. After that the moveTo(x, y) method starts a new subpath without drawing by moving the current point to the specified coordinates (10, 10). Then the lineTo(410, 10) segment command is used to draw a line to the given point where x=410 and y=10. Another line command places the current position at the (200, 200) point. The final command is closePath(): it creates a straight line automatically connecting the current point (200, 200) with the initial point of the path. All these commands have outlined the bounding box of the triangle. But it will not be visible to the user until at least a single path painting command is launched. In the example this is the stroke() method: it runs a logical pen along the segments of the path.

Polynomial Curves

Besides lines, path API methods can be used to create quadratic or cubic curves. A quadratic Bézier curve is defined by three values: a starting point, an endpoint, and a single control point. The location of the control point affects the shape of the curve. However, it does not mean that the curve must necessarily pass through that point:

context.beginPath();
context.moveTo(10, 200);
context.lineTo(410, 200);
context.moveTo(10, 200);
context.quadraticCurveTo(105, 1, 210, 200);
context.quadraticCurveTo(315, 410 , 410, 200);
context.stroke();

The moveTo(10, 200) method makes the path jump directly to the point with those coordinate values. Then a straight line segment is drawn to the point (410, 200). The start of the new subpath is reset by another moveTo(10, 200) method to prepare the ground for curves construction. In the method quadraticCurveTo(105, 1, 210, 200) the first pair of coordinates (105, 1) is used to calculate the location of the control point, the second pair (210, 200) determines the end point of the curve. For demonstration purposes another quadratic curve is created (quadraticCurveTo(315, 410 , 410, 200)). This time 315 and 410 are x and y coordinates of the control point, and the pair of (410, 200) coordinate values is the endpoint of the curve. Stroking finishes the graphic operation, and the contours are rendered now.

Cubic Bézier curves employ two control points. The method bezierCurveTo(cpx1, cpy1, cpx2, cpy2, x, y) is used to draw a cubic curve to the point with the x and y coordinates:

context.beginPath();
context.moveTo(10, 200);
context.bezierCurveTo(200, 100, 400, 100, 410, 200);
context.stroke();

The shape of the cubic curve is influenced by two control points: (200, 100) and (400, 100). The end point of the curve is placed at (410, 200). Stroking makes the curve visible.

Elliptical Arcs

The first way to draw an arc is call the arcTo(x1, y1, x2, y2, radius) method:

context.beginPath();
context.moveTo(200, 200);
context.arcTo(20, 400, 400, 400, 200);
context.stroke();

The point (x0, y0) with x=200 and y=200 is the last point in the current path before arc construction begins. Then the method arcTo (20, 400, 400, 400, 200) is called. Rendering depends on the radius and two tangent points of the circumference of the circle to be drawn. The first tangent point (start tangent point) is positioned as the meeting point of the circumference and the half-infinite line that crosses the point (x0, y0) and ends at the point (x1, y1). In the example above the line crosses the point (200, 200) and ends at the point (20, 400). The first step in rendering implies the use of the straight line to connect the point (200, 200) with the start tangent point. After that the second tangent point (end tangent point) is defined. The end tangent point is positioned as the meeting point of circumference and the half-infinite line that crosses the point (x2, y2) and ends at the point (x1, y1). In the example the line crosses point (400, 400) and ends at point (20, 400). When the user agent defines the end tangent point, it draws the arc connecting the start and end tangent points. The end tangent point (400, 400) is added to the current path.

A more simple way to draw an elliptical arc is to employ the arc(x, y, radius, startAngle, endAngle, anticlockwise) method:

context.beginPath();
context.moveTo(200, 200);
context.arc(200, 200, 100 , 0.78, 3.14, false);

In the arc(200, 200, 100 , 0.78, 3.14, false) method the first two parameters are the center point of the circle which circumference is used to draw the arc. The radius of the circumference is equal to 100. Then the start and end angles measured in radians are used to locate the start and end points of the arc. There are 2π radians in the full circle. The first angle is equal to 0.78 radians. The end angle has the rounded value of π equivalent of 180 degrees. The last boolean parameter specifies the order of counting angles. It is optional and has a default value of false: in our case the circumference of the circle is going clockwise.

Rectangles

The rect(x, y, width, height) method provides a location (x, y) and dimension required to draw rectangles. The point at (x, y) is the upper left corner of the rectangle:

var x, y;
for(x=10; x<200; x+=20) {
 y=x;
 context.rect(x, y, 50, 50);
 context.stroke();
}