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. xincreases as a point moves to the right.yincreases 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 type | Coordinate format | Interpretation |
|---|---|---|
| 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
| Operation | Method | Required inputs | Common optional styling inputs | Purpose |
|---|---|---|---|---|
| Create drawing context | ImageDraw.Draw(img) | Target image | None for basic use | Creates the object used to invoke drawing methods |
| Draw rectangle | draw.rectangle() | Bounding box | fill, outline, width | Adds a filled or outlined rectangular shape |
| Draw line | draw.line() | Start and end coordinates | fill, width | Adds a colored stroke between two points |
| Preview image | img.show() | Image object | None for basic use | Opens 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
xvalues move right and largeryvalues 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, ImageDrawto 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
xincreasing rightward andyincreasing downward. - Use
img.show()to preview andimg.save()to retain the result in a file.
For the next step, continue with drawing with Pillow to explore additional image-drawing techniques.