Mastering Python Pillow
Mastering Python Pillow
Python Pillow
PIL / Pillow
The Complete Programmer's Reference
› NumPy integration
Chapter 11 · Colour Operations and Channels
› split() and merge()
› Alpha channel manipulation
› ImageChops arithmetic
—3—
MASTERING PYTHON PILLOW Complete Programmer's Reference
—4—
MASTERING PYTHON PILLOW Complete Programmer's Reference
› ImageFilter
› ImageEnhance
› ImageOps
› ImageChops
› ImageColor
› ImageStat
› ImageSequence
› ImageCms
› ImageGrab
Chapter 35 · Common Errors and Troubleshooting
› FileNotFoundError
› OSError
› Mode errors
› Font errors
› Solutions
Chapter 36 · Glossary
› PIL, RGBA, EXIF, DPI, JPEG and 40+ terms
—5—
MASTERING PYTHON PILLOW Complete Programmer's Reference
PART I
FOUNDATIONS
At its core, PIL treats every image as a rectangular grid of pixels. Each pixel holds a colour value —
usually expressed as a combination of red, green, and blue intensities. All image operations in Pillow
are ultimately about reading, computing, and writing these pixel values, whether you're cropping a
region, blurring the whole image, or drawing a line across the canvas.
PIL development slowed and eventually stopped around 2009. The last official release was version
1.1.7. As Python 2 gave way to Python 3, PIL's lack of Python 3 support became a serious problem. In
2010, Alex Clark and a team of contributors created Pillow — a community-maintained fork of PIL that
immediately added Python 3 support and resumed active development. Today Pillow is maintained on
GitHub by a team of volunteers and releases new versions regularly.
Year Event
1998 PIL 1.0 released — supports JPEG, PNG, BMP and many others
2010 Alex Clark forks PIL as 'Pillow' on GitHub, adds Python 3 support
—6—
MASTERING PYTHON PILLOW Complete Programmer's Reference
Year Event
• File I/O: Open, read, write and save 30+ image formats including JPEG, PNG, GIF, WebP, TIFF,
BMP, ICO, PSD, EPS, TGA, and PDF
• Geometry: Resize, crop, rotate, flip, transpose, warp, and apply affine/perspective transforms
• Colour: Convert between 10+ colour modes (RGB, RGBA, L, CMYK, HSV, LAB...), manipulate
channels, adjust histograms
• Drawing: Draw lines, rectangles, circles, ellipses, arcs, polygons, and curves onto images
• Text: Render text in any TrueType or OpenType font at any size with full Unicode support
• Filters: Apply 15+ built-in convolution filters including Gaussian blur, sharpen, edge detection,
emboss
• Enhancement: Adjust brightness, contrast, colour saturation, and sharpness with precise
numerical control
• Compositing: Paste, blend, and composite images together using alpha channels and masks
• Statistics: Analyse pixel distributions, compute histograms, mean, standard deviation per channel
• Sequences: Read and write animated GIF, multi-frame TIFF, and other multi-page formats
• Screen capture: Take screenshots of the entire screen or a specific region (ImageGrab)
• Colour management: Apply ICC colour profiles for accurate cross-device colour reproduction
• Pixel access: Read and write individual pixels, apply per-pixel transforms via lookup tables
■ Note: Pillow is the most downloaded image processing library on PyPI with hundreds of millions of
downloads. It is a dependency of Django, Matplotlib, scikit-image, and many other major Python libraries.
—7—
MASTERING PYTHON PILLOW Complete Programmer's Reference
The package name is Pillow (capital P) on PyPI. However, when you import it in Python, you use the
original name PIL — this is because Pillow maintains backward compatibility with the original PIL
namespace.
# INSTALL command (terminal / command prompt) pip install Pillow # UPGRADE existing
installation pip install --upgrade Pillow # Install a specific version pip install
Pillow==10.2.0 # Install with optional dependencies (WebP, AVIF, JPEG2000 etc.) pip
install Pillow[all]
10.2.0 --- PIL CORE support ok, compiled for 10.2.0 --- JPEG support ok --- PNG support ok
--- WebP support ok ...
—8—
MASTERING PYTHON PILLOW Complete Programmer's Reference
■ Tip: If you install Pillow via pip on Windows, macOS, or modern Linux, the pre-built wheel includes libjpeg,
zlib, libpng, and libwebp automatically. You only need to install system dependencies if you are compiling
from source.
—9—
MASTERING PYTHON PILLOW Complete Programmer's Reference
In Pillow, the coordinate system places the origin (0, 0) at the top-left corner of the image. The x-axis
increases to the right, and the y-axis increases downward. This is the standard convention for computer
graphics (note: this is different from mathematics where y increases upward).
↓ ↓ increasing y
# Some example RGB pixel values pixel_red = (255, 0, 0) # pure red pixel_green = ( 0, 255,
0) # pure green pixel_blue = ( 0, 0, 255) # pure blue pixel_white = (255, 255, 255) # white
pixel_black = ( 0, 0, 0) # black pixel_yellow = (255, 255, 0) # yellow pixel_gray = (128,
128, 128) # 50% gray
DPI (Dots Per Inch) is a printing concept — it tells a printer how many pixels to place per inch of paper.
A 300 DPI image will look sharp when printed. A 72 DPI image is fine for screen display but will look
blurry when printed large. DPI does not affect how an image looks on screen — only its print size.
— 10 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
• 1-bit: Each pixel is 0 or 1 (black or white only). Used for fax images and masks.
• 8-bit per channel (24-bit total for RGB): The standard for photographs. 256 levels per channel.
• 16-bit per channel (48-bit total): Used in professional photography and RAW image editing.
Smoother gradients.
• 32-bit float: Used in scientific imaging and HDR. Values not limited to 0–255.
■ Note: Standard Pillow images use 8-bit per channel (mode 'L' for grayscale, 'RGB' for colour). For 16-bit
images use mode 'I' (32-bit integer) or 'F' (32-bit float). Most consumer cameras and monitors work with 8-bit.
— 11 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
The mode of an image is one of the most important concepts in Pillow. It defines what kind of data each
pixel holds: how many channels (colour components) there are, what each channel means, and how
many bits per pixel are used. Getting the mode right is essential — many Pillow operations only work on
specific modes.
1 Binary 1 1 0 or 255 Pure black & white. Each pixel is either off (0) or on (255). Used fo
L Luminance 1 8 int 0–255 Grayscale. One channel, 256 shades of gray. Used for B&W phot
P Palette 1 8 int 0–255 (index) Indexed colour. Each pixel is an index into a 256-colour palette ta
RGB Red Green Blue 3 24 (R,G,B) tuple Standard full colour. The most common mode. Used for JPEG, BM
RGBA RGB + Alpha 4 32 (R,G,B,A) tuple RGB with transparency. Alpha channel 0=transparent, 255=opaqu
LAB CIE L*a*b* 3 24 (L,a,b) tuple Perceptually uniform colour space. Good for colour-difference ope
HSV Hue Saturation Value 3 24 (H,S,V) tuple Intuitive colour control. H=colour type, S=vividness, V=brightness
I Integer pixels 1 32 int32 32-bit signed integer pixels. Used for scientific and medical imagin
F Float pixels 1 32 float32 32-bit floating point pixels. Used for scientific data, HDR, and inte
— 12 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
■ Warning: Converting RGBA → RGB with .convert('RGB') will make transparent pixels solid black. Always
composite onto a background colour first if you want transparent areas to have a specific fill colour.
Transparent
Calling .paste() with RGBA onto RGB areas paste as black Use mask=[Link]()[3] parameter
Operations
Loading a PNG and getting palette fail or look wrong
('P') Convert: [Link]('RGBA') or [Link]('RGB')
Trying to getpixel on 'CMYK' Returns 4-tuple, not 3 Remember CMYK has 4 channels (C,M,Y,K)
— 13 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
Pillow supports an impressive range of image file formats. Understanding the strengths and limitations
of each format helps you choose the right one for every use case and configure saving options
correctly.
JPEG .jpg Yes No No Photos, web images Each save degrades quality
PNG .png No Yes No Screenshots, graphics Large file size for photos
GIF .gif No* Yes* Yes Simple animations Max 256 colours per frame
WebP .webp Both Yes Yes Web — best compression Not all software supports it
TIFF .tiff Both Yes Yes Print, archival, scientific Very large files
ICO .ico No Yes No Windows application icons Limited to 256×256px per size
PDF .pdf Dep No Yes Multi-page document output Read-only in most editors
AVIF .avif Both Yes Yes Next-gen web images Requires libavif, newer
* GIF supports only 1-bit transparency (one transparent colour), not full alpha. Lossy via 256-colour quantisation.
■ Warning: Never repeatedly open and re-save a JPEG. Each save is lossy — the image degrades further
with each generation. If you need to edit a JPEG multiple times, keep the working copy as PNG or TIFF, and
only export to JPEG as the final step.
— 14 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
# Save PNG with maximum compression (smallest file, slower save) [Link]('[Link]',
optimize=True, compress_level=9) # Save PNG with fast compression (faster save, slightly
larger file) [Link]('[Link]', compress_level=1) # Save RGBA PNG (with transparency)
rgba_img.save('[Link]') # transparency preserved automatically
— 15 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
Understanding how Pillow is organised helps you know which module to reach for when you need a
specific feature. Pillow is structured as a set of sub-modules, each focused on a specific aspect of
image processing.
Image Core module — open, save, create, resize, crop, convert, paste from PIL import Image
ImageDraw Draw shapes, lines, arcs, polygons, text onto images from PIL import ImageDraw
ImageFont Load TrueType/OpenType fonts for use with ImageDraw from PIL import ImageFont
ImageFilter Predefined and custom convolution filters (blur, sharpen, edges...) from PIL import ImageFilter
ImageEnhance Adjust brightness, contrast, colour saturation, sharpness from PIL import ImageEnhance
ImageOps High-level one-liner operations (grayscale, invert, fit, pad...) from PIL import ImageOps
ImageChops Arithmetic operations on image channels (add, subtract, multiply...) from PIL import ImageChops
ImageColor Parse colour strings ('red', '#ff0000', 'hsl(0,100%,50%)') from PIL import ImageColor
ImageStat Statistical analysis of images (mean, std, histogram, extrema) from PIL import ImageStat
ImageSequence Iterate over frames in animated GIFs and multi-page TIFFs from PIL import ImageSequence
ImageCms Colour management using ICC profiles for print-accurate colour from PIL import ImageCms
ImageGrab Take screenshots of the screen or a specific screen region from PIL import ImageGrab
ImagePath 2D path geometry operations for drawing complex shapes from PIL import ImagePath
ImagePalette Manage colour palettes for indexed-colour ('P' mode) images from PIL import ImagePalette
ImageTk Convert Pillow images for display in Tkinter GUI applications from PIL import ImageTk
ImageQt Convert Pillow images for display in Qt (PyQt5/PySide2) apps from PIL import ImageQt
ExifTags Dictionary mapping EXIF tag ID numbers to human-readable names from PIL import ExifTags
Once loaded, an Image object stores its pixel data in a C-level buffer. Python operations on this buffer
go through the ImagingCore C extension which provides high performance. When you apply a filter or
resize an image, Pillow calls optimised C code internally — not a slow Python loop.
— 16 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
■ Tip: Always call [Link]() explicitly if you need to ensure pixel data is in memory before closing the
source file or before passing the image to another thread.
— 17 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
PART II
The Image module is the single most important module in Pillow. Every image processing task begins
here. An Image object is the Python representation of an image — it holds the pixel data, mode, size,
and metadata. Nearly every other Pillow module works by receiving or returning Image objects.
[Link](fp) Open an image file from disk or stream Loading any image
[Link](mode, bands) Merge single-channel images into one Reassembling colour channels
— 18 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
[Link]() Load pixels; return pixel access object Fast pixel-level editing
fp str/Path/IO required File path string, [Link], or file-like object opened in binary mode
mode str 'r' Must be 'r' (read). Provided for API compatibility.
formats list/None None List of format names to try. If None, all supported formats are tried.
Returns: An Image object. The file is not fully loaded until pixel data is first accessed.
from PIL import Image # Open from a file path (string) img = [Link]('[Link]') # Open
from a [Link] from pathlib import Path img = [Link](Path('photos') /
'[Link]') # Open from a URL (download first) import [Link] from io import
BytesIO url = '[Link] with [Link](url) as resp:
img = [Link](BytesIO([Link]())) # Always convert mode after opening if you need a
specific mode img = [Link]('[Link]').convert('RGB') # force RGB (strips alpha) img
= [Link]('[Link]').convert('RGBA') # ensure RGBA (adds alpha if missing)
— 19 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
Common Errors:
• FileNotFoundError: The file path does not exist. Check spelling, forward/backward slashes, and
working directory.
• UnidentifiedImageError: The file is not a valid image or is corrupted.
• OSError: cannot identify image file: Same as above — file may be empty or not an image.
mode str required Colour mode: 'RGB', 'RGBA', 'L', '1', 'CMYK', etc.
color int/tuple/str 0 Fill colour. Can be int (for L/1), tuple (for RGB/RGBA), or colour name string.
# White RGB canvas canvas = [Link]('RGB', (800, 600), (255, 255, 255)) # Black RGB
canvas (default) canvas = [Link]('RGB', (800, 600)) # Colour by name blue_canvas =
[Link]('RGB', (400, 300), 'royalblue') # Fully transparent RGBA canvas transp =
[Link]('RGBA', (400, 300), (0, 0, 0, 0)) # Semi-transparent red RGBA red_50 =
[Link]('RGBA', (400, 300), (255, 0, 0, 128)) # Grayscale canvas — 50% gray gray =
[Link]('L', (400, 300), 128)
— 20 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
Resizing is one of the most common image operations. Pillow provides two primary methods: resize()
for exact dimensions and thumbnail() for aspect-ratio-safe scaling. The choice of resampling algorithm
significantly affects both output quality and processing speed.
size tuple required (width, height) in pixels — the exact output size
box tuple None (left, upper, right, lower) source region to resize. Resizes a crop of the source.
reducing_gap float None Speed optimisation for large downscaling. 2.0 or 3.0 gives good results.
from PIL import Image img = [Link]('[Link]') # Basic resize — LANCZOS for best
quality small = [Link]((640, 480), [Link]) # Fast resize for preview (lower
quality but fast) preview = [Link]((320, 240), [Link]) # Resize a specific
region of the source image region_resized = [Link]((200, 200), [Link],
box=(100, 100, 500, 500)) # Enlarge (upscale) large = [Link](([Link] * 2, [Link]
* 2), [Link]) # Resize maintaining aspect ratio manually def
resize_keep_aspect(img, max_w, max_h): ratio = min(max_w / [Link], max_h / [Link])
new_w = int([Link] * ratio) new_h = int([Link] * ratio) return [Link]((new_w,
new_h), [Link])
■ Tip: Use [Link] for any final output that people will see. Use [Link] or
[Link] for quick previews during development. The difference in quality is especially visible when
upscaling.
— 21 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
■ Note: Because thumbnail() modifies in-place, always work on a copy if you want to preserve the original:
copy = [Link](); [Link]((256, 256))
— 22 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
[Link](box=None)
box tuple None (left, upper, right, lower) pixel coordinates. Returns full image if None.
The box coordinates use the standard Pillow coordinate system: (0,0) = top-left. The box is exclusive
on the right and bottom — crop((0,0,100,100)) returns a 100×100 image with pixels at x=0..99, y=0..99.
img = [Link]('[Link]') # 1920 × 1080 # Basic crop cropped = [Link]((100, 50, 700,
450)) # 600 × 400 region # Crop the top half top_half = [Link]((0, 0, [Link],
[Link] // 2)) # Crop the left third left_third = [Link]((0, 0, [Link] // 3,
[Link])) # Crop the centre square side = min([Link], [Link]) cx, cy = [Link]
// 2, [Link] // 2 center_square = [Link]((cx - side//2, cy - side//2, cx + side//2,
cy + side//2)) # crop() can extend BEYOND image bounds — extra area is filled with 0 beyond
= [Link]((-50, -50, [Link]+50, [Link]+50)) # The outer 50 pixels on all sides will
be black
resample constant NEAREST Resampling filter. Use [Link] or [Link] for smooth rotation.
expand bool False If True, expand the canvas to fit the entire rotated image. If False, crop to original size.
fillcolor str/tuple None Colour for pixels outside the original image after rotation.
— 23 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
[Link](method)
[Link] Transpose along main diagonal (reflect x and y) Rare — mathematical transpose
■ Tip: Always use ImageOps.exif_transpose() on photos taken with a smartphone. Phone cameras often
store images in their native sensor orientation and record the correct viewing orientation in EXIF metadata.
Without this fix, many photos will appear sideways.
— 24 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
10 Pixel-Level Operations
Sometimes the operations you need are so specific that you must work on individual pixels. Pillow
provides several approaches, from simple but slow (getpixel/putpixel) to fast bulk access (load()) to
vectorised transforms (point()). Choosing the right approach is critical for performance.
getpixel / putpixel Very slow Low Accessing 1–10 pixels for inspection
load() pixel access Medium Low Loops over all pixels in Python
■ Warning: Never use getpixel/putpixel inside a loop over all pixels. A 1000×1000 image has 1,000,000
pixels. Python function call overhead makes this approach 100x slower than using load() and 1000x slower
than NumPy.
— 25 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
— 26 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
img = [Link]('[Link]') # RGB # Split into individual channels (each becomes an 'L'
mode image) r, g, b = [Link]() # Visualise a single channel [Link]() # shows red channel
as grayscale # Swap red and blue channels swapped = [Link]('RGB', (b, g, r)) #
Increase only the red channel r_arr = [Link](r) r_boosted =
[Link]([Link](r_arr * 1.5, 0, 255).astype(np.uint8)) boosted =
[Link]('RGB', (r_boosted, g, b)) # For RGBA — split out alpha r, g, b, a =
[Link]('RGBA').split() # ... modify ... result = [Link]('RGBA', (r, g, b, a))
— 27 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
— 28 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
PART III
The ImageDraw module provides 2D graphics drawing capabilities. It lets you draw lines, shapes, text,
and other geometric primitives directly onto an Image object. The drawing context is created once and
all drawing operations modify the underlying image pixel data in real time.
■ Note: The Draw object holds a reference to the image. Any drawing method called on 'draw' immediately
modifies the underlying image pixels. There is no 'commit' step — changes take effect the moment the
method returns.
[Link](xy, fill, width) Straight line(s) through a sequence of points Graphs, borders, underlining
Axis-aligned
[Link](xy, fill, outline, width)rectangle Boxes, borders, highlight regions
Rectangle
draw.rounded_rectangle(xy, radius, with rounded corners
...) Modern UI-style boxes
Ellipse
[Link](xy, fill, outline, inside a bounding box
width) Circles, ovals
Arcwidth)
[Link](xy, start, end, fill, (ellipse outline portion) Progress bars, dials
Arc + ...)
[Link](xy, start, end, fill, closing straight line Filled arc segments
Filled
[Link](xy, fill, outline, polygon from list of vertices
width) Triangles, stars, custom shapes
Regular
draw.regular_polygon(bc, n, rot, ...)N-sided polygon inscribed in circle Triangles, pentagons, hexagons
— 29 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
[Link](xy, fill) Single pixel or list of pixels Scatter plots, dot markers
Draw...)
[Link](xy, text, fill, font, text string at position Labels, captions, titles
draw.multiline_text(xy, text,Draw
...)text with newline support Paragraphs, multi-line labels
[Link](xy, bitmap, fill)Paste a 1-bit bitmap at position Custom icons, custom glyphs
[Link](xy, value) Flood fill from (x,y) with value Filling enclosed regions
12.3 Lines
# Single straight line from (x1,y1) to (x2,y2) [Link]([(10, 10), (590, 390)],
fill='black', width=3) # Multiple connected line segments [Link]([(50,50), (200,100),
(350,50), (500,100)], fill='blue', width=2) # Horizontal rule [Link]([(0, 200),
([Link], 200)], fill='gray', width=1) # Diagonal cross [Link]([(0, 0),
([Link], [Link])], fill='red', width=2) [Link]([([Link], 0), (0,
[Link])], fill='red', width=2)
12.4 Rectangles
# Filled rectangle [Link]([50, 50, 250, 150], fill='skyblue') # Outline only (no
fill) [Link]([50, 50, 250, 150], outline='navy', width=3) # Filled with outline
[Link]([50, 50, 250, 150], fill='lightyellow', outline='goldenrod', width=2) #
Rounded rectangle (Pillow 8.2+) draw.rounded_rectangle([50, 50, 250, 150], radius=20,
fill='coral', outline='red', width=2) # Draw a grid for x in range(0, [Link], 50):
[Link]([(x, 0), (x, [Link])], fill='lightgray', width=1) for y in range(0,
[Link], 50): [Link]([(0, y), ([Link], y)], fill='lightgray', width=1)
— 30 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
Text rendering in Pillow combines two modules: ImageDraw provides the [Link]() methods, and
ImageFont provides the font objects. You can use a built-in bitmap font (no files needed) or load any
TrueType (.ttf) or OpenType (.otf) font file for professional typography.
ImageFont.load_default(size) Load built-in bitmap font Quick tests, no font file needed
[Link](text) Get bounding box of text with this font Layout calculations
[Link] Font size in points (attribute, not method) Checking loaded font size
— 31 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
100), font=font)
■ Tip: Use the anchor='mm' parameter to position text from its visual centre rather than its top-left corner.
Common anchor values: 'la'=left-ascender, 'mm'=centre, 'rs'=right-baseline, 'lt'=left-top, 'rb'=right-bottom.
— 32 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
PART IV
The ImageFilter module provides a collection of convolution filters and rank filters for image processing.
A convolution filter works by sliding a small matrix (called a kernel) over every pixel in the image,
computing a weighted average of the pixel and its neighbours. Different kernel values produce different
effects — blurring, sharpening, edge detection, and more.
# Applying any filter is always the same one line: result = [Link]([Link]) #
Stacking multiple filters result =
[Link]([Link](3)).filter([Link])
[Link] Kernel None Simple 5×5 box blur. Uniformly softens the image.
[Link] Kernel None Traces edges and renders them as sketch-like lines on gray background.
[Link] Kernel None Enhances local contrast and fine texture detail slightly.
ImageFilter.EDGE_ENHANCE Kernel None Sharpens edges throughout the image at moderate strength.
Kernel
ImageFilter.EDGE_ENHANCE_MORE None Stronger version of EDGE_ENHANCE. Very pronounced edge sharpenin
ImageFilter.FIND_EDGES Kernel None Edge detection. Bright lines on black where colour changes rapidly.
[Link] Kernel None Standard sharpening kernel. Enhances detail across the entire image.
[Link] Kernel None Gentle smoothing. Reduces noise while preserving edges lightly.
ImageFilter.SMOOTH_MORE Kernel None Stronger smoothing than SMOOTH. More blurring, more noise reduction.
[Link](r)Gaussian radius (float) High-quality bell-curve blur. More natural than box blur.
— 33 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
[Link](r) Box radius (int) Fast averaging blur. Less natural than Gaussian but faster.
USM
[Link](r,p,t) radius,percent,threshold Professional sharpening technique. Best for final output sharpening.
[Link](s)Rank size (odd int) Replaces each pixel with median of neighbourhood. Excellent for noise.
[Link](s) Rank size (odd int) Replaces pixel with minimum in neighbourhood. Erodes bright areas.
[Link](s) Rank size (odd int) Replaces pixel with maximum in neighbourhood. Dilates bright areas.
[Link](s) Rank size (odd int) Replaces pixel with most common value. Smooths areas of uniform colou
Custom
[Link](sz,k,s,o) size,kernel,scale,offset Apply your own custom convolution kernel matrix.
# UnsharpMask(radius, percent, threshold) # radius: how wide the sharpening halo is (1-3
typical) # percent: sharpening strength (100-200 typical) # threshold: minimum contrast to
sharpen (0-10, prevents noise sharpening) subtle =
[Link]([Link](radius=1, percent=100, threshold=3)) normal =
[Link]([Link](radius=2, percent=150, threshold=3)) strong =
[Link]([Link](radius=2, percent=250, threshold=2)) crisp =
[Link]([Link](radius=0.5, percent=200, threshold=0))
— 34 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
The ImageEnhance module provides four classes for adjusting visual properties of images with precise
numerical control. Each class has a single method: enhance(factor). A factor of 1.0 always returns
the original unchanged.
Class Factor 0.0 Factor 0.5 Factor 1.0 Factor 1.5 Factor 2.0
Brightness Pure black 50% darker Original 50% brighter Twice as bright
Contrast Solid grey Low contrast Original More contrast Very high contrast
— 35 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
The ImageOps module provides ready-made, single-call operations that would otherwise require
combining multiple lower-level steps. Think of it as a toolkit of the most common photo-editing and
layout operations.
[Link](img, bits)Reduce colour depth (bits per channel) Poster / graphic art effect
[Link](img, size) Resize to fit inside size, add padding Letterbox / pillarbox
[Link](img, size) Resize to fill size exactly, crop excess Social media thumbnails
[Link](img, border) Remove border pixels from all 4 sides Trimming whitespace
[Link](img, factor) Scale by float factor (e.g. 0.5 = half size) Quick proportional scaling
ImageOps.exif_transpose(img) Auto-rotate based on EXIF orientation tag Fix sideways phone photos
— 36 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
The ImageColor module provides utilities for parsing colour strings into pixel values. You rarely need to
import it directly — Pillow calls it internally when you pass a string colour to fill or outline parameters.
But it is useful when you need to convert colour names to RGB tuples in your own code.
— 37 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
PART V
The ImageStat module computes statistical properties of an image's pixel distribution. This is useful for
determining if an image is over- or under-exposed, comparing images, detecting anomalies, and making
data-driven processing decisions.
[Link] list of (min,max) tuples Min and max pixel value per channel
[Link] list of floats Root mean squared pixel value per channel
— 38 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
A histogram shows the distribution of pixel values across all 256 levels for each channel. It is the
fundamental tool for understanding an image's exposure and tonal range.
— 39 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
Alpha compositing is the process of combining images using transparency information. It is fundamental
to creating professional overlays, watermarks, UI elements, and multi-layer image compositions.
Understanding the different compositing methods and when to use each one is an important skill.
paste(im, box, mask) In-place No (mask optional) Yes (with mask) Overlaying any image with optional transparency
alpha_composite(dst, src) Returns new Yes — both RGBA Yes — full Correct layer compositing, UI elements
blend(im1, im2, alpha) Returns new No — same mode No Cross-fade / dissolve transitions
composite(im1, im2, mask) Returns new No Via mask Masked blending, custom alpha shapes
— 40 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
The ImageCms module provides colour management features using ICC (International Color
Consortium) profiles. Colour management ensures that colours look consistent across different devices
— cameras, monitors, and printers. For web work this is usually not needed, but for print production it is
essential.
from PIL import Image, ImageCms # Convert from sRGB to AdobeRGB img =
[Link]('[Link]').convert('RGB') srgb_profile = [Link]('sRGB') #
adobe_profile = [Link]('[Link]') # Build a reusable transform
transform = [Link]( srgb_profile, srgb_profile, 'RGB', 'RGB' ) converted
= [Link](img, transform) # Read embedded ICC profile from JPEG profile =
[Link]('icc_profile') if profile: cms_profile =
[Link]([Link](profile)) desc =
[Link](cms_profile) print(f'Embedded profile: {desc}')
— 41 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
The ImageSequence module provides tools for working with multi-frame images such as animated
GIFs and multi-page TIFFs. Each frame is a regular Image object that can be processed individually.
from PIL import Image, ImageSequence # --- READING an animated GIF --- gif =
[Link]('[Link]') print(f'Total frames: {gif.n_frames}') # Method 1:
[Link] for i, frame in enumerate([Link](gif)):
[Link](f'frame_{i:03d}.png') # Method 2: Manual seek for i in range(gif.n_frames):
[Link](i) # [Link]() returns current frame index duration = [Link]('duration',
100) # ms per frame print(f'Frame {i}: {[Link]}, {duration}ms') frame_copy =
[Link]().convert('RGB') # copy before seeking away frame_copy.save(f'frame_{i}.jpg') #
--- CREATING an animated GIF --- frames = [] for i in range(36): frame = [Link]('RGB',
(200, 200), 'white') from PIL import ImageDraw draw = [Link](frame) # Draw rotating
line import math angle = [Link](i * 10) cx, cy = 100, 100 [Link]([(cx, cy), (cx +
80*[Link](angle), cy + 80*[Link](angle))], fill='navy', width=3) [Link](frame)
# Save as animated GIF frames[0].save('[Link]', save_all=True,
append_images=frames[1:], loop=0, # 0 = loop forever duration=50, # 50ms per frame = 20fps
optimize=True)
— 42 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
The ImageGrab module can take screenshots of the screen or a specific region. It returns a regular PIL
Image object, so all standard Pillow operations can be applied immediately. ImageGrab is available on
Windows and macOS natively. On Linux it requires an additional dependency.
from PIL import ImageGrab # Capture the entire screen screenshot = [Link]()
[Link]('[Link]') # Capture a specific region: (left, upper, right, lower)
region = [Link](bbox=(100, 100, 800, 600)) [Link]('[Link]') # On Linux —
requires: pip install mss # from mss import mss # with mss() as sct: # screenshot =
[Link](output='[Link]') # Grab from all monitors (include_layered_windows=False,
all_screens=True) all_screens = [Link](all_screens=True) # Continuous screen
recording — save a frame every second import time for i in range(10): frame =
[Link]() [Link](f'screen_{i:03d}.png') [Link](1.0)
■ Note: ImageGrab requires the display/screen to be accessible. It will not work in headless server
environments. For Linux, install with: pip install Pillow[extras]
— 43 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
PART VI
One of Pillow's most powerful real-world uses is batch processing — automatically performing the same
operation on hundreds or thousands of images. This is essential for web development, data science,
photography workflows, and e-commerce.
from PIL import Image, ImageOps from pathlib import Path from [Link] import
ProcessPoolExecutor, as_completed import os def process_one(args): src_path, dst_path =
args try: img = [Link](src_path) img = ImageOps.exif_transpose(img)
[Link]((800, 800), [Link]) [Link]('RGB').save(dst_path, quality=85,
optimize=True) return src_path.name, True, None except Exception as e: return
src_path.name, False, str(e) def parallel_batch(src_dir, dst_dir, max_workers=None): src =
Path(src_dir) dst = Path(dst_dir) [Link](parents=True, exist_ok=True) if max_workers is
None: max_workers = os.cpu_count() # use all CPU cores files = list([Link]('*.jpg')) +
list([Link]('*.png')) jobs = [(f, dst / [Link] + '.jpg') for f in files] with
ProcessPoolExecutor(max_workers=max_workers) as ex: futures = {[Link](process_one, j):
j for j in jobs} done = 0 for fut in as_completed(futures): name, ok, err = [Link]()
done += 1 status = 'OK' if ok else f'FAIL: {err}' print(f'[{done}/{len(jobs)}] {name}:
{status}') parallel_batch('photos/', 'thumbnails/')
— 44 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
26 Performance Optimisation
When processing large images or large numbers of images, performance matters. Understanding how
Pillow handles memory and computation allows you to write significantly faster code.
Use [Link] for non-final resizes 4–10× Good for previews; bad for final output
Use [Link]() for JPEG thumbnails 3–5× JPEG only. Reduces decode memory too.
Use point() instead of pixel loops 50–200× Built-in C-speed LUT application
Use NumPy for mathematical ops 100–500× Best for per-pixel math
Parallel processing (multiprocessing) N× CPUs Great for batch jobs. 8 cores = ~8× faster
Save JPEG with optimize=True 10–30% smaller Slower save but smaller files
Use WebP instead of JPEG/PNG 25–50% smaller Modern format, great browser support
Open images lazily, process on-demand Low RAM Don't load all images at once
— 45 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
PART VII
This project creates a professional command-line batch image resizer. It scans a folder for images, fixes
EXIF rotation, resizes to a specified maximum dimension while preserving aspect ratio, and saves the
results with detailed logging.
"""batch_resize.py — Professional batch image resizer""" from PIL import Image, ImageOps
from pathlib import Path import argparse import logging import time [Link](
level=[Link], format='%(asctime)s [%(levelname)s] %(message)s', datefmt='%H:%M:%S' )
log = [Link](__name__) SUPPORTED = {'.jpg', '.jpeg', '.png', '.webp', '.bmp',
'.tiff', '.gif'} def resize_image(src: Path, dst: Path, max_size: int, quality: int = 85,
fmt: str = None) -> dict: """Resize a single image. Returns a stats dict.""" t0 =
[Link]() img = [Link](src) img = ImageOps.exif_transpose(img) # fix phone rotation
orig_size = [Link] orig_mode = [Link] # Resize to fit within max_size × max_size
[Link]((max_size, max_size), [Link]) # Determine output format out_fmt = fmt
or [Link]('.').upper() if out_fmt in ('JPG',): out_fmt = 'JPEG' # Ensure
correct mode for format if out_fmt == 'JPEG' and [Link] in ('RGBA', 'P'): bg =
[Link]('RGB', [Link], 'white') if [Link] == 'P': img = [Link]('RGBA')
[Link](img, mask=[Link]()[3] if [Link]=='RGBA' else None) img = bg out_path = dst /
([Link] + f'.{out_fmt.lower()}') save_kwargs = {'quality': quality, 'optimize': True} if
out_fmt == 'JPEG' else {} [Link](out_path, format=out_fmt, **save_kwargs) return {
'src': [Link], 'orig': orig_size, 'new': [Link], 'mode': orig_mode, 'time':
[Link]() - t0, 'src_bytes': [Link]().st_size, 'dst_bytes': out_path.stat().st_size, }
def batch_resize(src_dir, dst_dir, max_size=1920, quality=85, fmt=None): src =
Path(src_dir) dst = Path(dst_dir) [Link](parents=True, exist_ok=True) files = [f for f
in [Link]() if [Link]() in SUPPORTED] [Link](f'Found {len(files)} images in
{src}') total_saved = 0 errors = 0 for i, path in enumerate(files, 1): try: stats =
resize_image(path, dst, max_size, quality, fmt) saved = stats['src_bytes'] -
stats['dst_bytes'] total_saved += saved [Link](f'[{i}/{len(files)}] {stats["src"]:30s} '
f'{stats["orig"]} → {stats["new"]} ' f'({stats["time"]*1000:.0f}ms, {saved//1024}KB
saved)') except Exception as e: [Link](f'[{i}/{len(files)}] FAILED {[Link]}: {e}')
errors += 1 [Link](f'Complete. {len(files)-errors} OK, {errors} failed. ' f'Total saved:
{total_saved//1024//1024:.1f} MB') # --- CLI interface --- if __name__ == '__main__': p =
[Link](description='Batch Image Resizer') p.add_argument('src',
help='Source folder') p.add_argument('dst', help='Output folder') p.add_argument('--size',
type=int, default=1920, help='Max dimension (default: 1920)') p.add_argument('--quality',
type=int, default=85, help='JPEG quality (default: 85)') p.add_argument('--format',
default=None, help='Output format (JPEG/PNG/WEBP etc)') args = p.parse_args()
batch_resize([Link], [Link], [Link], [Link], [Link]) # Run with: #
python batch_resize.py photos/ resized/ --size 1200 --quality 90 --format JPEG
— 46 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
This project creates a flexible watermarking tool that can add text or image watermarks to photos, with
configurable position, opacity, size, and batch mode.
— 47 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
— 48 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
Machine learning models require images to be a consistent size, mode, and format. This tool
preprocesses an entire image dataset for training, with augmentation options.
"""ml_preprocess.py — Image dataset preprocessing for machine learning""" from PIL import
Image, ImageOps, ImageEnhance, ImageFilter import random from pathlib import Path def
preprocess_for_ml( img: [Link], target_size: tuple = (224, 224), mode: str = 'RGB',
normalize: bool = False # returns float array if True ) -> [Link]: """ Standard
preprocessing for ML: resize, mode convert, centre-crop. Equivalent to torchvision
[Link]([ [Link](256), [Link](224) ]) """ img =
ImageOps.exif_transpose(img) img = [Link](mode) # Step 1: Resize shortest side to
target w, h = [Link] tw, th = target_size scale = max(tw/w, th/h) img =
[Link]((int(w*scale), int(h*scale)), [Link]) # Step 2: Centre crop to exact
target img = [Link](img, target_size) return img def augment( img: [Link],
flip_prob: float = 0.5, rotate_range: float = 15.0, brightness_range: tuple = (0.8, 1.2),
contrast_range: tuple = (0.8, 1.2), blur_prob: float = 0.1, ) -> [Link]: """ Random
data augmentation to increase dataset diversity. """ # Random horizontal flip if
[Link]() < flip_prob: img = [Link](Image.FLIP_LEFT_RIGHT) # Random rotation
angle = [Link](-rotate_range, rotate_range) if abs(angle) > 0.5: img =
[Link](angle, resample=[Link], fillcolor=0) # Random brightness bf =
[Link](*brightness_range) img = [Link](img).enhance(bf) # Random
contrast cf = [Link](*contrast_range) img = [Link](img).enhance(cf)
# Occasional blur (simulates camera blur/motion) if [Link]() < blur_prob: img =
[Link]([Link](radius=0.8)) return img def prepare_dataset( src_dir:
str, dst_dir: str, target_size: tuple = (224, 224), augment_n: int = 3, # extra augmented
copies per image fmt: str = 'PNG', ): src = Path(src_dir) dst = Path(dst_dir)
[Link](parents=True, exist_ok=True) exts = {'.jpg', '.jpeg', '.png', '.webp', '.bmp'}
files = [f for f in [Link]('*') if [Link]() in exts] total = 0 for path in
files: try: img = [Link](path).convert('RGB') img = preprocess_for_ml(img,
target_size) # Save original preprocessed [Link](dst /
f'{[Link]}_orig.{[Link]()}') total += 1 # Save augmented copies for i in
range(augment_n): aug = augment(img) [Link](dst / f'{[Link]}_aug{i}.{[Link]()}')
total += 1 except Exception as e: print(f'ERROR: {[Link]}: {e}') print(f'Dataset ready:
{total} images in {dst}') # prepare_dataset('raw_dataset/', 'processed/',
target_size=(224,224), augment_n=4)
— 49 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
PART VIII
This chapter documents every common Pillow error, its cause, and its solution. Understanding these
errors will help you debug your programs quickly.
Cause: The file path you passed to [Link]() does not exist.
Solutions:
1. Check the file path for typos (including upper/lower case differences on Linux)
2. Print the absolute path: print(Path('[Link]').resolve())
3. Check that your working directory is what you expect: import os; print([Link]())
4. Use [Link]() to check before opening: if Path('[Link]').exists(): ...
Cause: The file exists but Pillow cannot recognise it as an image. Either it is not an image file, or it is corrupted.
Solutions:
1. Verify the file is actually an image: open it in an image viewer
2. Check the file extension — a file called .jpg might actually be a PDF or text file
3. Use [Link]() right after opening to check integrity
4. Check if the file downloaded completely (partial downloads are common)
Cause: You are trying to save an RGBA (transparent) image as JPEG. JPEG does not support transparency.
Solutions:
1. Convert to RGB first: [Link]('RGB').save('[Link]')
2. If you want to preserve transparency, save as PNG instead: [Link]('[Link]')
3. If you want a specific background for transparent areas: bg = [Link]('RGB', [Link],
'white'); [Link](img, mask=[Link]()[3]); [Link]('[Link]')
— 50 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
Solutions:
1. Convert: [Link]('RGB').save('[Link]')
2. GIF images are P mode — always convert before saving as JPEG
Cause: You are passing an image with the wrong mode to a function that requires a specific mode.
Solutions:
1. Check [Link] to see the current mode
2. Convert to the required mode: img = [Link]('RGB')
3. Read the function documentation to see which modes are accepted
Cause: The image file is incomplete — it was not fully downloaded or is corrupted.
Solutions:
1. Download the file again
2. Add: from PIL import ImageFile; ImageFile.LOAD_TRUNCATED_IMAGES = True (use with
caution — may cause garbled images)
Cause: Usually means you are trying to save to a path you don't have write permission for, or the disk is full.
Solutions:
1. Check that the output directory exists: [Link](dst_dir, exist_ok=True)
2. Check disk space
3. Check file/directory permissions
Cause: n_frames attribute does not exist for still images (only multi-frame formats like GIF/TIFF).
Solutions:
1. Use: n = getattr(img, 'n_frames', 1) to safely default to 1 for still images
2. Check: hasattr(img, 'n_frames') before accessing it
Solutions:
1. Use the full absolute path to the font file
2. Check font file locations: Windows: C:/Windows/Fonts/, macOS: /Library/Fonts/, Linux:
/usr/share/fonts/
3. Fall back to default: try: font = [Link]('[Link]', 36) except OSError: font =
ImageFont.load_default(36)
4. Install common fonts on Linux: apt install fonts-dejavu fonts-liberation
— 51 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
Solutions:
1. Use [Link]() for JPEG to resize during decode
2. Process image in strips/chunks
3. Reduce image size before processing
4. Use 64-bit Python (can address more memory)
— 52 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
36 Glossary
This glossary explains all important terms, abbreviations, and concepts used in image processing and in
this book.
Anti-aliasing
A technique for reducing the jagged 'staircase' appearance of diagonal lines and curves in raster images.
Achieved by blending edge pixels with surrounding colours. Pillow's LANCZOS and BICUBIC resampling
filters include anti-aliasing.
BMP — Bitmap
An uncompressed raster image format developed by Microsoft. Files are large because there is no
compression. Most commonly encountered in Windows environments.
Canvas
In the context of Pillow, a canvas is a blank Image object (usually created with [Link]()) onto which
you draw shapes, paste other images, or render text.
Channel
A single component of a pixel's colour value. An RGB image has three channels (Red, Green, Blue). An
RGBA image has four. A grayscale ('L') image has one.
Codec
Short for coder-decoder. A codec is the algorithm used to compress and decompress image (or video)
data. JPEG uses the DCT (Discrete Cosine Transform) codec.
Compositing
The process of combining two or more images into a single image, respecting transparency and blend
modes. See: alpha compositing, paste(), blend().
— 53 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
Convolution
A mathematical operation where a small kernel matrix is slid over every pixel, computing a weighted
average of the pixel and its neighbours. Most image filters (blur, sharpen, edge detect) are implemented
as convolutions.
Filter (image)
An operation that transforms pixel values based on local neighbourhood information. In Pillow, filters are
applied via [Link]([Link]) etc.
Grayscale
An image where each pixel has only one value (luminance) instead of three. Mode 'L' in Pillow. Values 0
(black) to 255 (white). Grayscale images are half the memory of equivalent RGB images.
Histogram
A bar chart showing the distribution of pixel values from 0 to 255 in an image. Used to evaluate exposure:
a histogram bunched on the left = dark image; bunched on the right = bright image; spread evenly = well
exposed.
ICC Profile
A standardised file that describes the colour characteristics of a device (camera, monitor, printer). Used in
colour management workflows to ensure consistent colour reproduction across devices. See ImageCms.
— 54 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
A small matrix (typically 3×3 or 5×5) of numbers used in convolution filters. The values define what
transformation is applied. A blur kernel averages neighbours. A sharpen kernel amplifies differences from
neighbours.
Lossless compression
A compression method that reduces file size without discarding any image data. The original pixel values
are perfectly reconstructed on decompression. PNG and GIF use lossless compression.
Lossy compression
A compression method that discards some image data to achieve smaller file sizes. The image cannot be
perfectly recovered after lossy compression. JPEG and lossy WebP use lossy compression.
NumPy
A Python library for numerical computing with fast array operations. Pillow images can be converted to
NumPy arrays for fast pixel-level mathematics, then converted back. Essential for machine learning
workflows.
Pillow
The modern, actively maintained fork of PIL. Installable via pip install Pillow. Imported as 'from PIL import
Image'. Supports Python 3.8+ and is maintained on GitHub at [Link]/python-pillow/Pillow.
Raster image
An image stored as a grid of pixels (as opposed to vector graphics which store mathematical shapes). All
formats that Pillow handles are raster images. Raster images have a fixed resolution — enlarging them
causes pixelation.
Resampling
— 55 —
MASTERING PYTHON PILLOW Complete Programmer's Reference
The algorithm used to compute pixel values when resizing an image. Higher quality algorithms
(LANCZOS) produce sharper results but are slower. NEAREST is fastest but produces blocky results.
BICUBIC is a good balance.
Thumbnail
A small preview version of an image, typically 100-300 pixels in its largest dimension. In Pillow,
[Link]() creates a thumbnail in-place while preserving aspect ratio and never enlarging the image.
WebP
A modern image format developed by Google. Supports both lossy and lossless compression, full alpha
transparency, and animation. Produces 25-35% smaller files than JPEG/PNG at equivalent quality.
Recommended for web use.
Thank you for reading Mastering Python Pillow. We hope this book serves as your definitive reference
for every image processing task you encounter. Pillow is a remarkably capable library — and now you
have the knowledge to use every part of it. Happy coding!
— 56 —