Learning objectives
In this code along, we will give a short introduction to graphics in Python, which will help us in future code alongs to visualize the Game of Life and other cellular automata. We will then use this introduction to make a simple but beautiful drawing of a face that showcases our artistic prowess.
Code along summarypy
Setup
To complete this code along as well as other code alongs relying on graphics in this and following chapters, you will use a popular Python graphics library called pygame
, which will help us draw objects and generate images.
First, install pygame
by opening a command line terminal and executing the command pip3 install pygame
(on macOS/Linux) or pip install pygame
(on Windows).
In python/src
, create a folder drawing
, then create main.py
inside it with the following contents. Note that we import the pygame
package at the top.
import pygame def main(): print("Drawing a head.") if __name__ == "__main__": main()
Understanding Pygame: our library for drawing
The pygame
library allows us to create a graphical window for our programs. We will cover more about object-oriented programming in a future chapter, but for now, think of a pygame
window as a rectangular screen with a specified width and height, similar to the screen you are reading this on. The width and height are measured in pixels, where each pixel is a tiny point on the screen that can be colored individually.
In the RGB color model, every rectangular pixel on a computer screen emits a single color formed as a mixture of differing amounts of the three primary colors of light: red, green, and blue (hence the acronym “RGB”). The intensity of each primary color in a pixel is expressed as an integer between 0 and 255, inclusive, with larger integers corresponding to greater intensities.
A few colors are shown in the figure below along with their RGB equivalents; for example, magenta corresponds to equal parts red and blue. Note that a color like (128, 0, 0) contains only red but appears duskier than (255, 0, 0) because the red in that pixel is less intense.

The functions from pygame
that we will need in this and the next code along are listed below. However, a best practice for using Python libraries is to refer to the online API provided here to understand functions and objects in the library: https://www.pygame.org/docs/. We will say more about these functions as they are needed. (Links are provided for specific documentation of each function below for those who are interested.)
pygame.Surface.fill()
: Fills the Surface with a solid color. (link)pygame.draw.circle()
: draw a circle. (link)pygame.draw.rect():
draw a rectangle. (link)pygame.
init
()
: initialize all imported pygame modules. (link)pygame.
Surface
()
: pygame object for representing images. (link)pygame.image.save()
: save an image to file. (link)pygame.quit()
: uninitialize all pygame modules. (link)
Creating a blank canvas
In main()
, we initialize pygame
by calling pygame.init()
. Even though we won’t open a window, pygame
still needs to set up its internal modules before we can create surfaces or draw anything.
def main(): print("Drawing a head.") # Initialize pygame but don't open window pygame.init()
Rather than placing all our drawing code inside def main()
, we will call a function named draw_face()
. This function will not take any inputs or return anything, but it will contain code for drawing our image.
def main(): print("Drawing a head.") # Initialize pygame but don't open window pygame.init() draw_face()
After we have drawn the face, we call pygame.quit()
to shut down all Pygame modules and release all resources. This step is important to prevent memory leaks and save memory and properly close Pygame’s internal systems.
def main(): print("Drawing a head.") # Initialize pygame but don't open window pygame.init() draw_face() pygame.quit()
We will next implement draw_face()
. The first thing that we will do is to create a canvas object, which we will call c.
First, in main.py
, we will create a Pygame “surface” by calling the function pygame.Surface()
, which takes a tuple (width
, height
) of two integers as input and returns a Surface
object that is width
pixels wide and height
pixels tall. The Surface
object acts like a virtual canvas that we can draw on. “Drawing” on surface
will not show anything on the screen as of yet; surface
only exists in memory.
def draw_face() -> None: """ Draws a simple cartoon face onto the given Pygame surface. The face consists of: - A large white head (circle) - Two black eyes (circles) - A small black nose (circle) - A red rectangular mouth Args: None Returns: None """ # Create an off-screen surface width, height = 1000, 2000 surface = pygame.Surface((width, height)) # to fill in...
Note: The functionpygame.Surface((width, height))
is a constructor in Python. This creates a newSurface
object and stores that in the variable surface. ThisSurface
objectsurface
, has functions “in it” which can be accessed by doingsurface.function_name()
. We will discuss constructors and object-oriented programming in greater detail in the future.
Next, we will define some colors using RGB values. black = (0, 0, 0)
represents the absence of light, white = (255, 255, 255)
is full light for all three channels, and red = (255, 0, 0)
is just red. Pygame functions will take in these 3-int tuples for color parameters.
def draw_face() -> None: """ Draw a simple cartoon face on a new off-screen Pygame Surface and save it. The face consists of: - A large white head (circle) - Two black eyes (circles) - A small black nose (circle) - A red rectangular mouth Args: None Returns: None """ # Create an off-screen surface width, height = 1000, 2000 surface = pygame.Surface((width, height)) black = (0, 0, 0) white = (255, 255, 255) red = (255, 0, 0) # fill in
Next, we paint the entire surface with black by calling surface.fill(black)
.
def draw_face() -> None: """ Draw a simple cartoon face on a new off-screen Pygame Surface and save it. The face consists of: - A large white head (circle) - Two black eyes (circles) - A small black nose (circle) - A red rectangular mouth Args: None Returns: None """ # Create an off-screen surface width, height = 1000, 2000 surface = pygame.Surface((width, height)) black = (0, 0, 0) white = (255, 255, 255) red = (255, 0, 0) # Fill canvas with black surface.fill(black) # fill in
Note:surface.fill
is a method (a function that is part of theSurface
object) that fills the entire surface with the input color. More information on how to define these functions for objects will be discussed in a future code along.
We can convert surface
to an image by saving it to a PNG so that the image file can be viewed outside of Python. We call the function pygame.image.save()
to produce this PNG, which we call "fun.png"
.
def draw_face() -> None: """ Draw a simple cartoon face on a new off-screen Pygame Surface and save it. The face consists of: - A large white head (circle) - Two black eyes (circles) - A small black nose (circle) - A red rectangular mouth Args: None Returns: None """ # Create an off-screen surface width, height = 1000, 2000 surface = pygame.Surface((width, height)) black = (0, 0, 0) white = (255, 255, 255) red = (255, 0, 0) # Fill canvas with black surface.fill(black) # save to file pygame.image.save(surface, "fun.png")
Even though we have not finished creating our face, let’s run our code so that we can see what is produced.
STOP: In a new terminal window, navigate into our directory usingcd python/src/drawing
. Then run your code by executingpython3 main.py
(macOS/Linux) orpython main.py
(Python).
After running your code, you should see the achievement of modern art shown below appear as fun.png
in your python/src/drawing
directory.

The graphics coordinate system
Before adding a head to our drawing, we need to understand the
coordinate system. In the Cartesian plane that we are accustomed to working with in mathematics, increasing x-coordinates extend to the right, and increasing y-coordinates extend upward (see figure below).pygame

However, graphics uses a different standard that dates to the foundation of computing. Early computers drew an image to the screen starting in the top left corner of the screen and extending right and down. As a result, many graphics packages (pygame
included) still use the standard of viewing the top left corner of a window as an “origin”, with increasing x-coordinates extending to the right, and increasing y-coordinates extending downward (see figure below).

Adding a head
We will now add a head to surface, which will appear as a white circle. To do so, we will call the pygame.draw.circle()
function. Remember when your geometry teacher told you that a circle is defined by its center and its radius? We know that you don’t remember it, but it is nevertheless true. We will now use this fact by calling pygame.draw.circle()
, which takes four parameters as input:
surface
(pygame.Surface): the surface you want to draw on.color
(tuple or pygame.Color): the color of the circle, e.g.(255, 0, 0)
for red.center
(tuple of two ints): the(x, y)
coordinates of the circle’s center.radius
(int): the radius of the circle in pixels.
We want the head to lie in the top part of the rectangle, halfway across from left to right. Because the canvas is 1000 pixels wide, we know that the x-coordinate of the circle’s center should be 500. In order to have the same amount of space on the top, left, and right, let’s make its y-coordinate 500 as well; that is, the circle’s center will be 500 pixels from the top. As for the radius, let’s set it to be 200 pixels.
As a result, we will call pygame.draw.circle(surface, white, (500, 500), 200)
. This function call should occur after we fill the canvas with black, and before we save to PNG.
def draw_face() -> None: """ Draws a simple cartoon face onto the given Pygame surface. The face consists of: - A large white head (circle) - Two black eyes (circles) - A small black nose (circle) - A red rectangular mouth Args: None Returns: None """ # Create an off-screen surface width, height = 1000, 2000 surface = pygame.Surface((width, height)) black = (0, 0, 0) white = (255, 255, 255) red = (255, 0, 0) # Fill canvas with black surface.fill(black) # add head (white circle) pygame.draw.circle(surface, white, (500, 500), 200) # save to file pygame.image.save(surface, "fun.png")
When we run our code again, we obtain the white circle below as fun.png
.

Adding facial features to our head
Let’s next add a nose, which we will draw as a small black circle, whose center will be slightly below the center of the head. The nose’s x-coordinate will therefore be the same as that of the head, but the y-coordinate will be a little bit larger at 550 pixels. As for the radius, let’s make it ten pixels. And the nose’s color should be black.
def draw_face() -> None: # previous code here # add head (white circle) pygame.draw.circle(surface, white, (500, 500), 200) # add nose (black circle) pygame.draw.circle(surface, black, (500, 550), 10) # save to file pygame.image.save(surface, "fun.png")
When we run our code once more, we see the figure shown below.

Next, we will add two black eyes, which are once again circles. The eyes will fall slightly above the center of the face, and so we will make their y-coordinates equal to 475. Let’s move them to 75 pixels left and right of the center line, so that their x-coordinates will be 425 and 575. We will set their radii equal to 15 to make the eyes a little bigger than the nose.
def draw_face() -> None: # previous code here # add head (white circle) pygame.draw.circle(surface, white, (500, 500), 200) # add nose (black circle) pygame.draw.circle(surface, black, (500, 550), 10) # add eyes (black circles) pygame.draw.circle(surface, black, (425, 475), 15) pygame.draw.circle(surface, black, (575, 475), 15) # save to file pygame.image.save(surface, "fun.png")
Our updated face is shown in the figure below and is starting to take shape as a landmark achievement of Western art.

Finally, we will make a mouth, which gives us the opportunity to draw a different color, red.
We will also make the mouth rectangular, which will allow us to use the function pygame.draw.rect()
. This function takes the following parameters as input:
surface
(pygame.Surface): the surface you want to draw the rectangle on.color
(tuple or pygame.Color): the color of the rectangle, e.g.(255, 0, 0)
for red.- rect (tuple[int, int, int, int]): A 4-tuple
(x, y, width, height)
specifying the rectangle’s top-left x- and y-coordinates along with width and height, in pixels.
The function then creates a rectangle at the indicated location with the given dimensions and color. In our case, we will make the rectangle representing the mouth 200 pixels wide and 20 pixels tall, and so the x-coordinates will range from 400 to 600, and the y-coordinates will range from 600 to 620.
def draw_face() -> None: # previous code here # add head (white circle) pygame.draw.circle(surface, white, (500, 500), 200) # add nose (black circle) pygame.draw.circle(surface, black, (500, 550), 10) # add eyes (black circles) pygame.draw.circle(surface, black, (425, 475), 15) pygame.draw.circle(surface, black, (575, 475), 15) # add mouth (red rectangle) pygame.draw.rect(surface, red, (400, 600, 200, 20)) # save to file pygame.image.save(surface, "fun.png")
Running our full code produces our final face, as shown below.

Note: Feel free to continue editing our face. Add a body, some arms, or change colors however you like to get the hang of working with.
pygame
Looking ahead
Now that we understand a bit more about drawing, we are ready to apply what we have learned to draw cellular automata. Please join us in the next code along to do so!