Lesson 01 introduced a number of 2d primitives, namely: arcs, ellipses, lines, points, quads, rectangles, and triangles. However, many shapes do not fit into any such category – like hearts (♥), stars (★), octagons, and Pikachu silhouettes, to name just a few. In this tutorial, you will look at drawing with points and curves, as opposed to more restrictive shape functions. Fonts also rely on curves to describe each glyph, and the latter part of this tutorial delves into Typography (and by extension, strings). Be forewarned: this lesson may be a little tedious, but is necessary to lay down important programming and drawing fundamentals for future lessons.
Processing deals with two types of curves: Bézier and Catmull-Rom. Both are named after the people who developed them, and both involve some complicated math. Fortunately, the complex underlying calculus is handled by Processing’s various curve functions, leaving you to deal with just the coordinates of a few control points.
The best way to grasp curves is to draw a few, then manipulate their control points. Create a new sketch and save it as “curves”. This section will be coordinate-intensive; so, to make things easier, download this “grid.png” file:
Additional sketch assets (images, fonts, and other media) always belong in a sub-folder named “data”. Create a new data folder within your curves sketch now and place the grid.png within it:
Frustratingly, many operating systems hide file extensions – that is, the
.png part of the file. However, if you dig around in your Windows or Mac Finder settings, you can get extensions to show in your file manager.
This grid will lie beneath everything you draw, assisting you in gauging x/y-coordinates. Setup your sketch using the following code:
Note that it is essential to include the file extension (
'grid.png') when referencing the image file.
curve() function is an implementation of Catmull-Rom splines (named after Edwin Catmull and Raphael Rom). Once visualised, the operation of these curves is intuitive.
Add a diagonal line to your “curves” sketch:
A line is drawn between the pairs of x/y coordinates specified, corresponding to the relevant lines on the grid. Now comment out the
line() function and replace it with a
The visual result is exactly the same. If you study the
curve() arguments, you will notice that the four middle values match those of the
curve(0,0, 100,100, 400,400, 500,500)
500,500 arguments represent the control point coordinates, but more on those shortly. Set the stroke to yellow and add another
In this instance, the control point coordinates have been tweaked – resulting in a yellow curve with a slight S-bend:
Now for the part where it all makes sense! To provide a clearer idea of how the control points work, add the following code:
With the control point curves visualised, it should be evident how these determine the direction and amount of curvature. If you have ever used a flexible curve, this will look familiar. If you’re interested to know how smooth curves were ever drawn without computers, then look these up.
curveTightness() function determines how the curve fits, as if you were replacing it with a less/more elastic material. The function accepts values ranging from
0 being the default. To experiment, add a
curveTightness() line above the
stroke('#FFFF00'), so as to affect all of the curves beneath it.
French engineer, Pierre Bézier popularised, but did not create the Bézier curve. He used them in his of design automobile bodies at Renault, devising a system whereby the shape of a curve is controlled by series of anchor and control points. If you have any experience with vector graphics drawing software, these will look familiar. Popular such applications include Adobe Illustrator and Inkscape, where Bézier curves are commonly referred to as “paths”.
Bézier curves are widely used in computer graphics. Their ability to model smooth curves makes them fundamental to vector graphics, animation paths, and fonts. Vector graphics (such as SVG files) can scale to any size – making them resolution-independent. Consider a digital photograph: as you zoom further and further in toward a given point, discernible squares of colour appear. Popular photographic file formats, such as JPG and PNG, are comprised of a pixel grid, the dimensions of which limit the overall resolution. However, in the case of vector-based graphics, the points along a Bézier curve can be recalculated to fit any resolution.
bezier() function takes the following arguments, expanded across multiple lines here for easier comprehension:
Yikes! That’s a lot of arguments. Add some code to draw a Bézier curve, but use four variables to represent the control points:
Notice how all of the
cp__ variables reference the centre of the display window (
250, 250), meaning that all of the control points currently lie where the yellow and pale pink lines cross. To visualise how the curve is manipulated, add a red line connecting the first vertex and control point. Adjust the
cp1y variable to add some curve:
line() functions now share the control point’s x/y-coordinates. Making any adjustments to
cp1x, therefore, affects both functions. Add another red line to connect the lower/second vertex and control point:
Observe how the red handles ‘magnetically’ draw the line toward the control point. Getting the hang of where to place the vertex- and control points for any desired curve takes some practice. Perhaps try the Bézier Game to hone your skills:
You can also develop your Bézier skills using Inkscape (free), Illustrator, or other similar vector graphics drawing software. It is usually easier to draw shapes using such software, then gauge the relevant control points for Processing – which is how the tasks for this tutorial were devised ;)
You can think of vertices as the dots in a connect-the-dots style drawing puzzle. A triangle requires three vertices; a pentagon, five; a five-pointed star (★), ten; and so forth. By connecting vertices using lines and curves, the shape possibilities become limitless. A vertex (singular) is not limited to two-dimensional space – for instance, Blender’s Suzanne (a monkey head) has around five-hundred vertices positioned in 3D space.
Create a new sketch and save it as “vertices”. Within the new vertices folder, add a “data” folder containing the grid.png file from your last sketch.
Now draw a square using vertices:
endShape() functions should be self-explanatory. But, the shape will not automatically close unless you use
endShape(CLOSE). However, an active
fill() will fill a shape however it can:
There are also various parameters one can provide the
beginShape() function to determine how the enclosed vertices are connected, if at all:
beginShape() parameters, consult the reference.
bezierVertex() function allows one to create curved shape lines. There is also a
curveVertex() for Catmull-Rom-type curves, but this lesson will focus on the Bézier type, as these allow for greater control and more graceful curves.
bezierVertex() function takes the following arguments, expanded across multiple lines here for easier comprehension:
To get a better grip on how this function works, we’ll work toward completing the remaining shapes depicted below. The pale blue lines/circles provide a visual indication of where the handles and control-points lie (so there is no need to recreate them).
You will be referencing this image repeatedly through this section. It may be useful to save a copy and open a preview of it alongside your Processing editor.
The S-bend is comprised of two vertices, both of which are attached to control points. Of course, this is a curve, so you could draw it using a
bezier() function. However, the purpose of this section is to introduce shapes. Within a
beginShape(), you can mix
vertex() however necessary. But, the first time a
bezierVertex() is used, it must be prefaced with a
vertex(). Begin a new shape and place the first (in this case, upper) vertex:
Now add the second vertex using
I’ll admit, it’s a bit confusing. But, with the positions of the vertices presented for you in the reference image, it’s really just a matter of writing in the correct sequence of coordinates.
You can think of the heart shape as two lines connecting two vertices. To begin, draw one half of the shape:
All that is left for you to do is complete the right-half of the heart. Add a second
bezierVertex() line and fill-in the arguments:
Round metal coins with square holes in the centre were first introduced in China many centuries ago. The violet-filled shape resembles such a coin, albeit with none of the relief/engraving. Its form requires that one shape be subtracted from another. Processing provides the
endContour() functions for this purpose.
The first challenge is the outer circle. The contour functions are used within a
endShape(), so using an
ellipse function is not an option. However, circles can be drawn using Bézier curves:
You can begin by forming a circle using a diamond shape:
With your vertices in place, you can now convert the
vertex() functions to
bezierVertex() functions. Remember, though: the first point must be a
To save you having to workout where the control points lie, here are the missing arguments:
With the circle in place, you can go about removing a square from the middle. This is a relatively straightforward exercise, but there is one crucial thing to be aware of: one must use reverse winding for the subtracted shape. Read through the circle code again and notice how all of the vertices are plotted in a clockwise manner; this means that the square’s vertices must be plotted counter-clockwise, i.e. opposite to the winding of the shape from which it will subtract.
Place the square’s vertices within a
endContour() function. Of course, you cannot observe the effect unless you add a fill:
Time for a challenge!
Create a new sketch and save it as “bezier_task”. Within the sketch’s folder, create a “data” sub-folder and add a copy of the grid.png, along well as this image file:
Add the following setup code:
When you run the sketch, you will see four Bézier curves. Recreate them using
The curves need not be pixel-perfect replicas, as this is just something to get you used to working with them.
Before looking at Processing’s functions for drawing text, an introduction to strings is required. This section covers some essential Python string operations.
In programming terminology, text is referred to as string data. More correctly, one could refer to a string as a series of characters. You have already encountered this data type in lesson 01, and know that strings are to be wrapped in quotation marks. One may use single- or double-quotes, but always ensure that you close-off using the same type with which you opened.
Create a new sketch and save it as “strings”. Add the following code to get started:
Add another string variable:
... whatsup = 'what's up!'
Because of the apostrophe in
what's, the string is closed before the
s, leaving a dangling third quote with no counterpart. Run the sketch and observe the error message:
There are a few ways to fix this. One can opt for double quotation marks:
whatsup = "what's up!"
or escape the apostrophe character using a backslash:
whatsup = 'what\'s up!
whatsup variable, and add another line using nested single- and double-quotes:
Concatenation and Formatting
+ operator performs arithmetic addition on numbers (integers and floats). However, it becomes a concatenation operator when provided string operands. To concatenate means to connect together, or link, in a series or chain. Try this example:
This displays the following line in the Console:
hello worldwhat's up!is your name really "world"?
Note how concatenating joins strings together exactly as they are defined, with no additional space characters. Spaces – along with any other punctuation you desire – must be explicitly included. Edit your code:
The Console now displays:
hello world. what's up! is your name really "world"?
An alternative to concatenating is string formatting, for which Python provides the
% operator. This works by substituting placeholder symbols with the relevant string values, as opposed to chaining them together in a sequence with other characters. As an example, here is the same line constructed using the
all = ('%s. %s %s') % (hello, whatsup, question)
This approach has its advantages, but for this lesson, we’ll stick to the concatenate operator (
+). For more on this string formatting, consult the reference.
What follows below are descriptions for several string manipulation functions and methods, along with some code that you can add to your working sketch. Feel free to experiment with the arguments to see how things respond. Each demonstration acts on the
all variable which, to restate, now represents:
hello world. what's up! is your name really "world"?
length function returns the total number of characters of any string within the parentheses.
Python slice notation (
) provides a simple, yet powerful means of extracting characters from strings. Add this basic example to your code:
The position (index) of the character you intend to retrieve is placed within the square brackets. Take note that the indexing system is zero-based, meaning that the character indices begin at
0 (and not
A colon (
:) can be used to specify a range of characters. It operates in a few different ways. Add a number to the right of it, and it will return all of the characters up to but not including the specified index:
Add numbers to both the left and right of the colon, and it will return all of the characters beginning at the left index up to, but not including, the right:
A single value to the left of the colon returns everything from the given index to the end of the string:
You may also include negative values:
There are a few other ways in which the colon operator can slice strings, but these should be sufficient for now.
You will reencounter this notation in future lessons dealing with lists and dictionaries.
A Python method looks and behaves much like a function. With no knowledge of object-oriented programming, it’s difficult to explain exactly why methods are methods. However, all that you need to understand for now is the syntactical differences between the two, i.e. how you write a method versus a function. To contrast the two approaches – take the length function as an example:
Were the length function a method, it would be instead be written as:
len() is not a method, so this would result in an error. What is important to note, however, is how the method begins with a period (
.) and is appended to the variable.
What follows below are descriptions for several string methods, along with some code to add to your working sketch. Each example builds on the code before it, so you’ll need to work through all of them, entering each line as you progress.
Returns a version of the string with all lowercase characters converted to uppercase.
Returns a version of the string in title case (the first letter of each word in uppercase).
Returns the total of times the character/character-sequence appears in the given string.
Returns the index of where the term (first) appears in the string. If the substring is not found, returns
If the term appears multiple times, one can provide a second argument indicating the index from which the search should begin:
A third argument can be provided to indicate where along the string the search terminates.
Time for a challenge!
Using just the
all variable, produce a Console output that reads:
Hello. What is your name?
To start you off, here is a snippet of the solution:
To successfully complete the task, you will need to combine various string methods.
With a good grasp of strings, you can move onto displaying text in the display window.
Typography refers to the arranging and styling of text (or, more correctly, type) to make it more legible, readable, and aesthetically appealing. Typographical treatment can truly make or break a design. Headings work best if they stand-out from the rest of your text; letter-spacing should be tighter than word-spacing; cursive fonts are not ideal for road signs.
Early computer fonts were pixel-based, which required variant glyph sets for each font-size. However, modern fonts are vector-based, which is why you can scale text as large as you like without encountering any pixelation. Fonts must be loaded into Processing, but there is a default sans-serif font should you not load any.
If you are unfamiliar with font classifications, serifs are the small lines attached to the tips of characters. By prefixing a term with “Sans”, one implies an absence of whatever follows it; hence a sans-serif fonts have no serifs.
Monospaced fonts may also be serifed, but what defines them is how each character occupies the same amount of horizontal space. To make text more legible, (variable-width) fonts include metrics to specify how far a given character should sit from any neighbours. For example, having an “i” and “m” character occupy the same size container results in some awkward spacing issues – which many monospaced fonts attempt to resolve by adding enlarged serifs to the “i” and cramping the “m”:
That said, monospace fonts are more legible in certain situations – for instance, when it is helpful to have characters line-up in columns:
sam | jan | amy | tim
99 | 359 | 11 | 3
sam | jan | amy | tim
99 | 359 | 11 | 3
This makes monospaced fonts preferable for source code, which is why the default font for the Processing editor (and every other code editor) is monospace.
Create a new sketch and save it as “typography”. Setup your sketch using the following code:
Now add a string variable (note: the line must not wrap):
When you run the sketch, an empty blue display window appears. What follows below are descriptions for several typographic functions, along with some code to add to your working sketch. Feel free to experiment with the arguments to see how things respond.
Draws text to the display window, the colour of which is determined by the active
fill(). The arguments represent the string value, x-coordinate, and y-coordinate respectively. Additional third and fourth argument can be added to specify a width and height for the text area.
Sets the font size (in pixels) to be used in all subsequent
Converts a font to the format used by Processing. The two arguments represent the font name and size, respectively. For a list of fonts available on your computer, use
PFont.list(). It is probably a good idea to place the font files (TTF or OTF) in the sketch’s “data” directory, as not every computer is likely to have the font you have used installed. If you are loading fonts from the data directory, use the full file name (including the extension).
Sets the font for any subsequent
Sets the line-spacing (in pixels) for any subsequent
Sets the text-alignment for any subsequent
text() functions. Accepts the arguments
Calculates and returns the width of any string.
Apple Logo Task
Here is the final challenge before moving onto lesson 03.
The first incarnation of the iconic apple logo was rainbow-coloured (although the bands are incorrectly ordered). One common rumour around the bite involves computer pioneer, Alan Turing – a man who is widely considered to be the father of theoretical computer science and artificial intelligence. Among his many accomplishments, Turing managed to crack the Enigma cypher used to encrypt Nazi communications during World War II. When authorities discovered in 1952 that he was gay, Turing was forced to undergo hormonal treatment. Two years later he was found dead, having committed suicide by biting into a poisoned apple.
You will recreate the logo in Processing, such that the final result looks this:
Create a new sketch and save it as “apple_logo”. Within a “data” sub-folder, add the grid.png image, along with this apple.png file:
Add the following code to get things started:
To assist you in approximating the positions of the anchor and control points, here is an image split into an in-progress and complete version:
Notice how straight-line connections between pairs of control points ensure smooth curves along the perimeter of the apple. Conversely, the leaf handles are bent in different directions resulting in a sharp tip.
That’s it for lesson 02. I hope it was enjoyable if a little tedious. If you are familiar with any markup languages – such as HTML, XML, or SVG in particular – you have probably been cruising through the tutorials thus far. In lesson 03 we’ll begin to look at what really separates a programming language from markup; this includes topics like conditional statements and iteration. You will also explore randomness – one of the most powerful and exciting tools in the creative programmer’s tool-set. For now, though, take a break – you deserve it!
Begin Lesson 03: Control Flow and Randomness