Skip to Content
Course content

336: Reading and Manipulating Images with Pillow

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this exact snippet of code dozens of times from developers moving into image processing. They're trying to create a simple thumbnail generator, but when they check their output folder, the images are still full-sized. It looks something like this:

from PIL import Image

img = Image.open("vacation.jpg")
img.resize((128, 128)) 
img.save("thumbnail.jpg")

# Result: thumbnail.jpg is still the original 4000x3000 size!

The "In-Place" Modification Misconception

The problem here is a fundamental misunderstanding of how the Pillow library handles image objects. Coming from other parts of Python, you might expect a method like .resize() to modify the object it's called on. It doesn't. In Pillow, almost every manipulation method returns a new Image object, leaving the original untouched.

When you call img.resize((128, 128)), Pillow creates a perfectly resized copy of your image in memory, and then—because you didn't assign it to a variable—Python immediately throws that copy away. You're then saving the original img object, which never changed.

The fix is simple: assign the result to a variable. I usually just overwrite the original variable name if I don't need the high-res version anymore:

from PIL import Image

img = Image.open("vacation.jpg")
img = img.resize((128, 128)) # Capture the returned object!
img.save("thumbnail.jpg")

Maintaining Aspect Ratio with thumbnail()

Now, if you actually run the fix above on a rectangular photo, you'll notice your image looks squashed or stretched. That's because .resize() forces the image into the exact dimensions you provide, regardless of the original proportions. This is where .thumbnail() comes in.

Unlike .resize(), the .thumbnail() method does modify the image in place (yes, I know, the inconsistency is annoying) and it calculates the best fit to maintain the aspect ratio. If you give it a 128x128 box, it will make the longest side 128 and shrink the other side proportionally.

img = Image.open("vacation.jpg")
img.thumbnail((128, 128)) # This modifies 'img' directly
img.save("thumbnail_proportional.jpg")

Precision Cutting with Crop and Paste

Sometimes you don't want the whole image; you just want a piece of it. Pillow uses a coordinate system where (0, 0) is the top-left corner. The .crop() method takes a tuple of four coordinates: (left, top, right, bottom).

I like to think of it as defining a box. If you want a 100x100 square starting at point (50, 50), your coordinates are (50, 50, 150, 150). Once you've cropped a piece, you can use .paste() to put it onto another image, which is how you build collages or add watermarks.

background = Image.open("canvas.jpg")
logo = Image.open("logo.png")

# Paste the logo onto the background at position (10, 10)
# The third argument 'logo' acts as a mask to preserve transparency (Alpha channel)
background.paste(logo, (10, 10), logo)
background.save("branded_image.jpg")

Dealing with Color Modes and Alpha Channels

You will eventually hit a ValueError: cannot write mode RGBA as JPEG. This happens because JPEGs don't support transparency (the "A" in RGBA). If you open a transparent PNG and try to save it as a JPG, Pillow will crash.

To fix this, you need to convert the image to RGB. I usually do this by creating a white background image and pasting the transparent image over it, or simply calling .convert("RGB"). Note that .convert(), like .resize(), returns a new object.

png_img = Image.open("transparent_logo.png")
rgb_img = png_img.convert("RGB")
rgb_img.save("logo_no_transparency.jpg")



📋 Practical Task

Exercise: Automated Profile Picture Cropper

You've been tasked with creating a script that prepares user-uploaded photos for a profile page. The requirement is that every profile picture must be a perfect square of 300x300 pixels, centered on the original image, to avoid stretching the user's face.

Write a Python script using Pillow that does the following:

  • Opens an image file named user_upload.jpg.
  • Calculates the center of the image.
  • Determines the shortest side of the image (width or height) and uses that as the size for a square crop centered in the middle of the photo.
  • Resizes that resulting square crop to exactly 300x300 pixels.
  • Saves the final result as profile_square.jpg.

Hint: You'll need to find the center coordinates and subtract/add half of the shortest side to find your (left, top, right, bottom) crop box.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.