Drawing Shapes on Images with Python and Pillow

Pillow provides a drawing module called ImageDraw that you can use to create simple 2D graphics on your Image objects. According to Pillow’s documentation, “you can use this module to create new images, annotate or retouch existing images, and to generate graphics on the fly for web use.”

If you need more advanced drawing capabilities than what is included in Pillow, you can get a separate package called aggdraw.

You will focus on what comes with Pillow in this article. Specifically, you will learn about the following:

  • Common Parameters
  • Drawing Lines
  • Drawing Arcs
  • Drawing Chords
  • Drawing Ellipses
  • Drawing Pie Slices
  • Drawing Polygons
  • Drawing Rectangles

When drawing with Pillow, it uses the same coordinate system that you have been using with the rest of Pillow. The upper left corner is still (0,0), for example. If you draw outside of the image bounds, those pixels will be discarded.

If you want to specify a color, you can use a series of numbers or tuples as you would when using PIL.Image.new(). For “1”, “L”, and “I” images, use integers. For “RGB” images, use a 3-tuple containing integer values. You may also use the color names that are supported by Pillow that you learned about in chapter 2.

Common Parameters

When you go to use the various drawing methods, you will discover that they have a lot of common parameters that they share. Rather than explain the same parameters in every section, you will learn about them up-front!

xy

Most of the drawing methods have an xy parameter that sets a rectangular area in which to draw a figure. This can be defined in the following two ways:

  • ((upper left x, upper left y), (lower right x, lower right y)) or simply ((x1, y1), (x2, y2))
  • A box tuple of (x1, y1, x2, y2)

When it comes to drawing a line, polygon, or point, multiple coordinates are specified in either of these ways:

  • (x1, y1, x2, y2, x3, y3...)
  • ((x1, y1), (x2, y2), (x3, y3)...)

The line() method will draw a straight line, connecting each point. The polygon() will draw a polygon where each point is connected. Finally, the point() will draw a point of 1-pixel at each point.

fill

The parameter, fill, is used to set the color that will fill the shape. The way you set the fill is determined by the mode of the image:

  • RGB: Set each color value (0-255) using (R, G, B) or a color name
  • L (grayscale): Set a value (0-255) as an integer

The default is None or no fill.

outline

The outline sets the border color of your drawing. Its specification is the same as the one you use for fill.

The default is None, which means no border.

Now that you know about the common parameters, you can move on and learn how to start drawing!

Drawing Lines

The first type of drawing you will learn about is how to draw lines in Pillow. All shapes are made up of lines. In Pillow’s case, a line is drawn by telling Pillow the beginning and ending coordinates to draw the line between. Alternatively, you can pass in a series of XY coordinates and Pillow will draw lines to connect the points.

Following is the line() method definition:

def line(self, xy, fill=None, width=0, joint=None):
    """Draw a line, or a connected sequence of line segments."""

You can see that it accepts several different parameters. You learned what some of these parameters mean in the previous section. The width parameter is used to control the width of the lines.

Before you learn how to use joint, you should learn how to draw lines without it. But first, you will need an image to draw on. You will use this image of one of the Madison County bridges:

Madison County Covered Bridge
Madison County Covered Bridge

Now go open up your Python editor and create a new file named draw_line.py  and add this code to it:

# draw_line.py

import random
from PIL import Image, ImageDraw


def line(image_path, output_path):
    image = Image.open(image_path)
    draw = ImageDraw.Draw(image)
    colors = ["red", "green", "blue", "yellow",
              "purple", "orange"]

    for i in range(0, 100, 20):
        draw.line((i, 0) + image.size, width=5, 
                  fill=random.choice(colors))

    image.save(output_path)

if __name__ == "__main__":
    line("madison_county_bridge_2.jpg", "lines.jpg")

Here you open up the image in Pillow and then pass the Image object to ImageDraw.Draw(), which returns an ImageDraw object. Now you can draw lines on your image. In this case, you use a for loop to draw five lines on the image. The beginning image starts at (0,0) in the first loop. Then the X position changes in each iteration. The endpoint is the size of the image.

You use the random module to choose a random color from a list of colors. When you run this code, the output will look something like this:

Lines drawn on an image
Lines drawn on an image

Now you can try creating a series of points and drawing lines that way. Create a new file named draw_jointed_line.py and put this code in your file:

# draw_jointed_line.py

from PIL import Image, ImageDraw


def line(output_path):
    image = Image.new("RGB", (400, 400), "red")
    points = [(100, 100), (150, 200), (200, 50), (400, 400)]
    draw = ImageDraw.Draw(image)
    draw.line(points, width=15, fill="green", joint="curve")
    image.save(output_path)

if __name__ == "__main__":
    line("jointed_lines.jpg")

This time you create an image using Pillow rather than drawing on one of your own. Then you create a list of points. To make the line connections look nicer, you can set the joint parameter to “curve”. If you look at the source code for the line() method, you will find that “curve” is the only valid value to give it other than None. This may change in a future version of Pillow.

When you run this code, your image will look like this:

 

Jointed lines
Drawing jointed lines

Now try removing the joint parameter from your code and re-run the example. Your output will now look like this:

Lines without joints
Lines without joints

By setting joint to “curve”, the output will be slightly more pleasing to the eye.

Now you’re ready to learn about drawing arcs with Pillow!

Drawing Arcs

An arc is a curved line. You can draw arcs with Pillow too. Here is the arc() method specification:

def arc(self, xy, start, end, fill=None, width=1):
    """Draw an arc."""

An arc() can also be made using xy points. The start parameter defines the starting angle, in degrees. The end parameter tells Pillow what the ending angle is, which is also in degrees. The other two parameters are ones that have already been introduced.

To see how you might draw an arc, create a new file named draw_arc.py and add this code to it:

# draw_arc.py

from PIL import Image, ImageDraw


def arc(output_path):
    image = Image.new("RGB", (400, 400), "white")
    draw = ImageDraw.Draw(image)
    draw.arc((25, 50, 175, 200), start=30, end=250, fill="green")

    draw.arc((100, 150, 275, 300), start=20, end=100, width=5, 
             fill="yellow")

    image.save(output_path)

if __name__ == "__main__":
    arc("arc.jpg")

In this code, you create a new image with a white background. Then you create your Draw object. Next, you create two different arcs. The first arc will be filled with green. The second arc will be filled in yellow, but its line width will be 5. When you draw an arc, the fill is referring to the arc’s line color. You aren’t filling the arc itself.

When you run this code, your output image will look like this:

Drawing arcs
Drawing arcs

Try changing some of the parameters and re-running the code to see how you can change the arcs yourself.

Now let’s move on and learn about drawing chords!

Drawing Chords

Pillow also supports the concept of chords. A chord is the same as an arc except that the endpoints are connected with a straight line.

Here is the method definition of chord():

def chord(self, xy, start, end, fill=None, outline=None, width=1):
    """Draw a chord."""

The only difference here is that you can also add an outline color. This color can be specified in any of the ways that you can specify a fill color.

Create a new file and name it draw_chord.py. Then add this code so you can see how you make chords yourself:

# draw_chard.py

from PIL import Image, ImageDraw


def chord(output_path):
    image = Image.new("RGB", (400, 400), "green")
    draw = ImageDraw.Draw(image)
    draw.chord((25, 50, 175, 200), start=30, end=250, fill="red")

    draw.chord((100, 150, 275, 300), start=20, end=100, width=5, fill="yellow",
                outline="blue")

    image.save(output_path)

if __name__ == "__main__":
    chord("chord.jpg")

This example will draw two chords on a green image. The first chord is filled in with a red color. The second chord is filled in with yellow but is outlined in blue. The blue outline has a width of 5.

When you run this code, you will create the following image:

Drawing chords
Drawing chords

That looks pretty good. Go ahead and play around with this example too. You’ll soon master chord making with Pillow with a little practice.

Now let’s continue and learn about drawing ellipses!

Drawing Ellipses

An ellipse, or oval, is drawn in Pillow by giving it a bounding box (xy). You have seen this several other times in previous sections.

Here is the ellipse() method definition:

def ellipse(self, xy, fill=None, outline=None, width=1):
    """Draw an ellipse."""

The ellipse() lets you fill it with a color, add a colored border (outline) and change the width of that outline.

To see how you can create an ellipse(), make a new file named draw_ellipse.py and add this code to it:

# draw_ellipse.py

from PIL import Image, ImageDraw


def ellipse(output_path):
    image = Image.new("RGB", (400, 400), "white")
    draw = ImageDraw.Draw(image)
    draw.ellipse((25, 50, 175, 200), fill="red")

    draw.ellipse((100, 150, 275, 300), outline="black", width=5,
                 fill="yellow")

    image.save(output_path)

if __name__ == "__main__":
    ellipse("ellipse.jpg")

In this code, you create a nice white image via the new() method. Then you draw a red ellipse on top of it. Finally, you draw a second ellipse that is filled with yellow and outlined in black where the outline width is set to 5.

When you run this code, the image it creates will look like this:

Drawing ellipses
Drawing ellipses

You can create ovals and circles using ellipse(). Give it a try and see what you can do with it.

Now let’s find out how to create pie slices!

Drawing Pie Slices

A pie slice is the same as arc()), but also draws straight lines between the endpoints and the center of the bounding box.

Here is how the pieslice() method is defined:

def pieslice(self, xy, start, end, fill=None, outline=None, width=1):
    """Draw a pieslice."""

You have used all of these parameters in other drawings. To review, fill adds color to the inside of the pieslice() while outline adds a colored border to the figure.

To start practicing this shape, create a new file named draw_pieslice.py and add this code to your file:

# draw_pieslice.py

from PIL import Image, ImageDraw


def pieslice(output_path):
    image = Image.new("RGB", (400, 400), "grey")
    draw = ImageDraw.Draw(image)
    draw.pieslice((25, 50, 175, 200), start=30, end=250, fill="green")

    draw.pieslice((100, 150, 275, 300), start=20, end=100, width=5, 
                  outline="yellow")

    image.save(output_path)

if __name__ == "__main__":
    pieslice("pieslice.jpg")

In this code, you generate a grey image to draw on. Then you create two pie slices. The first pieslice() is filled in with green. The second one is not filled in, but it does have a yellow outline. Note that each pieslice() has a different starting and ending degree.

When you run this code, you will get the following image:

Drawing pie slices
Drawing pie slices

With a little work, you could create a pie graph using Pillow! You should play around with your code a bit and change some values. You will quickly learn how to make some nice pie slices of your own.

Now let’s find out how to draw polygons with Pillow!

Drawing Polygons

A polygon is a geometric shape that has a number of points (vertices) and an equal number of line segments or sides. A square, triangle, and hexagon are all types of polygons. Pillow lets you create your own polygons. Pillow’s documentation defines a polygon like this: The polygon outline consists of straight lines between the given coordinates, plus a straight line between the last and the first coordinate.

Here is the code definition of the polygon() method:

def polygon(self, xy, fill=None, outline=None):
    """Draw a polygon."""

All of these parameters should be familiar to you now. Go ahead and create a new Python file and name it draw_polygon.py. Then add this code:

# draw_polygon.py

from PIL import Image, ImageDraw


def polygon(output_path):
    image = Image.new("RGB", (400, 400), "grey")
    draw = ImageDraw.Draw(image)
    draw.polygon(((100, 100), (200, 50), (125, 25)), fill="green")

    draw.polygon(((175, 100), (225, 50), (200, 25)),
                  outline="yellow")

    image.save(output_path)

if __name__ == "__main__":
    polygon("polygons.jpg")

This code will create a grey image like the last example in the previous section. It will then create a polygon that is filled with the color green. Then it will create a second polygon and outline it in yellow without filling it.

In both of the drawings, you are supplying three points. That will create two triangles.

When you run this code, you will get this output:

Drawing polygons
Drawing polygons

Try changing the code by adding additional points to one or more of the polygons in the code above. With a little practice, you’ll be able to create complex polygons quickly with Pillow.

Drawing Rectangles

The rectangle() method allows you to draw a rectangle or square using Pillow. Here is how rectangle() is defined:

def rectangle(self, xy, fill=None, outline=None, width=1):
    """Draw a rectangle."""

You can pass in two tuples that define the beginning and ending coordinates to draw the rectangle. Or you can supply the four coordinates as a box tuple (4-item tuple). Then you can add an outline, fill it with a color, and change the outline’s width.

Create a new file and name it draw_rectangle.py. Then fill it in with this code so you can start drawing rectangles:

# draw_rectangle.py

from PIL import Image, ImageDraw


def rectangle(output_path):
    image = Image.new("RGB", (400, 400), "blue")
    draw = ImageDraw.Draw(image)
    draw.rectangle((200, 100, 300, 200), fill="red")
    draw.rectangle((50, 50, 150, 150), fill="green", outline="yellow",
                   width=3)
    image.save(output_path)

if __name__ == "__main__":
    rectangle("rectangle.jpg")

This code will create a blue image that is 400×400 pixels. Then it will draw two rectangles. The first rectangle will be filled with red. The second will be filled with green and outlined with yellow.

When you run this code, you will get this image as output:

Drawing rectangles
Drawing rectangles

Aren’t those lovely rectangles? You can modify the rectangle’s points to create thinner or wider rectangles. You could also modify the outline width that you add to the rectangles.

Wrapping Up

You can use Pillow to add shapes to your images. This can be helpful for adding outlines to your images, highlighting one or more portions of your image, and more.

In this article, you learned about the following topics:

  • Common Parameters
  • Drawing Lines
  • Drawing Arcs
  • Drawing Chords
  • Drawing Ellipses
  • Drawing Pie Slices
  • Drawing Polygons
  • Drawing Rectangles

You can do a lot with the shapes that are provided by Pillow. You should take these examples and modify them to test them out with your own photos. Give it a try and see what you can come up with!

Related Reading

 

Pillow: Image Processing with Python

Purchase now on Leanpub