πŸ‘‘ Advanced Python Pillow Cheat Sheet

πŸ‘‘ Advanced Python Pillow Cheat Sheet #

A deep dive into the Pillow library, focusing on advanced techniques, performance optimization, and best practices for modern image manipulation in Python.


πŸš€ Setup & Best Practices #

  • Always use a context manager: Ensures files are properly closed.
  • Load image data: Use im.load() before accessing pixel data for the first time to load the image data from the file.
  • Prefer LANCZOS for resizing: Image.Resampling.LANCZOS (formerly Image.ANTIALIAS) provides the highest quality downscaling.
  • Close images: Explicitly call im.close() if not using a context manager, to free up resources.
from PIL import Image, ImageFilter, ImageEnhance, ImageDraw, ImageFont, ImageOps
import numpy as np

# Best practice for opening files
try:
    with Image.open('image.jpg') as im:
        im.load() # Lazily loads the image data
        # ... process image
except FileNotFoundError:
    print("File not found.")

🎨 Modes, Bands & Color Spaces #

Understanding modes is key to advanced manipulation.

  • Bands: An image can have one or more bands. An RGB image has three bands (Red, Green, Blue).
  • Modes: Defines the type and depth of a pixel. Common modes: L (luminance), RGB, RGBA, CMYK, 1 (binary).
with Image.open('image.jpg') as im:
    # Split image into individual bands
    r, g, b = im.split()

    # Merge bands back together
    im_merged = Image.merge('RGB', (b, r, g)) # Swapping red and blue channels
    # im_merged.show()

    # Convert between modes
    grayscale_im = im.convert('L')
    cmyk_im = im.convert('CMYK')

πŸ’Ύ Advanced File Operations #

Loading & Saving #

# Reading from a URL (requires requests)
# import requests
# from io import BytesIO
# response = requests.get('URL_TO_IMAGE')
# with Image.open(BytesIO(response.content)) as im_from_url:
#     im_from_url.show()

# Draft mode for faster thumbnail creation
with Image.open('large_image.jpg') as im:
    # Load a half-resolution grayscale version
    im.draft('L', (im.width // 2, im.height // 2))
    # im.show()

# Saving with quality options (for JPEG)
with Image.open('image.jpg') as im:
    im.save('high_quality.jpg', quality=95)
    im.save('low_quality.jpg', quality=20, optimize=True)

🎭 Masking & Pasting #

A mask is a grayscale image where the value of each pixel determines the transparency for pasting.

  • 0 = Fully transparent
  • 255 = Fully opaque
with Image.open('background.jpg') as bg, Image.open('logo.png') as logo:
    # Create a circular mask
    mask = Image.new('L', logo.size, 0)
    draw = ImageDraw.Draw(mask)
    draw.ellipse((0, 0) + logo.size, fill=255)

    # Paste logo onto background using the mask
    # The box defines the top-left corner for the paste
    bg.paste(logo, (50, 50), mask)
    # bg.show()

✨ Image Enhancement #

ImageEnhance Module #

Provides classes for adjusting contrast, brightness, color, and sharpness.

with Image.open('image.jpg') as im:
    # Brightness: 0.0 (black) -> 1.0 (original) -> >1.0 (brighter)
    enhancer = ImageEnhance.Brightness(im)
    bright_im = enhancer.enhance(1.8)

    # Contrast: 0.0 (solid gray) -> 1.0 (original) -> >1.0 (more contrast)
    enhancer = ImageEnhance.Contrast(im)
    contrast_im = enhancer.enhance(1.5)

    # Sharpness: 0.0 (blurred) -> 1.0 (original) -> 2.0 (sharpened)
    enhancer = ImageEnhance.Sharpness(im)
    sharp_im = enhancer.enhance(2.0)
    # sharp_im.show()

ImageOps Module #

Ready-made image processing operations.

with Image.open('image.jpg') as im:
    # Auto-contrast: Maximizes image contrast
    auto_contrast_im = ImageOps.autocontrast(im, cutoff=5)

    # Equalize: Equalizes the image histogram
    equalized_im = ImageOps.equalize(im)

    # Invert colors
    inverted_im = ImageOps.invert(im.convert('RGB'))

    # Solarize: Invert all pixel values above a threshold
    solarized_im = ImageOps.solarize(im, threshold=128)
    # solarized_im.show()

πŸ”¬ Pixel-Level Operations #

point() Method & eval() Function #

  • im.point(lambda i: i * 1.5): Applies a function to each pixel. Fast for single-band images.
  • Image.eval(im, func): Applies a function to each pixel of each band in an image.
with Image.open('image.jpg') as im:
    # Increase brightness of a grayscale image
    brighter_l = im.convert('L').point(lambda p: p + 50)

    # Apply a function to each band of an RGB image
    def tint_red(pixel):
        return tuple(p + 30 if i == 0 else p for i, p in enumerate(pixel))
    # Note: eval is not available in Pillow 10.0.0+, use numpy instead
    # tinted_im = Image.eval(im, tint_red) # Deprecated

    # Modern approach with NumPy
    arr = np.array(im)
    arr[:, :, 0] = np.clip(arr[:, :, 0] + 30, 0, 255) # Add 30 to Red channel
    tinted_im = Image.fromarray(arr)
    # tinted_im.show()

Custom Convolution Kernels #

Apply custom filters using ImageFilter.Kernel.

from PIL import ImageFilter

# A simple edge detection kernel
kernel = ImageFilter.Kernel(
    (3, 3), # size
    (-1, -1, -1, -1, 8, -1, -1, -1, -1), # weights
    1, # scale
    0 # offset
)

with Image.open('image.jpg') as im:
    edge_im = im.filter(kernel)
    # edge_im.show()

πŸ”’ Integration with NumPy #

For complex, fast, per-pixel operations, converting to a NumPy array is the best practice.

with Image.open('image.jpg') as im:
    # Image to NumPy array
    arr = np.array(im)

    # Example: Set a rectangular region to black
    arr[100:300, 200:500] = 0 # [y1:y2, x1:x2]

    # Example: Create a sepia effect
    sepia_filter = np.array([
        [0.393, 0.769, 0.189],
        [0.349, 0.686, 0.168],
        [0.272, 0.534, 0.131]
    ])
    sepia_arr = arr @ sepia_filter.T
    sepia_arr = np.clip(sepia_arr, 0, 255).astype(np.uint8)

    # NumPy array back to Image
    processed_im = Image.fromarray(sepia_arr)
    # processed_im.show()

🎞️ Creating Animations (GIFs) #

Save a list of Image objects as an animated GIF.

frames = []
for i in range(10):
    # Create a new frame for each step
    frame = Image.new('RGB', (200, 200))
    draw = ImageDraw.Draw(frame)
    draw.ellipse((i*10, i*10, 50+i*10, 50+i*10), fill='blue')
    frames.append(frame)

# Save the frames as an animated GIF
frames[0].save(
    'animation.gif',
    save_all=True,
    append_images=frames[1:],
    duration=100, # milliseconds per frame
    loop=0 # 0 means loop forever
)