Skip to content

Image Processing with Pillow

By the end of this lesson you'll be able to:

  • Open and display images with Pillow (PIL)
  • Inspect image size, mode, and pixel data
  • Resize, crop, flip, and rotate images
  • Convert between color modes (RGB, grayscale, RGBA)
  • Apply basic filters and save to different formats

A photograph on your phone is just a grid of colored pixels. Python can read, analyze, and transform that grid — automating tasks like resizing profile pictures, converting formats, adding watermarks, or applying artistic filters.

Welcome to Chapter 34!

Monty waving welcome Pillow is Python's go-to library for image work — and it's surprisingly simple. From a school photo to a satellite image, the same functions apply! Let's start editing! Let's code it together!

What is Pillow?

Pillow is the maintained fork of the classic PIL (Python Imaging Library). You install it with:

1
pip install pillow

But you import it using the original name:

1
from PIL import Image

Pillow supports over 30 image formats including JPEG, PNG, GIF, BMP, TIFF, and WebP.

Opening and Inspecting Images

Image.open(filepath) loads an image file into memory. The returned object has several useful attributes:

Attribute What it tells you
image.size (width, height) in pixels
image.width Width in pixels
image.height Height in pixels
image.mode Color mode: "RGB", "RGBA", "L" (grayscale), "P" (palette)
image.format File format: "JPEG", "PNG", etc. (None if created in code)
1
2
3
4
5
6
from PIL import Image

img = Image.open("photo.jpg")
print(img.size)    # (1920, 1080)
print(img.mode)    # "RGB"
img.show()         # opens in the system image viewer

What Do You Think Will Happen?

Monty thinking The code below creates a tiny image from scratch using pixel data. What color do you think pixel (1,1) will be — red, green, or blue? Predict first, then run it!


  

Understanding Pixel Data

A pixel in RGB mode is a tuple of three integers — (red, green, blue) — each ranging from 0 to 255.

Color RGB
Red (255, 0, 0)
Green (0, 255, 0)
Blue (0, 0, 255)
White (255, 255, 255)
Black (0, 0, 0)
Yellow (255, 255, 0)
Cyan (0, 255, 255)
Gray (128, 128, 128)

img.getpixel((x, y)) returns the color of one pixel. Note: x = column (left→right), y = row (top→bottom).

Resizing and Thumbnailing

image.resize((width, height)) creates a new image at the requested size. The original is not modified — Pillow operations always return a new image.

1
2
3
4
5
from PIL import Image

img = Image.open("photo.jpg")
small = img.resize((200, 150))
small.save("photo_small.jpg")

image.thumbnail((max_width, max_height)) is smarter — it shrinks the image to fit within the box while keeping the aspect ratio. It modifies the image in place:

1
img.thumbnail((400, 400))   # fits in 400×400 box, keeps proportions

Cropping Images

image.crop((left, upper, right, lower)) extracts a rectangular region. The four numbers are pixel coordinates measured from the top-left corner.

1
2
3
# Crop a 100×100 region starting at pixel (50, 30):
region = img.crop((50, 30, 150, 130))
region.save("cropped.jpg")

  

Flipping and Rotating

image.transpose(method) flips or rotates the image. The method constants live in Image.Transpose (or the older Image namespace):

1
2
3
4
5
6
from PIL import Image

img = Image.open("photo.jpg")
flipped_h = img.transpose(Image.Transpose.FLIP_LEFT_RIGHT)   # mirror horizontally
flipped_v = img.transpose(Image.Transpose.FLIP_TOP_BOTTOM)   # mirror vertically
rotated   = img.rotate(90)                                    # rotate 90° counter-clockwise

img.rotate(angle) rotates by the given angle — positive angles rotate counter-clockwise.

Color Mode Conversion

image.convert(mode) changes the color mode of an image.

The most common conversions are:

Conversion What it does
img.convert("L") Convert to grayscale (L = luminance)
img.convert("RGB") Convert to 3-channel color
img.convert("RGBA") Add transparency channel
img.convert("1") Convert to 1-bit black and white

Converting to grayscale is a simple way to apply a classic photo effect:

1
2
3
4
5
from PIL import Image

img  = Image.open("photo.jpg")
gray = img.convert("L")
gray.save("photo_grayscale.jpg")

  

Applying Filters

Pillow's ImageFilter module provides ready-made filters:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from PIL import Image, ImageFilter

img = Image.open("photo.jpg")

blurred   = img.filter(ImageFilter.BLUR)
sharpened = img.filter(ImageFilter.SHARPEN)
edges     = img.filter(ImageFilter.FIND_EDGES)
embossed  = img.filter(ImageFilter.EMBOSS)

edges.save("edges.png")

Filters work by examining each pixel's neighbors and computing a new value — a process called convolution.

Saving Images

image.save(filepath) saves to any supported format. Pillow detects the format from the file extension:

1
2
3
img.save("output.png")   # saves as PNG
img.save("output.jpg", quality=85)   # JPEG with quality setting (1-95)
img.save("output.bmp")   # BMP format

For JPEG: lower quality = smaller file but more visual artifacts. Quality 80–90 is usually a good balance.

Learning Check

Your Turn — Write a Brightness Booster

Monty thinking The code below has a function stub for brighten(image, amount). Complete it: for every pixel, add amount to each R, G, B channel — but clamp the result between 0 and 255!


  

Experiments

Try these changes. Predict what will happen first, then run it to check!

  1. Create a 100×100 image where each pixel's R value equals its x-coordinate and G value equals its y-coordinate. Save it as a PNG and open it. You'll know it worked when you see a gradient that shifts from dark to bright in two directions.

  2. Open a JPEG photo and use thumbnail((300, 300)) to shrink it, then save as PNG. You'll know it worked when the output file is a smaller image in PNG format.

  3. Convert an RGB image to grayscale and then convert it back to RGB. Print a pixel — is it the same as the original? You'll know it worked when you see the gray color has equal R, G, B values.

  4. Use img.rotate(45) on a photo. Note that the corners are black (background fill). Add expand=True to see the difference. You'll know it worked when expand=True grows the canvas to show the full rotated image.

  5. Apply ImageFilter.FIND_EDGES to a photo. Save the result and compare it to the original. You'll know it worked when you see bright lines where the original had color edges.

Image Wizard!

Monty celebrating You can now open, inspect, transform, filter, and save images with Python! These same techniques power computer vision systems, photo-editing apps, and machine learning pipelines. The next chapter returns to turtle graphics for event-driven animation. Let's keep coding!

Take the Chapter Review Quiz

See Annotated References