81.7 Pillow: Image Manipulation in Pure Python
Right, let’s talk about Pillow. You know, the friendly fork of the now-defunct Python Imaging Library (PIL). If you need to open, manipulate, and save images in Python without summoning the eldritch horrors of OpenCV’s C++ bindings, Pillow is your first, last, and best port of call. It’s not the fastest kid on the block, but it’s pure Python, beautifully straightforward, and it gets the job done. Think of it as the trusty multi-tool in your image-processing kit.
Opening and Examining Images: More Than Meets the Eye
First things first, you need an image. Let’s grab one. The Image.open() function is your gateway. It doesn’t actually read the whole image data right away; it just reads the header to figure out the file format and basic properties. This is clever, lazy loading at its finest.
from PIL import Image
# This doesn't load the pixel data yet. It's just peeking.
img = Image.open('your_awesome_cat.jpg')
Now, let’s see what we’re working with. These properties are non-negotiable knowledge.
print(f"Format: {img.format}") # JPEG, PNG, etc. The source of truth.
print(f"Size: {img.size}") # (width, height) in pixels. A tuple, because of course it is.
print(f"Mode: {img.mode}") # This is the big one: 'L' (grayscale), 'RGB', 'RGBA', 'CMYK'. Mess this up and everything goes sepia-toned.
The Three Pillars: Convert, Resize, Crop
Most image work boils down to these three operations. Get them right, and you’re a wizard.
Conversion is about changing the mode. Want to take a color image and make it grayscale for a model input? Convert its mode.
# Converting a color image to grayscale
grayscale_img = img.convert('L')
grayscale_img.save('your_very_serious_cat.jpg')
Resizing is where you’ll make your first classic mistake. thumbnail() and resize() are not the same thing, and the difference is utterly important. resize() forces the image to an exact size, potentially mangling the aspect ratio. thumbnail() resizes the image in place to fit within a given box, preserving the aspect ratio. It’s the difference between a well-framed photo and a funhouse mirror.
# The RIGHT way to make an image fit a space (say, for a thumbnail)
img.thumbnail((400, 400)) # Modifies the image in-place. The result will be *at most* 400x400.
img.save('cat_thumbnail.jpg')
# The "I hope you like stretched cats" way
squished_cat = img.resize((400, 200)) # Will mercilessly squish it to exactly 400x200.
squished_cat.save('squished_cat.jpg') # Don't do this. Your cat deserves better.
Cropping is straightforward. You define a box (left, upper, right, lower) and you get what’s inside. The coordinate system starts from (0,0) at the top-left. Just remember that.
# Let's crop to the left half of the image
width, height = img.size
box = (0, 0, width//2, height) # Left, Top, Right, Bottom
left_half = img.crop(box)
left_half.save('left_half_cat.jpg') # Now you have half a cat. Useful?
The Pit of Despair: Saving and Format Woes
This is where Pillow will happily, silently betray you. You can’t just call save() and be done with it. The file format is determined by the extension you use in the filename. Save a PNG as .jpg? Pillow will do it, and you’ll get a nasty JPG with no transparency and potentially horrible compression artifacts where your beautiful alpha channel used to be.
# Load a PNG with transparency
png_img = Image.open('cat_with_transparency.png')
print(png_img.mode) # Probably 'RGBA'
# Now let's accidentally ruin it by saving as a JPG
png_img.save('oops_no_more_transparency.jpg') # Pillow converts it to RGB, flattens the transparency to black/white, and saves. Gross.
# The correct way: specify the format if there's any ambiguity, or just use the right extension.
png_img.save('correctly_saved.png')
# Or, to explicitly convert and save as JPEG (which doesn't support alpha)
rgb_img = png_img.convert('RGB') # First, strip the alpha channel
rgb_img.save('correctly_saved.jpg', format='JPEG')
Also, for the love of all that is holy, use the quality parameter when saving JPEGs. The default is 75, which is often a mushy, artifact-filled mess. Crank it to 95 for most use cases. It’s a bigger file, but it won’t look like it was encoded in a cave.
img.save('high_quality_cat.jpg', format='JPEG', quality=95)
Why You’ll Eventually Outgrow It (And That’s Okay)
Pillow is brilliant for manipulation—cropping, resizing, compositing, basic filtering. But it’s not a computer vision library. It doesn’t have built-in face detection, object recognition, or fancy neural network filters. For that, you graduate to opencv-python or torchvision. But you know what? Those libraries often use Pillow under the hood for the basic I/O stuff. It’s the foundation. Mastering it means you understand the raw materials you’re working with before you start applying the rocket science on top. Now go forth and process some images. Just please, preserve your cat’s aspect ratio.