Documentation Menu

Pillow Handbook

The Python Imaging Library adds image processing capabilities to your Python interpreter. This library provides extensive file format support, an efficient internal representation, and fairly powerful image processing capabilities.

The core image library is designed for fast access to data stored in a few basic pixel formats. It should provide a solid foundation for a general image processing tool.

PIL vs Pillow: Pillow is a friendly fork of the Python Imaging Library (PIL). PIL development stopped in 2011, but Pillow continues with active maintenance, Python 3 support, and security patches. Always use from PIL import Image — not import Pillow.

Using the Image Class

The most important class in Pillow is the Image class. You can create instances by loading from files, processing other images, or creating from scratch.

Opening an Image
from PIL import Image im = Image.open("hopper.ppm") print(im.format, im.size, im.mode) # PPM (512, 512) RGB # Display the image (opens in default viewer) im.show()
AttributeTypeDescription
im.formatstr | NoneSource file format (e.g. 'JPEG', 'PNG'). None for programmatically created images.
im.modestrPixel format: '1', 'L', 'P', 'RGB', 'RGBA', 'CMYK', 'YCbCr', 'LAB', 'HSV', 'I', 'F'
im.sizetupleImage dimensions as (width, height) in pixels.
im.infodictMetadata dictionary populated by the file decoder (DPI, copyright, comments, EXIF).
im.widthintImage width in pixels (shortcut for im.size[0]).
im.heightintImage height in pixels (shortcut for im.size[1]).

Reading & Writing Files

Pillow can read and write over 30 image formats. The format is auto-detected on read, and inferred from the file extension on write.

Convert & Save
import sys from PIL import Image # Convert any image to PNG with Image.open(sys.argv[1]) as im: im.save(sys.argv[2]) # Convert PNG to JPEG (must drop alpha channel) img = Image.open("icon.png").convert("RGB") img.save("icon.jpg", quality=95) # Create a thumbnail — modifies in place, preserves aspect ratio img = Image.open("photo.jpg") img.thumbnail((128, 128)) img.save("thumb.jpg")

Using StringIO / BytesIO

In-Memory Buffer
from PIL import Image import io # Save to in-memory buffer (useful for web APIs) img = Image.open("photo.jpg") buf = io.BytesIO() img.save(buf, format="WEBP", quality=85) buf.seek(0) # buf.read() returns the raw bytes

Cutting, Pasting & Merging

The Image class contains methods to cut out sub-rectangles, paste them back, and merge separate bands into one image.

Copy / Paste Regions
im = Image.open("hopper.ppm") # Define a box: (left, upper, right, lower) box = (100, 100, 400, 400) region = im.crop(box) # Rotate region 180 degrees region = region.transpose(Image.Transpose.ROTATE_180) # Paste it back im.paste(region, box) im.save("patched.jpg") # Merge: split RGB into bands, swap R and B, merge r, g, b = im.split() merged_im = Image.merge("RGB", (b, g, r))

Geometric Transforms

Pillow supports resizing, rotating, and more complex geometric transforms via the transform() method.

MethodSignatureNotes
resize()resize(size, resample=None, box=None)Returns resized copy. Resample: NEAREST, BILINEAR, BICUBIC, LANCZOS
thumbnail()thumbnail(size, resample=BICUBIC)Modifies in-place. Preserves aspect ratio. Never upscales.
crop()crop(box)Returns rectangular sub-region. Box: (left, upper, right, lower)
rotate()rotate(angle, expand=False, center=None, translate=None)Counter-clockwise. expand=True grows canvas to fit.
transpose()transpose(method)Flip/rotate. Methods: FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM, ROTATE_90/180/270, TRANSPOSE, TRANSVERSE
transform()transform(size, method, data, resample)Affine, perspective, mesh, quad transforms.
Transforms Example
out = im.resize((128, 128), resample=Image.Resampling.LANCZOS) out = im.rotate(45, expand=True, fillcolor="white") out = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)

Color Transforms

Pillow can convert between image modes using convert(). It supports dithering when converting from colour to palette mode.

Mode Conversion
# Convert to grayscale gray = im.convert("L") # Convert to palette mode (256 colors) with dithering p_img = im.convert("P", dither=Image.Dither.FLOYDSTEINBERG) # Point transform: apply a lookup table lut = [i ** 2 // 255 for i in range(256)] out = im.point(lut) # Apply a different function per-band r, g, b = im.split() r = r.point(lambda i: i * 1.4) # boost red 40% out = Image.merge("RGB", (r, g, b))

Image Enhancement

The ImageEnhance module provides a standardized API for contrast, brightness, color saturation, and sharpness adjustment. Each class takes an Image and returns an enhancer object; call .enhance(factor) where 1.0 = no change.

Enhancement Pipeline
from PIL import Image, ImageEnhance img = Image.open("photo.jpg") # Increase contrast by 50% img = ImageEnhance.Contrast(img).enhance(1.5) # Increase sharpness (0.0=blurred, 1.0=original, 2.0=enhanced) img = ImageEnhance.Sharpness(img).enhance(2.0) # Boost saturation img = ImageEnhance.Color(img).enhance(1.2) # Increase brightness img = ImageEnhance.Brightness(img).enhance(1.1) img.save("enhanced.jpg")

Image Sequences (Animations)

Pillow has basic support for image sequences (animation formats like GIF and animated WebP). When you open a sequence file, Pillow automatically loads the first frame.

GIF Frame Extraction
from PIL import Image, ImageSequence img = Image.open("animation.gif") print(f"Frames: {img.n_frames}, animated: {img.is_animated}") for i, frame in enumerate(ImageSequence.Iterator(img)): frame.save(f"frame_{i:04d}.png") # Seek to a specific frame number img.seek(5) img.save("frame_5.png")

Creating a GIF

Creating Animated GIF
frames = [Image.open(ff"frame_{i}.png") for i in range(10)] frames[0].save( "output.gif", save_all=True, append_images=frames[1:], duration=100, # ms per frame loop=0 # 0 = loop forever )

Postscript Printing

Pillow includes a PSDraw module for drawing on Postscript printers. You can set the text font, rotate text, and draw images. This is primarily useful for print pipelines and legacy workflows.

Postscript Output
from PIL import Image, PSDraw import sys img = Image.open("hopper.ppm") title = "hopper" box = (1*72, 2*72, 7*72, 10*72) # 1"x2" to 7"x10" ps = PSDraw.PSDraw(sys.stdout) ps.begin_document(title) ps.image(box, img, 75) ps.setfont("HelveticaNarrow-Bold", 36) ps.text((3*72, 4*72), title) ps.end_document()

Batch Processing

Pillow is used extensively in batch workflows — converting, resizing, or watermarking large collections of images. Here are the key patterns.

Batch Resize All JPEGs
from PIL import Image from pathlib import Path input_dir = Path("./photos") output_dir = Path("./thumbnails") output_dir.mkdir(exist_ok=True) for img_path in input_dir.glob("*.jpg"): with Image.open(img_path) as img: img.thumbnail((800, 800), Image.Resampling.LANCZOS) img.save(output_dir / img_path.name, quality=85) print(ff"Processed: {img_path.name}")