Tutorial 7: Drawing with Functions

Using Functions

You've already been using functions in your sketches, every time that you've drawn a line or set the colour to fill shapes you've called functions with values that specify what they should do, called "parameters".

Functions make code more concise by extracting the common elements of repeated code and making them into code blocks that can be run many times within a program. This can also make code much easier for read and update and reduces the chance of errors being introduced when code is repeated.

Parameters

Functions can take no parameters, or they can take multiple parameters that modify their actions. Parameters are defined for functions inside round brackets after the function's name.

Using a custom function

First, we'll look at how we might use a custom function. The following sketch contains a custom function called face() that we first saw in the lecture on Functions and Recursion.

NOTE: In the desktop version of Processing, when we want to use custom functions in a Processing sketch, we must first move our existing code inside a function called setup(), or split it across two functions, setup() and draw() if our sketch is to be animated. We will look at these functions in more detail later. If you want to run the above code in the desktop version of Processing you'll need to put the code outside of the face function in setup().

The face() function takes three parameters:

An integer called x that defines the horizontal location of the top part, i.e., the bridge, of the nose.

An integer called y that defines the vertical location of the end of the nose. The vertical positions of the nose and mouth are calculated relative to the end of the nose.

An integer called gap that defines the width of the nose, as well as the separation of the eyes and the width of the mouth.

When a function is defined, the type of each parameter, e.g., int is provided before the name of the parameter that will be used to refer to the value provided within the body of the function. Note that this name does not have to match the name of any variables used to pass the function values.

Notice that the parameters are defined in round brackets after the name of the function in the same order that the values must be provided to the function. In the above sketch the face() function is called just once to draw the face with values for x, y and gap of 20, 80 and 26 respectively.

Edit the above sketch to increase the size of the canvas to 200x200 pixels and modify the values for x, y and gap so that they are calculated relative to the values for width and height provided by Processing such that the face is drawn in a similar location and size relative to the display.

The power of defining functions that take parameters is that they can be called with different values without having to re-write the code.

Edit the sketch you created above to generate a different variation of the face each time the sketch is run. You should only have to edit one line of code.

A Custom Drawing Function

In this chapter we'll look at how we can create a function to draw a shape, similar to the functions provided by Peep. The shape we're going to draw is a star. To understand how we're going to draw a star in a function let's first write a sketch to explore the construction.

A regular star shape can be defined using a center point, an inner and outer radius and the number points that the star should have. Let's have a look at how we would draw this without using a function. First, we'll just use the regular drawing functions to draw some "construction lines" so that we can see where the points should be:

In this sketch the inner and outer circles are draw in blue, the radii to the outer points are drawn in green and the radii to the inner points are drawn in red. Creating a sketch like this, using the built-in functions to draw "construction lines" can help us to identify useful code that we can use in the drawing of complex shapes. For example, we can see when we look at the above code that we should be able to combine the loops for drawing the outer points and the inner points:

Notice how this sketch differs from the previous one with that last section of code that uses the beginShape() and endShape() functions to define our custom shape and uses the same calculations that we used to draw the construction lines before to determine where the vertices should be. That last piece of code that we added is the bit that we want to make a function out of. Here's how the function might look to start with:

Notice that we didn't include the code that sets the fill or stroke colours. Like the built-in drawing functions, we want our custom function to use the current fill and stroke values, whatever they might be. And here's how we would use it:

Adding Parameters

In the version of our star() function that we've developed, we've just moved the code that we had previously inside a function. The star() function takes no parameters. i.e., the parentheses after the function name are empty, and returns no value, i.e., has a return type of void.

This first version of the star() function relies on using the global variables x, y, inner, outer and points to draw the shape. In general, it is considered bad programming practice to write functions that rely on global variables, so we should first move those inside the function so that it is self-contained:

Now that our function is self-contained, we can start to turning some of our variables into parameters that can be passed to the function. To do this we, simply remove the definition of the variable from inside the function and add it to the list of parameters inside the round brackets. So to make x and y parameters of the function we would do this:

We now have a function that we can call multiple times with different values for the parameters x, y, inner and outer to draw regular 5-pointed stars at different locations and of different sizes and shapes:

Practice drawing with loops by drawing a grid of stars with our new custom function.

Modify the star() function above to accept an additional parameter to control the number of points for the star.

We can use the new function that we're developed to generate variations using the random() function. allowing us to quickly explore the space of possibilities offered by the function.

Write a sketch to generate the following drawing using your extended star() function.

Notice that one of the few things about the stars that we haven't added as a parameter is the direction of the first point, in all of the versions of this function the first point is always oriented to the right, along the horizontal axis. We could extend the star() function further to add an angle that would control this direction, but this would be out of character with the built-in functions, i.e., rect() doesn't directly support the drawing of rotated rectangles, so we're going to leave this function the way it is. As we'll see in a future tutorial, Processing supports the general transformation of drawing functions, including the rotation of any shape, so this is something that can be done outside of the star() function, just like the setting of stroke and fill colours. This type of decision, i.e., to follow the convention of the built-in drawing functions, is the sort of thing that you might choose to put in comments about a function so that you can understand why the function is coded the way it is at a later stage and how you propose that the function should be used.

Functions Calling Functions

In the previous chapter we developed a custom function and called that from the our main program. There is nothing stopping us from writing functions that call other functions - in fact, we did this when we called any drawing functions above.

A common reason that we might want to write our own functions that call other functions is to "wrap" an existing function with a simpler interface. For example, we might write functions for drawing circles or squares that provide simpler interfaces for drawing these shapes compared to the ellipse() and rect() function that they rely on:

The advantage of writing these types of functions is that the simpler interface reduces the likelihood of making an error, it also helps the code to be a little more self-documenting, i.e., it is more obvious when using one of these functions that the drawing will contain circles or squares than when using the functions for drawing ellipses or rectangles.

Another reason for writing functions that call other functions is to build on the functionality of other functions to achieve more complex tasks. To explore this we'll experiment with the face() function that we used earlier. Here's the function as we used it previously:

We want to write a new function that will allow us to draw a "crowd" of these faces. The first thing we'll want to do if put some bounds around the face, so that the drawing no longer covers the whole display window. We'll do this by adding parameters to control the position and size of the face. We'll start by renaming the current x and y parameters to something so that we can use those for the position of the face, while we're at it we'll also rename the gap parameter to make the code a little easier to read:

The next step is to write our function for drawing a crowd of faces. The function will use five parameters to define a rectangular area of the display to draw the crowd within, and the number of faces to draw the crowd with:

It is important when writing recursive functions that there is some way for the program to know when to stop. In the above example this is done with the test if (w > 10 || h > 10) { ..., this makes sure that when the square becomes too small to subdivide the functions stops calling itself. We can use random() in our tests to recurse different amounts, which can create interesting effects: