PIL Tutorial: From Basic to Advanced Drawing

The ImageDraw module provides simple 2D graphics for Image objects. You can use it to create new images, annotate or retouch existing images, and generate graphics on the fly for usage on the web. In this tutorial I'm going to show you how to use this module to draw 2D graphics. I'll start with basic shapes and then move on to fairly complicated ones.

The Draw Object

To draw on an image, you need to create an ImageDraw object first. This is what the Draw method is for:

Draw(image) => Draw instance

This object will enable you to draw on the given image. You can use it to draw as many shapes as needed, and the image will be modified in place.

Drawing a Rectangle

draw.rectangle(box, fill=None, outline=None)

The box can be any sequence object containing either 2-tuples [(x1, y1), (x2, y2)] or numeric values [x1, y1, x2, y2]. It should contain two pairs of coordinates. The outline is the color to use for the rectangle's outline. The fill is the color to use for the rectangle's interior.

Example:

from PIL import Image, ImageDraw
im = Image.new('RGBA', (100, 100), (0, 0, 0, 0)) # Create a blank image
draw = ImageDraw.Draw(im) # Create a draw object
draw.rectangle((10, 10, 90, 90), fill="yellow", outline="red")

Here I used color names to specify fill and outline colors. You can also use tuples or hexadecimal color specifiers as #rrggbb. Refer to ImageDraw documentation for the full list of supported color formats.

Tip: To draw a rectangle with a specific outline width that is greater than 1px, you can draw two rectangles on top of each other with the inner box slightly smaller. Fill the outer rectangle with the outline color and the inner one with the fill color:

from PIL import Image, ImageDraw
 
def rectangle(input, box, fill, outline, width):
    draw = ImageDraw.Draw(input)
    draw.rectangle(box, fill=outline) # The outer rectangle
    draw.rectangle( # The inner rectangle
        (box[0] + width, box[1] + width, box[2] - width, box[3] - width),
        fill=fill
    )
 
input = Image.new('RGBA', (40, 40), (0, 0, 0, 0)) 
rectangle(input, (0, 0, 39, 39), "lightblue", "blue", 5)

The result will look like this:

Drawing a Circle

draw.ellipse(box, fill=None, outline=None)

Draws an ellipse inside the given bounding box. If width is equal to height, the result will be a circle:

im = Image.new('RGBA', (100, 100), (0, 0, 0, 0)) # Create a blank image
draw = ImageDraw.Draw(im) # Create a draw object
draw.ellipse((0, 0, 100, 100), fill=(255, 255, 255)) # Draw a circle

To specify the outline width you can use the same tip as in the rectangle example.

Drawing a Polygon

draw.polygon(coordinates, fill=None, outline=None)

The polygon outline consists of straight lines between the given coordinates, plus a straight line between the last and the first coordinate.
The coordinate list can be any sequence object containing either 2-tuples [ (x, y), ... ] or numeric values [ x, y, ... ]. It should contain at least three coordinates.
The following will draw a regular pentagon:

im = Image.new('RGBA', (100, 100), (0, 0, 0, 0)) # Create a blank image
draw = ImageDraw.Draw(im)
lines = [(50, 0), (0, 40), (20, 100), (80, 100), (100, 40)]
draw.polygon(lines, fill="black")

Drawing Text

text(position, text, fill=None, font=None)

Draws the string at the given position. The position gives the upper left corner of the text. The font option is used to specify which font to use. It should be an instance of the ImageFont class, typically loaded from file using the load or truetype method in the ImageFont module. The fill option specifies the color to use for the text:

im = Image.new('RGBA', (100, 100), (0, 0, 0, 0)) # Create a blank image
draw = ImageDraw.Draw(im)
draw.text((10, 50), 'Hello World!')

You can use the ImageFont.truetype method to create a font object for drawing text.

ImageFont.truetype(font_path, size) => Font instance

This will load a TrueType or OpenType font file, and create a font object. This function loads a font object from the given file, and creates a font object for a font of the given size. On Windows, if the given file name does not exist, the loader also looks in Windows fonts directory.

Here are the default font directory paths:

Linux: /usr/share/fonts/
Max OS: /Library/Fonts/
Windows: C:\Windows\fonts

Example:

from PIL import ImageDraw, ImageFont
font = ImageFont.truetype(
    'path/to/font.ttf', 30
)
draw.text((10, 50), 'Logo', font=font, fill="blue")

If you want to draw text that fits exactly inside an image, then you can use the handy font.getsize method:

def draw_text(text, size, fill=None):
    font = ImageFont.truetype(
        'path/to/font.ttf', size
    )
    size = font.getsize(text) # Returns the width and height of the given text, as a 2-tuple.
    im = Image.new('RGBA', size, (0, 0, 0, 0)) # Create a blank image with the given size
    draw = ImageDraw.Draw(im)
    draw.text((0, 0), text, font=font, fill=fill) #Draw text
    return im
 
img = draw_text('Google', 30, (82, 124, 178))

Output:

Drawing Text at an Angle

The text method doesn't support writing at an angle. But we can achieve rotation by simply rotating the resulting image. Here is the modified version:

def draw_text(text, size, angle=0, fill=None):
    font = ImageFont.truetype(
        'path/to/font.ttf', size
    )
    size = font.getsize(text) # Returns the width and height of the given text, as a 2-tuple.
    im = Image.new('RGBA', size, (0, 0, 0, 0)) # Create a blank image with the given size
    draw = ImageDraw.Draw(im)
    draw.text((0, 0), text, font=font, fill=fill) #Draw text
    return im.rotate(angle, expand=True)
 
img = draw_text('Google', 30, 45, (82, 124, 178)) 

Output:

Drawing Rounded Corners Rectangle

The idea is simple enough: draw a rectangle then paste a rounded corner on top of each corner.

def round_corner(radius, fill):
    """Draw a round corner"""
    corner = Image.new('RGBA', (radius, radius), (0, 0, 0, 0))
    draw = ImageDraw.Draw(corner)
    draw.pieslice((0, 0, radius * 2, radius * 2), 180, 270, fill=fill)
    return corner
 
def round_rectangle(size, radius, fill):
    """Draw a rounded rectangle"""
    width, height = size
    rectangle = Image.new('RGBA', size, fill)
    corner = round_corner(radius, fill)
    rectangle.paste(corner, (0, 0))
    rectangle.paste(corner.rotate(90), (0, height - radius)) # Rotate the corner and paste it
    rectangle.paste(corner.rotate(180), (width - radius, height - radius))
    rectangle.paste(corner.rotate(270), (width - radius, 0))
    return rectangle
 
img = round_rectangle((50, 50), 10, "yellow")

The result:

Drawing a Heart

The heart shape can be a little tricky. You need to get the calculations right. The following method draws a polygon and two ellipses, like this:
+ =>

def heart(size, fill):
    width, height = size
    im = Image.new('RGBA', size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(im)
    polygon = [
        (width / 10, height / 3),
        (width / 10, 81 * height / 120),
        (width / 2, height),
        (width - width / 10, 81 * height / 120),
        (width - width / 10, height / 3),
    ]
    draw.polygon(polygon, fill=fill)
    draw.ellipse((0, 0,  width / 2, 3 * height / 4), fill=fill)
    draw.ellipse((width / 2, 0,  width, 3 * height / 4), fill=fill)
    return im
 
im = heart((50, 40), "red")

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Drawing

You can also create nice antialiased graphics with pycairo:
http://www.cairographics.org/samples/

this is very useful thanks

this is very useful thanks

Thank you

I love theses

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options