Friday, April 8, 2016

Path drawing mode: lines, circles, arcs, curves and other path drawing methods.

Immediate drawing mode vs path drawing mode

IMMEDIATE MODE = EXECUTING A CALL TO A DRAWING METHOD MEANS IMMEDIATELY DRAWING IN THE CANVAS

In the previous examples, we saw how to draw some rectangles using the fillRect(x, y, width, height)and strokeRect(x, y, width, height) methods of the context.
We also learned how to draw a text message using the fillText(message, x, y) and strokeText(message, x, y) methods that draws a text in filled and wireframe mode, respectively.
These methods, along with the drawImage(...) method already seen in section 3.3.3, are "immediate methods": as soon as they are executed, the results are displayed on screen, the drawings are performed, pixels on the canvas area change their colors, etc.
Here is a code extract that will draw 1000 random rectangles in a canvas, using immediate mode rectangle drawing calls. 
  1. var canvas, ctx, w, h;
  2. function init() {
  3.     canvas = document.getElementById('myCanvas');
  4.     ctx = canvas.getContext('2d');
  5.     w = canvas.width;
  6.     h = canvas.height;
  7.     console.time("time to draw");
  8.     for(var i=0; i < 1000; i++) {
  9.        var x = Math.random() * w;
  10.        var y = Math.random() * h;
  11.        var width = Math.random() * w;
  12.        var height = Math.random() * h;
  13.        ctx.strokeRect(x, y, width, height);
  14.     }
  15.     console.timeEnd("time to draw");
  16. }
Lines 12-18 draw 1000 rectangles of random sizes in immediate mode. We also measure the time using the usual console.time(name_of_timer) and console.timeEnd(name_of_timer) that will write in the browser console the time elapsed. Note that console.time(...) and console.timeEnd(...) display results only in the browser's console, not in the JS Bin console.
On a Mac Book Pro from 2015, the result is:
random rectangles time elapsed: around 4s

PATH MODE = FILL A BUFFER THEN EXECUTE ALL BUFFERED ORDERS AT ONCE TO ENABLE OPTIMIZATION AND PARALLELISM

There is another drawing mode called "path drawing mode" where you first send drawing orders to the graphics processor, and these orders are stored in a buffer. Then you call methods to draw the whole buffer at once. There are also methods to erase the buffer's content.
Path drawing mode allows parallelism: if you need to draw 10.000 rectangles, it's better to store the orders in the graphics card, then execute the drawing all at once, rather than doing 10.000 immediate calls tostrokeRect(...) for example. With the buffered mode, the Graphic Processing Unit (GPU) of the graphics card hardware will be able to parallelize the computations (modern graphics cards can execute hundreds/thousands of things in parallel).
Same example as before, this time using the buffered mode for drawing rectangles: http://jsbin.com/zanizu/5/edit
Extract from source code (the part that draws the rectangles):
  1. for(var i=0; i < 1000; i++) {
  2.      var x = Math.random() * w;
  3.      var y = Math.random() * h;
  4.      var width = Math.random() * w;
  5.      var height = Math.random() * h;
  6.     ctx.rect(x, y, width, height); // store a rectangle in path/buffer
  7. }
  8. ctx.stroke(); // draws the whole buffer (the 1000 rectangles) at once
Instead of calling strokeRect(...) or fillRect(...), we just call the rect(...) method of the context (line 7). This is how we can delay the drawing of the rectangles. The 1000 rectangles are stored in a buffer in the hardware.
The call to ctx.stroke() (line 9) or to its sister method ctx.fill() will draw the entire buffer contents in fill or stroke mode.
And here is what the timer gives: a slightly faster execution time. Changing 1000 to 100.000 will give even larger differences.
Path mode is faster than immediate mode!
time to draw 1000 rectangles using path-buffered mode

RESET THE PATH MODE BUFFER

A call to ctx.beginPath() will reset the buffer (empty its contents). We will see many more examples of using the Path drawing mode in another section later on this week. 
  1. // start a new buffer / path
  2. ctx.beginPath();
  3. // all these orders are in a buffer/path
  4. ctx.moveTo(10, 10);
  5. ctx.lineTo(100, 100);
  6. ctx.lineTo(150, 70);
  7. // Draw the buffer
  8. ctx.stroke();

SUMMARY OF PATH MODE PRINCIPLES

    1. Call drawing methods that work in path mode, for example call ctx.rect(...) instead ofctx.strokeRect(...) or ctx.fillRect(...)
    2. Call ctx.stroke() or ctx.fill() to draw the buffer's contents,
    3. Beware that the buffer is never emptied, two consecutive calls to ctx.stroke() will draw the buffer contents twice! Instead, use ctx.beginPath() to empty it if needed.
    4. It is possible to empty the buffer by calling ctx.beginPath().
    5. Path drawing is faster than immediate drawing (parallelization is possible).
  1. Path drawing: lines

    INTRODUCTION

    We have been drawing rectangles so far.
    Let's go a bit further now by introducing the notion of "path drawing". This approach consists in using thectx.moveTo(x, y) method of the context, in conjunction with other drawing methods that end in "To", such as ctx.lineTo(x, y).
    This makes drawing multiple connected lines easier. Consecutive calls to ctx.lineTo(x, y) will store in the path/buffer a set of connected lines that we will draw altogether by a single call to ctx.stroke() orctx.fill().
    Here are the different steps:
      1. Put the "pencil" somewhere with a call to ctx.moveTo(x1, y1). This will be the origin of the first line.
      2. Call the ctx.lineTo(x2, y2) method to draw a line from the previous position (previous step) to the position passed as parameters to the lineTo(...) method. This position will serve as the origin for the next line to be drawn.
      3. Call lineTo(x3, y3) again to draw a line that goes from (x2, y2) to (x3, y3). This line will start at the end of the previous one.
      4. Repeat step 3 to draw more connected lines.
      5. Call the ctx.stroke() or the ctx.fill() methods to draw the path defined by the different lines.
    Note that the call to ctx.stroke() or ctx.fill() will use the current values of the strokeStyle orfillStyle properties. It is possible to call ctx.moveTo(x, y) in the middle of steps 1 through 5 in order to move the pen somewhere else without connecting to the last drawn line.
    Here is an example that draws a grid: http://jsbin.com/zugale/1/edit
    a blue grid made of multiple vertical and horizontal lines
    1. var canvas=document.getElementById('myCanvas');
    2. var ctx=canvas.getContext('2d');
    3. // Vertical lines
    4. for (var x = 0.5; x < 500; x += 10) {
    5.     ctx.moveTo(x, 0);
    6.     ctx.lineTo(x, 375);
    7. }
    8. // Horizontal lines
    9. for (var y = 0.5; y < 375; y += 10) {
    10.     ctx.moveTo(0, y);
    11.     ctx.lineTo(500, y);
    12. }
    13.  
    14. // Draw in blue
    15. ctx.strokeStyle = "#0000FF";
    16.  
    17. // Before the execution of the next line nothing as been drawn yet !
    18. ctx.stroke();
    In this example, the entire grid is drawn during the execution of the last line of code, with the single call toctx.stroke().

    ANOTHER EXAMPLE MIXING FILLED AND WIREFRAME SHAPES (AND IMMEDIATE AND PATH MODES)

    Try this interactive example here: http://jsbin.com/zetupi/4/edit
    two consecutive lines and a filled rectangle in immediate mode
    1. var canvas=document.getElementById('myCanvas');
    2. var ctx=canvas.getContext('2d');
    3. // a filled rectangle in immediate mode
    4. ctx.fillStyle='#FF0000';
    5. ctx.fillRect(0,0,80,100);
    6. // two consecutive lines in path mode
    7. ctx.moveTo(0,0);
    8. ctx.lineTo(100, 100);
    9. ctx.lineTo(100,0);
    10. // draws only the two lines in wireframe mode
    11. ctx.strokeStyle = "#0000FF";
    12. ctx.stroke();
    This example shows that filled and wireframe shapes should be drawn differently (here a filled rectangle is drawn using a call to the fillRect(...) method while a wireframe set of connected lines is drawn using thestroke() method of the context).

    DRAWING A SINGLE PATH MADE WITH DISCONNECTED LINES / PARTS

    Try this interactive example here: http://jsbin.com/lefoze/2/edit
    One path made of two disconnected lines
    1. var canvas=document.getElementById('myCanvas');
    2. var ctx=canvas.getContext('2d');
    3. // first part of the path
    4. ctx.moveTo(20,20);
    5. ctx.lineTo(100, 100);
    6. ctx.lineTo(100,0);
    7. // second part of the path, moveTo(...) is used to "jump" to another place
    8. ctx.moveTo(120,20);
    9. ctx.lineTo(200, 100);
    10. ctx.lineTo(200,0);
    11. // indicate stroke color + draw the path
    12. ctx.strokeStyle = "#0000FF";
    13. ctx.stroke();
    In this example, we simply called the moveTo() method between each part of the path. And we calledstroke() only once to draw the whole path.

Drawing lines with different styles

COMMON MISTAKE: DRAWING THE SAME PATH TWICE

Let's look at the drawing from the last example of the previous section:
two wireframe shapes, disconnected in one single path
Imagine that we would like to draw them with different styles and colors: the shape on the left will stay as it is now (blue, wireframe), while the second shape on the right will be filled, colored in pink. Let's look at how we can do this...

DRAWING TWO PATHS WITH DIFFERENT STYLES, THE WRONG AND THE GOOD WAY!

First, the wrong way!

In this example, we will draw the two parts of the path with different styles: the first part in wireframe mode, and the second part in filled mode.
What we will try first is to call stroke() after the first half of the path, then call fill() after the second half of the path (check this interactive example):
twice drawn path
Here is the code:
  1. var canvas=document.getElementById('myCanvas');
  2. var ctx=canvas.getContext('2d');
  3. // first part of the path
  4. ctx.moveTo(20,20);
  5. ctx.lineTo(100, 100);
  6. ctx.lineTo(100,0);
  7. // indicate stroke color + draw first part of the path
  8. ctx.strokeStyle = "#0000FF";
  9. ctx.stroke();
  10. // second part of the path
  11. ctx.moveTo(120,20);
  12. ctx.lineTo(200, 100);
  13. ctx.lineTo(200,0);
  14. // indicate stroke color + draw the path
  15. ctx.fillStyle = "pink";
  16. ctx.fill();
Hey - it does not work! Weirdly, the two parts of the path are filled in pink! But we called stroke() after the first half of the path was drawn (lines 5-8). Then we called fill() only after the second part of the path was specified (lines 14-19)... so, what happened?
Remember that fill() or stroke() draws the whole path, even if it is disconnected, and even if it has already been drawn!
What happened is:
    1. The call to stroke() has drawn the path corresponding to the lines 5-7. Indeed, the first part of the path (on the left) has actually been drawn once in wireframe mode, and in blue.
    2. Then, the call to fill() at line 20 has drawn the whole path again, but in pink and in filled mode. But this time the path corresponds to lines 5-7 plus lines 14-16 that make up the second shape on the right. So the path that has been drawn this time is made of both of the triangles.
Important: If you do not want to draw parts of the same path several times, you need to draw two different paths, using the ctx.beginPath() method, as shown in the next example.

Now, the right way!

two different paths
Source code:
  1. var canvas=document.getElementById('myCanvas');
  2. var ctx=canvas.getContext('2d');
  3. // first part of the path
  4. ctx.moveTo(20,20);
  5. ctx.lineTo(100, 100);
  6. ctx.lineTo(100,0);
  7. // indicate stroke color + draw first part of the path
  8. ctx.strokeStyle = "#0000FF";
  9. ctx.stroke();
  10. // start a new path, empty the current buffer
  11. ctx.beginPath();
  12. // second part of the path
  13. ctx.moveTo(120,20);
  14. ctx.lineTo(200, 100);
  15. ctx.lineTo(200,0);
  16. // indicate stroke color + draw the path
  17. ctx.fillStyle = "pink";
  18. ctx.fill();
This time, in order to draw the two shapes differently, we defined two separate paths. The way to do this is just to call ctx.beginPath() to start a new path. In this example, the first path has been drawn in wireframe mode, then a new path has been started that is drawn in filled mode.

Drawing lines in immediate mode

Sometimes it might be useful to draw just one line without being in another path.
It's interesting to see how we can write a single "draw line" function that takes the start and end coordinates, the color, the line width, etc.
Here is the code:
  1. function drawLine(x1, y1, x2, y2, color, width) {
  2.     ctx.save();
  3.     // set color and lineWidth, if these parameters
  4.     // are not defined, do nothing (default values)
  5.     if(color)
  6.         ctx.strokeStyle = color;
  7.     if(width)
  8.         ctx.lineWidth = width;
  9.     // start a new path
  10.     ctx.beginPath();
  11.     ctx.moveTo(x1, y1);
  12.     ctx.lineTo(x2, y2);
  13.     ctx.stroke();
  14.     ctx.restore();
  15. }
Notice the save/restore of the context at the beginning/end of the function. This is REALLY a good practice to avoid affecting other functions' context.
  • Line 13 starts a new path so that the function will only draw what it is meant to draw: a single line.
  • Lines 15-17 move the "pen" at (x1, y1) then draw a line to (x2, y2), and the stroke at line 17 makes it appear on the screen.
Here is an example (see online example):
  1. drawLine(0, 0, 100, 100);
  2. drawLine(0, 50, 150, 200, 'red');
  3. drawLine(10, 100, 100, 10, 'green', 10);
Result:
3 lines

Practical example: drawing arrows

In this section, we present a function that draws arrows in a canvas, such as in the illustration below:
example of drawing arrows
You might find multiple implementations on the Web for drawing arrows in a canvas, but the one we are presenting has the advantage of being rather simple and has the possibility to set the color and line width of the arrows.
Let's see some examples of use:
  1. // Adapted from : http://stackoverflow.com/questions/808826/draw-arrow-on-canvas-tag
  2. function drawArrow(ctx, fromx, fromy, tox, toy, arrowWidth, color){
  3.     //variables to be used when creating the arrow
  4.     var headlen = 10;
  5.     var angle = Math.atan2(toy-fromy,tox-fromx);
  6.  
  7.     ctx.save();
  8.     ctx.strokeStyle = color;
  9.  
  10.     //starting path of the arrow from the start square to the end square
  11.     //and drawing the stroke
  12.     ctx.beginPath();
  13.     ctx.moveTo(fromx, fromy);
  14.     ctx.lineTo(tox, toy);
  15.     ctx.lineWidth = arrowWidth;
  16.     ctx.stroke();
  17.  
  18.     //starting a new path from the head of the arrow to one of the sides of
  19.     //the point
  20.     ctx.beginPath();
  21.     ctx.moveTo(tox, toy);
  22.     ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),
  23.                toy-headlen*Math.sin(angle-Math.PI/7));
  24.  
  25.     //path from the side point of the arrow, to the other side point
  26.     ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7),
  27.                toy-headlen*Math.sin(angle+Math.PI/7));
  28.  
  29.     //path from the side point back to the tip of the arrow, and then
  30.     //again to the opposite side point
  31.     ctx.lineTo(tox, toy);
  32.     ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),
  33.                toy-headlen*Math.sin(angle-Math.PI/7));
  34.  
  35.     //draws the paths created above
  36.     ctx.stroke();
  37.     ctx.restore();
  38. }
An arrow is made of one line (the arrow body) and three connected lines (the arrow head). 
As we modify some context properties in that function, we call save() and restore() at the beginning and at the end of the function.
This function can be improved in many ways: adding shadows, using fill() instead of stroke(), which gives strange results when the width is too big, etc.

EXAMPLES

Online example that uses the above code: http://jsbin.com/qekuqotumu/1/edit
  1. drawArrow(ctx, 10, 10, 100, 100, 10, 'red');
  2. drawArrow(ctx, 100, 10, 140, 140, 3, 'black');
Result:
2 arrows

DRAW NICER ARROWS?

On the Web, you will find many different ways to draw arrows.
This Web site is worth reading: http://www.dbp-consulting.com/tutorials/canvas/CanvasArrow.html. It details how to draw arrows with curved heads and different styles for the head. Note however that you will need to modify some parts if you want it to support different line widths, etc.
Screenshot from a demo available on the above Web site:
a clock with arrows
In a next part of the course dedicated to curve drawing in a canvas, we will also show how to draw curved arrows, with very simple code (much simpler than the one used for drawing the clock's arrows above).

Closing a path

The ctx.closePath() method indicates that we would like a closed path: draw from the last point to the first one.
Try this interactive example: http://jsbin.com/jocina/1/edit
closed path
Source code:
  1. var canvas=document.getElementById('myCanvas');
  2. var ctx=canvas.getContext('2d');
  3. // Path made of three points (defines two lines)
  4. ctx.moveTo(20,20);
  5. ctx.lineTo(100, 100);
  6. ctx.lineTo(100,0);
  7. // Close the path, try commenting this line
  8. ctx.closePath();
  9. // indicate stroke color + draw first part of the path
  10. ctx.strokeStyle = "blue";
  11. ctx.stroke();
  • Lines 5-7 corresponds to a path made of two consecutive lines. If we just call stroke() after that, two lines will be drawn on the canvas.
  • Line 10 indicates that we would like a closed path. In that case, the call to stroke() at line 14 will draw the two lines plus an extra line that connects the last point of the path to the first one. It will draw a closed triangle!
Try commenting the line in the online example and see the results!


Drawing circles and arcs

The ctx.arc(cx, cy, radius, startAngle, endAngle, drawInverse) method is useful for drawing arcs of circles. It takes the center of the circle/arc, its radius, the starting angle of the arc (turning clockwise), the ending angle of the arc, and an optional parameter we will talk about later.
drawing circle, coordinate system

TYPICAL USAGE

Typical usage for drawing an arc/circle/ellipse is:
  1. ctx.arc(centerX, centerY, radius, startAngle, endAngle); // clockwise drawing
  2. ctx.arc(centerX, centerY, radius, startAngle, endAngle, false);
The angles are in radians (between 0 and 2*Math.PI). The arc is drawn clockwise. Beware that this may not seem natural if you're used to the trigonometric order.
The last parameter is optional and has a value of false by default. If true, instead of drawing an arc of circle that corresponds to the parameters, it will draw its complementary. See the examples below to see the difference.

EXAMPLE 1: DRAWING AN ARC WITH RADIUS = 50, STARTING ANGLE = 0, END ANGLE = PI/2

Try this example online: http://jsbin.com/tikite/1/edit
  1. ctx.beginPath();
  2. // we ommited the last parameter
  3. ctx.arc(10075500Math.PI/2);
  4.  
  5. ctx.lineWidth 10;
  6. ctx.stroke();
Here is the result:
arc of circle
If we change the last parameter (we omitted it, so it took a value of false by default):
  1. ctx.beginPath();
  2. // we ommited the last parameter
  3. ctx.arc(10075500Math.PI/2, true);
  4.  
  5. ctx.lineWidth 10;
  6. ctx.stroke();
Then, the result is the "complementary" of the previous arc:
complementary of previous arc is drawn

EXAMPLE 2: DRAWING A FULL CIRCLE (FILLED + OUTLINED)

Try this example: http://jsbin.com/gazuba/2/edit
full circle outlined
Source code:
  1. var canvas = document.getElementById("myCanvas");
  2. var ctx = canvas.getContext("2d");
  3. var centerX = canvas.width / 2;
  4. var centerY = canvas.height / 2;
  5. var radius = 70;
  6. ctx.beginPath();
  7. // Add to the path a full circle (from 0 to 2PI)
  8. ctx.arc(centerX, centerY, radius, 0, 2*Math.PI, false);
  9. // With path drawing you can change the context
  10. // properties until a call to stroke() or fill() is performed
  11. ctx.fillStyle = "lightBlue";
  12. // Draws the filled circle in light blue
  13. ctx.fill();
  14. // Prepare for the outline
  15. ctx.lineWidth = 5;
  16. ctx.strokeStyle = "black";
  17. // draws AGAIN the path (the circle), this
  18. // time in wireframe
  19. ctx.stroke();
  20. // Notice we called ctx.arc() only once ! And drew it twice
  21. // with different styles
Notice that we called ctx.arc() only once! And drew it twice, with different styles, with calls to ctx.stroke()and ctx.fill(). Each call drew the defined path in wireframe and in filled mode!

PROPOSED PROJECTS

Project 1 - modify the previous example on http://jsbin.com/gazuba/2/edit in order to get:

half circle

Project 2 - make a small program that draws a smiling head like that (or make something better!)

smiling head

Drawing rounded rectangles: the arcTo(x1, y1, x2, y2, radius) method

INTRODUCTION

There is another method called ctx.arcTo(x1, y1, x2, y2, radius), which is a bit complex to use, but very practical for drawing rounded rectangles.
In fact, the arcTo(...) method draws an arc of a circle depending on some tangents. Let's look at these pictures for a better understanding (original picture from http://www.dbp-consulting.com/tutorials/canvas/CanvasArcTo.html):
arcTo coordinates

TYPICAL USE

  1. ctx.moveTo(x0, y0);
  2. ctx.arcTo(x1, y1, x2, y2, radius);
This method can be confusing. It was defined mainly for drawing rounded shapes like rounded rectangles. We used an excerpt here from the excellent tutorial on the arcTo(...) method available at http://www.dbp-consulting.com/tutorials/canvas/CanvasArcTo.html.
It works like this:
    1. Draw an imaginary line through (x0,y0) and (x1,y1), draw another imaginary line through (x1,y1) and(x2,y2),
    2. Take an imaginary circle of radius r, and slide it up between the two lines until it just touches both lines. The two points at which the circle touches the lines are called the tangent points.
    3. arcTo(x1, y1, x2, y2, r) will draw a line from the current point (x0,y0) to the first tangent point on the line from (x0,y0) to (x1,y1),
    4. It will also draw an arc from that tangent point to the other tangent point on the line from (x1,y1) to(x2,y2) along the circumference of the circle.
    5. Finally, it adds the tangent point where the arc ends up, on the line from (x1,y1) to (x2,y2) to the path as the new current point on the path.

EXAMPLE 1: SIMPLE USE

arcTO example 1
Try this interactive example: http://jsbin.com/bocomu/1/edit
  1. context.beginPath();
  2. context.moveTo(0, 20);
  3. context.arcTo(100, 100, 200, 20, 50);
  4. context.lineWidth = 5;
  5. context.strokeStyle = "#0000ff";
  6. context.stroke();

EXAMPLE 2: DRAW A ROUNDED RECTANGLE

Try this interactive example here: http://jsbin.com/kuqalu/1/edit
rounded rectangle
Source code:
  1. var roundedRect=function(ctx,x,y,width,height,radius,fill,stroke) {
  2.     ctx.beginPath();
  3.    // draw top and top right corner
  4.    ctx.moveTo(x+radius,y);
  5.    ctx.arcTo(x+width,y,x+width,y+radius,radius);
  6.    // draw right side and bottom right corner
  7.    ctx.arcTo(x+width,y+height,x+width-radius,y+height,radius);
  8.    // draw bottom and bottom left corner
  9.    ctx.arcTo(x,y+height,x,y+height-radius,radius);
  10.    // draw left and top left corner
  11.    ctx.arcTo(x,y,x+radius,y,radius);
  12.    if(fill) {
  13.       ctx.fill();
  14.    }
  15.    if(stroke){
  16.       ctx.stroke();
  17.    }
  18. }
  19. var canvas document.getElementById('myCanvas');
  20. var ctx    canvas.getContext('2d');
  21. ctx.strokeStyle 'rgb(150,0,0)';
  22. ctx.fillStyle   'rgb(0,150,0)';
  23. ctx.lineWidth   7;
  24. roundedRect(ctx151516012020truetrue);
In this example, each call to ctx.arcTo(...) draws a side plus a corner. This makes us suspect that thearcTo() method has been designed primarily for drawing rounded rectangles...

EXAMPLE 3 COMPARISON BETWEEN LINETO AND ARCTO

This example at JS Bin is the same as the previous one, except that we added at the end of the roundedRect function the same lines of code that draw the rounded rectangle, but using lineTo instead of arcTo. Just give it a look!
lineTo vs arcTo

Quadratic curves

quadratic curve

INTRODUCTION

Quadratic curves are defined by a starting point (called a "context point"), a control point, and an ending point. The curve fits the tangents between the context and control points and between the control and ending points.
The context point may be defined by a call to the moveTo(x, y) method of the context, or it may be the ending point of a previous path, if we're drawing a path made of several shapes. For example, drawing a line and a quadratic curve will make the endpoint of the line the context point for the quadratic curve.
The control point controls the curvature - if we move the control point farther we get a sharper curve.

TYPICAL USE

  1. context.moveTo(contextX, contextY);
  2. context.quadraticCurveTo(controlX, controlY, endX, endY);
  3. // Optional : set lineWidth and stroke color
  4. context.lineWidth = 5;
  5. context.strokeStyle = "#0000ff";
  6. // Draw!
  7. context.stroke();

EXAMPLE 1: QUADRATIC CURVE

Try this interactive example here: http://jsbin.com/vefivu/1/edit
quadratic curve example 1
Source code:
  1. var canvas=document.querySelector('#myCanvas1');
  2. var context=canvas.getContext('2d');
  3.  
  4. context.beginPath();
  5.  
  6. context.moveTo(100, 20);
  7. context.quadraticCurveTo(230, 200, 250, 20);
  8.  
  9. context.lineWidth = 5;
  10. context.strokeStyle = "#0000ff";
  11. context.stroke();
We set a starting point in line 6: moveTo(...), then set the control and ending points with a call toquadraticCurve(...), at line 7, then set some properties for color, thickness, and finally we call thestroke() method for drawing the curve.

EXAMPLE 2: LINES CONNECTED WITH A QUADRATIC CURVE

Try this interactive example here: http://jsbin.com/sibuse/1/edit
a line followed by a curve followed by a curve
Source code:
  1. context.beginPath();
  2. context.moveTo(100, 20);
  3. context.lineTo(200, 80);
  4. context.quadraticCurveTo(230, 200, 250, 20);
  5. context.lineTo(500, 90);
  6. context.lineWidth = 5;
  7. context.strokeStyle = "#0000ff";
  8. context.stroke();

Practical example: use quadratics for drawing curved arrows

curved arrow
We propose a useful function for drawing curved arrows. The code is a modified version of what has been proposed by several contributors to this thread at StackOverflow.
Source code of the function that draws a curved arrow:
  1. function drawCurvedArrow(startPointX, startPointY,
  2.                          endPointX, endPointY,
  3.                          quadPointX, quadPointY,
  4.                          lineWidth,
  5.                          arrowWidth,
  6.                          color) {
  7.     // BEST PRACTICE: the function changes color and lineWidth -> save context!
  8.     ctx.save();
  9.     ctx.strokeStyle = color;
  10.     ctx.lineWidth = lineWidth;
  11.  
  12.     // angle of the end tangeant, useful for drawing the arrow head
  13.     var arrowAngle = Math.atan2(quadPointX - endPointX, quadPointY -endPointY) + Math.PI;
  14.  
  15.     // start a new path
  16.     ctx.beginPath();
  17.     // Body of the arrow
  18.     ctx.moveTo(startPointX, startPointY);
  19.     ctx.quadraticCurveTo(quadPointX, quadPointY, endPointX, endPointY);
  20.     // Head of the arrow
  21.     ctx.moveTo(endPointX - (arrowWidth * Math.sin(arrowAngle - Math.PI / 6)),
  22.                endPointY - (arrowWidth * Math.cos(arrowAngle - Math.PI / 6)));
  23.  
  24.     ctx.lineTo(endPointX, endPointY);
  25.  
  26.     ctx.lineTo(endPointX - (arrowWidth * Math.sin(arrowAngle + Math.PI / 6)),
  27.                endPointY - (arrowWidth * Math.cos(arrowAngle + Math.PI / 6)));
  28.  
  29.     ctx.stroke();
  30.     ctx.closePath();
  31.     // BEST PRACTICE -> restore the context as we saved it at the beginning
  32.     // of the function
  33.     ctx.restore();
  34. }
This function takes as parameters the start and end points, the control point of the curve, the arrow width, the width of the arrow head.
It computes the angle of the arrow at its endpoint (line 14) in order to compute the rotated endpoints of the two lines of the arrow head (lines 24 and 29).
Notice that once again, as we modify the context properties (color, lineWidth) in the body of the function, we save and restore the context at the beginning / end of the function.

Bezier curves

bezier curve in S

INTRODUCTION

Bezier curves are interesting mostly for drawing "S" shapes or asymmetric curves.
bezier curve control points
Bezier curves are defined by a context point, similarly to quadratic curves, two control points that define two tangents, and an ending point.
The first part of the curve is tangential to the imaginary line defined by the context point and the first control point. The second part of the curve is tangential to the imaginary line defined by the second control point and the ending point.
The best way to understand how they work is to check out one of these interactive applications:

TYPICAL USAGE OF BEZIER CURVES

Source code:
  1. ctx.moveTo(contextX, contextY);
  2. context.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, endX, endY);
  3. // Optional : set lineWidth and stroke color
  4. context.lineWidth = 5;
  5. context.strokeStyle = "#0000ff";
  6. // Draw!
  7. ctx.stroke();

EXAMPLE 1

Try this interactive example here: http://jsbin.com/hodawa/2/edit
bezier curve example
  1. context.beginPath();
  2. context.moveTo(100, 20);
  3. context.bezierCurveTo(290, -40, 200, 200, 400, 100);
  4. context.lineWidth = 5;
  5. context.strokeStyle = "#0000ff";
  6. context.stroke();

EXAMPLE 2 WITH LINES, QUADRATIC, AND BEZIER CURVES IN A PATH

Try this example online: http://jsbin.com/wifowa/1/edit
path with bezier curve, quadratic curve and line in the same, closed path
Extract from source code:
  1. context.beginPath();
  2. context.moveTo(100, 20);
  3. context.lineTo(200, 160);
  4. context.quadraticCurveTo(230, 200, 250, 120);
  5. context.bezierCurveTo(290, -40, 300, 200, 400, 150);
  6. context.lineTo(500, 90);
  7. // TRY COMMENTING THIS LINE OUT
  8. context.closePath();
  9. context.lineWidth = 5;
  10. context.strokeStyle = "#0000ff";
  11. context.stroke();
In this example we use the closePath() method to draw a line between the last path point and the first path point (line 11), so that the drawing looks like a pair of goggles.
Note how the different parts are linked together and make a "path":
path composition explained

INTERESTING, INTERACTIVE TOOL FOR GENERATING CODE THAT DRAWS BEZIER CURVES

This tool is available online: http://www.victoriakirst.com/beziertool/, try it!
Screenshot:
bezier curves code generator

No comments:

Post a Comment