Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
336: Reading and Manipulating Images with Pillow
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.
There are no comments for now.