Drawing Shapes and Lines with Pillow

Learn how to use Pillow's ImageDraw module to draw rectangles and lines on an existing image, understand image coordinates, preview results, and troubleshoot common problems.

Pillow is a Python imaging library used to open, manipulate, draw on, and save image files. Its ImageDraw module provides tools for adding basic graphical elements such as rectangles and lines.

Drawing is a raster operation: Pillow changes colored pixels inside defined geometric boundaries. The drawing operations modify an Image object in memory. The image object can come from an existing file or from an image created in Python.

Import Pillow's Image and ImageDraw Modules

Import Image to open or create images, and import ImageDraw to draw shapes and other graphics.

from PIL import Image, ImageDraw

Pillow is the library. An Image is Pillow's object containing pixel data. ImageDraw is the module containing drawing APIs.

Open an Image as a Drawing Surface

Use Image.open() to load a blank image file or an existing image. The loaded image acts as the canvas, meaning the image surface onto which graphical elements are drawn.

from PIL import Image, ImageDraw

img = Image.open('blank.jpg')

The file path is relative to the program's current working directory unless you provide an absolute path. Loading the file creates an image object in memory; it does not yet change the file on disk.

An image's mode affects which color formats it supports. For example, RGB images use red, green, and blue color channels, while RGBA images also include an alpha channel for transparency. A grayscale image has a different set of supported pixel values. Simple named colors such as 'red' and 'green' are convenient for common RGB or RGBA drawing tasks.

Create an ImageDraw Drawing Context

A drawing context is an ImageDraw.Draw object associated with a target image. Shape methods are called on this object rather than directly on the Image object.

draw = ImageDraw.Draw(img)

After this statement, draw can add pixels to img using methods such as rectangle() and line().

Understand Pillow's Image Coordinates

A coordinate identifies a pixel location using horizontal x and vertical y values. Pillow uses this image coordinate system:

  • The origin (0, 0) is at the top-left corner.
  • x increases as a point moves to the right.
  • y increases as a point moves downward.

This differs from the traditional mathematical coordinate system, where positive y commonly points upward.

A rectangle uses a bounding box: two opposing corner positions that define its placement and size. Conventionally, provide the top-left corner first and the bottom-right corner second.

Drawing typeCoordinate formatInterpretation
Rectangle bounding box(left, top, right, bottom)Top-left and bottom-right corners of the rectangular area
Line start and end points(x1, y1, x2, y2)Start point followed by end point

Coordinate Example

For a line from (100, 280) to (330, 280), the two points have the same y value. Therefore, the line is horizontal. Its endpoint is farther to the right because 330 is greater than 100.

Draw a Rectangle

Call rectangle() on the drawing context and provide a bounding box. The fill argument chooses the interior color.

draw.rectangle((179, 15, 254, 282), fill='red')

This draws a filled red rectangle whose top-left corner is (179, 15) and whose bottom-right corner is (254, 282).

A filled rectangle colors the area inside its boundary. To draw an outline instead, omit the interior fill and provide an outline color:

draw.rectangle((179, 15, 254, 282), outline='red', width=3)

The exact styling options can depend on the Pillow version, but fill is the standard way to specify the interior and outline specifies a border.

Draw a Line

Use line() with a start point and an end point. The fill argument sets the line color, and width sets stroke thickness in pixels.

draw.line((100, 280, 330, 280), fill='green', width=10)

This creates a green horizontal stroke between the two coordinates with a thickness of 10 pixels.

Complete Drawing Example

The following example opens an image, prepares its drawing context, adds a filled rectangle and a thick line, and then previews the result.

from PIL import Image, ImageDraw

img = Image.open('blank.jpg')
draw = ImageDraw.Draw(img)

draw.rectangle((179, 15, 254, 282), fill='red')
draw.line((100, 280, 330, 280), fill='green', width=10)

img.show()

The expected result is an image with a solid red rectangle in the specified area and a thick green horizontal line near the lower portion of the canvas.

Preview the Drawing

Call the image's show() method to preview the modified image:

img.show()

The operating system's configured image viewer normally opens the preview. The drawing remains in the in-memory image object, but displaying it does not necessarily create a persistent output file.

When the drawing should be retained, saving is the natural next step. Save the modified image explicitly with an output filename and suitable format, for example:

img.save('annotated-image.png')

Saving is separate from previewing: show() displays a result, while save() writes image data to a file.

Core Pillow Drawing Operations

OperationMethodRequired inputsCommon optional styling inputsPurpose
Create drawing contextImageDraw.Draw(img)Target imageNone for basic useCreates the object used to invoke drawing methods
Draw rectangledraw.rectangle()Bounding boxfill, outline, widthAdds a filled or outlined rectangular shape
Draw linedraw.line()Start and end coordinatesfill, widthAdds a colored stroke between two points
Preview imageimg.show()Image objectNone for basic useOpens a temporary preview of the in-memory result

Troubleshooting

The Image File Cannot Be Opened

  • Verify the filename and path.
  • Check the program's current working directory, or use an absolute path.
  • Try opening a known-valid image if the file format may be unsupported or the file may be damaged.

The Shape Appears in an Unexpected Location

  • Remember that the origin is at the top-left, not at the bottom-left.
  • Make sure larger x values move right and larger y values move down.
  • For rectangles, provide the top-left corner followed by the bottom-right corner.

The Line Is Too Thin or Too Thick

Set width to an appropriate integer number of pixels and preview the result again.

draw.line((100, 280, 330, 280), fill='green', width=4)

The Preview Works but No Edited File Exists

show() only previews the in-memory result. Call save() with an output filename when you need an edited file.

Exam-Ready Summary

  • Use from PIL import Image, ImageDraw to import image and drawing functionality.
  • Use Image.open() to load the image that will act as the canvas.
  • Use ImageDraw.Draw(img) to create the drawing context.
  • Use draw.rectangle(box, fill=color) for a filled rectangle.
  • Use draw.line(points, fill=color, width=pixels) for a colored line with a chosen thickness.
  • Remember that image coordinates start at the top-left, with x increasing rightward and y increasing downward.
  • Use img.show() to preview and img.save() to retain the result in a file.

For the next step, continue with drawing with Pillow to explore additional image-drawing techniques.