cheat sheet
Pillow
Package-level reference for Pillow on PyPI — install variants, format-specific native deps, version policy, and alternatives.
Pillow
What it is
Pillow is the actively maintained fork of the original Python Imaging Library (PIL), which was last released in 2009. Forked in 2010 by Alex Clark and now stewarded by a community team, Pillow is the de-facto standard image I/O and pixel-manipulation library on PyPI. Almost every Python web framework, data-science library, ML preprocessor, and visualization tool reads images through Pillow.
Reach for Pillow when you need format support (JPEG, PNG, GIF, TIFF, WebP, BMP, ICO, and 25+ more), simple transforms (resize, crop, rotate, paste), or color-mode conversions. Reach for OpenCV when you need computer-vision algorithms; for wand if you want ImageMagick semantics; for imageio for video and scientific stacks.
Install
pip install pillow
Output: (none — exits 0 on success)
uv add pillow
Output: resolved + added to pyproject.toml
poetry add pillow
Output: updated lockfile + virtualenv install
conda install -c conda-forge pillow
Output: installs Pillow with a fully bundled libjpeg/libpng/libtiff/libwebp stack — preferred when wheels lag on a new Python.
Versioning & Python support
- Current stable line is the
11.xseries (as of late 2025). Pillow follows a roughly quarterly minor-release cadence. - Supports Python 3.9+ on recent releases. Older Pillow releases support 3.8 and earlier; the EOL policy matches CPython.
- Loose semver — major bumps drop EOL Python versions and may remove APIs deprecated in two prior majors.
- The deprecated
PILname is retained as the import path (from PIL import Image). The PyPI distribution name ispillow.
Package metadata
- Maintainer: Pillow contributors (Andrew Murray et al.)
- Project home: github.com/python-pillow/Pillow
- Docs: pillow.readthedocs.io
- PyPI: pypi.org/project/pillow
- License: HPND (Historical Permission Notice and Disclaimer — MIT-compatible)
- Governance: Tidelift-supported, community-maintained
- First released: 2010 (fork); PIL itself dates to 1995
- Downloads: hundreds of millions per month — consistently in the PyPI top 20
Optional dependencies & extras
Pillow has no extras_require markers — the wheel ships with bundled copies of the most common native codecs. Format support depends on what was compiled in at wheel-build time:
- JPEG — libjpeg (or libjpeg-turbo). Bundled in every official wheel.
- PNG — zlib. Bundled.
- TIFF / multi-page TIFF — libtiff. Bundled, but multi-page LZW and JPEG-inside-TIFF require recent libtiff.
- WebP / WebP-animated — libwebp. Bundled in wheels since
~8.0; missing in some minimal-Linux source builds. - HEIF / HEIC — not included. Install the separate
pillow-heifpackage and register:pillow_heif.register_heif_opener(). - AVIF — limited; install
pillow-avif-pluginfor full read/write. - JPEG2000 — needs libopenjp2 at wheel build time; ARM Linux wheels sometimes lack it.
- FreeType (text rendering) — bundled; lets
ImageFont.truetypeload TTF/OTF files. - LittleCMS (color management) — bundled; required for
ImageCmsICC-profile work.
For an exhaustive list of compiled-in features, run:
from PIL import features
features.pilinfo()
Output:
Pillow 11.x.y
Python ...
--------------------------------------------------------------------
--- PIL CORE support ok, compiled for 11.x.y
--- JPEG support ok, loaded ...
--- PNG support ok, loaded ...
... (truncated)
Alternatives
| Package | Trade-off |
|---|---|
opencv-python | Image + video + CV algorithms. Use for computer vision; much heavier dependency tree. |
imageio | Read/write 50+ formats including video and scientific. Use when you need uniform imread/imwrite across image and video. |
wand | Python binding for ImageMagick. Use when you want MagickWand semantics or already use ImageMagick CLI elsewhere. |
scikit-image | Image-processing algorithms on top of NumPy arrays. Use for filters, segmentation, morphology; not for I/O. |
pillow-simd | Drop-in Pillow fork with SIMD-accelerated resize/blur. Use only on Intel x86_64 with a compatible CPU. |
Common gotchas
- The import name is
PIL, notpillow.pip install pillowinstallsfrom PIL import Image.import pillowraisesModuleNotFoundError. The legacy name is retained for source compatibility with PIL. pillowandPILcannot coexist. If the original PIL is somehow still installed, uninstall it first:pip uninstall PIL pillow && pip install pillow. Mixed installs produce confusing namespace errors.- HEIF / HEIC photos from iPhones won't open. Pillow does not include libheif. Install
pillow-heifand callregister_heif_opener()once at startup. - WebP write quality defaults are aggressive.
Image.save("out.webp")uses quality=80; for archival-quality images passquality=95, method=6, lossless=Trueas appropriate. - Security CVEs. Pillow has a long history of buffer-overflow CVEs in native-codec paths — image parsers are large attack surfaces. Always pin to the latest Pillow rather than a fixed older version, and never run user-uploaded images on an old Pillow in a privileged process.
- Orientation EXIF is not auto-applied. A JPEG with
Orientation=6loads as raw pixels;Image.sizereports pre-rotation dimensions. UsePIL.ImageOps.exif_transpose(img)to apply orientation. save()infers format from extension only.img.save("photo.jpeg2000")raises — useimg.save("photo.jp2", format="JPEG2000"). Passformat=explicitly when the extension is ambiguous or non-standard.
Real-world recipes
Package-level recipes — patterns where the install matrix, codec choice, or production wrapping matters. For pure API usage (modes, draw, EXIF, filters) see the companion Python article.
Recipe 1 — JPEG batch thumbnailer with deterministic output
from pathlib import Path
from PIL import Image, ImageOps
SRC = Path("inbox")
DST = Path("thumbs")
DST.mkdir(exist_ok=True)
for src in SRC.glob("*.jpg"):
with Image.open(src) as im:
im = ImageOps.exif_transpose(im) # apply EXIF rotation
im.thumbnail((512, 512), Image.LANCZOS)
dst = DST / src.name
im.save(dst, "JPEG", quality=85, optimize=True, progressive=True)
Output: every inbox/*.jpg becomes a max-512px progressive JPEG in thumbs/. optimize=True runs a second Huffman pass — a few percent smaller files at ~2x encode cost.
Recipe 2 — WebP conversion pipeline (lossless when small, lossy when big)
from PIL import Image
from pathlib import Path
def convert(src: Path) -> Path:
dst = src.with_suffix(".webp")
with Image.open(src) as im:
if src.stat().st_size < 100_000:
im.save(dst, "WEBP", lossless=True, quality=100, method=6)
else:
im.save(dst, "WEBP", quality=85, method=6)
return dst
Output: thumbnails and icons land as lossless WebP, photos as lossy. method=6 is the slowest/best encoder; method=0 is ~20x faster and ~5% larger.
Recipe 3 — watermarking with alpha-composited PNG
from PIL import Image
base = Image.open("photo.jpg").convert("RGBA")
wm = Image.open("logo.png").convert("RGBA")
wm.thumbnail((base.width // 6, base.height // 6))
x = base.width - wm.width - 24
y = base.height - wm.height - 24
out = base.copy()
out.alpha_composite(wm, (x, y))
out.convert("RGB").save("watermarked.jpg", quality=90)
Output: watermarked.jpg with logo in the bottom-right corner. Convert back to RGB before JPEG save — JPEG has no alpha channel.
Recipe 4 — HEIF/HEIC iPhone-photo intake
iPhones since 2017 default to HEIC. Pillow needs pillow-heif to read them; register once at module load:
from PIL import Image
import pillow_heif
pillow_heif.register_heif_opener()
with Image.open("IMG_1234.HEIC") as im:
im.convert("RGB").save("IMG_1234.jpg", quality=92)
Output: iPhone photo lands as a JPEG. pillow-heif wraps libheif; both the C library and its codec dependencies (libde265 / x265) must be available on the platform — wheels exist for Linux/macOS/Windows.
Recipe 5 — color-space conversion with ICC profiles
from PIL import Image, ImageCms
src = Image.open("print.tif")
srgb = ImageCms.createProfile("sRGB")
cmyk = ImageCms.ImageCmsProfile("USWebCoatedSWOP.icc") # bundled with print drivers
transform = ImageCms.buildTransform(srgb, cmyk, "RGB", "CMYK")
out = ImageCms.applyTransform(src, transform)
out.save("print_cmyk.tif", compression="tiff_lzw")
Output: TIFF in CMYK color space — the only color model most print shops accept.
Performance tuning
Pillow's hot paths are C — but Python overhead and codec choice still dominate at scale.
Resize resampling
Image.Resampling.LANCZOS is highest quality; Image.Resampling.BILINEAR is ~2x faster with imperceptible loss for typical thumbnails. NEAREST is fastest but only acceptable for pixel art.
im.resize((512, 512), Image.LANCZOS) # best quality
im.resize((512, 512), Image.BILINEAR) # good default for batch
Output: the same dimensions; LANCZOS keeps high-frequency detail (text edges), BILINEAR is smoother.
thumbnail() vs resize()
thumbnail() modifies in place, respects aspect ratio, and short-circuits when the image is already smaller — preferred for "make this fit in a 512px box".
pillow-simd on Intel/AMD x86_64
pip install pillow-simd is a drop-in fork with SSE4/AVX2-accelerated resize and blur — 4-6x faster for resize, ~2x for blur. Caveat: it shadows pillow (same import name), so pin one or the other in requirements.txt and never both. Not available for ARM (M-series Macs).
Lazy decode
Image.open() does not decode pixels — it reads the header. The full decode happens on the first pixel access (im.load(), iteration, numpy.asarray(im)). For metadata-only inspection, never call .load():
with Image.open("huge.tif") as im:
print(im.size, im.format, im.mode) # cheap; no decode
Output: instant — even for a 2 GB multi-page TIFF.
In-memory pipelines with BytesIO
For HTTP upload pipelines where the input is bytes and the output is bytes, avoid temp files:
from io import BytesIO
from PIL import Image
def process(data: bytes) -> bytes:
with Image.open(BytesIO(data)) as im:
im.thumbnail((1024, 1024))
buf = BytesIO()
im.save(buf, "JPEG", quality=85)
return buf.getvalue()
Output: zero disk I/O — typical Lambda/Worker pattern.
Security considerations
Pillow's threat surface is large because image parsers are written in C and image files come from untrusted sources. Treat Pillow as a security-sensitive dependency.
CVE history
The Pillow CVE list runs to dozens of entries — most are buffer overflows or integer-overflow-leading-to-heap-corruption in format decoders (TIFF, JPEG2000, BMP, GIF, EPS, WebP via libwebp). High-impact recent classes:
- libwebp CVE-2023-4863 — heap overflow in WebP decode; transitively affected every Pillow build that linked the vulnerable libwebp. Pillow shipped a fixed wheel within days, but unpinned older Pillow versions remained vulnerable.
- libtiff CVEs — recurring; Pillow tracks libtiff upstream but lag is possible.
- Multi-frame GIF / TIFF DoS — malicious files claiming millions of frames exhaust memory.
Hardening checklist
- Always run the latest Pillow. Pin only to recent versions (e.g.
pillow>=11.0,<12.0), not to an old version. Subscribe to Pillow security advisories. Image.MAX_IMAGE_PIXELS— set to a sane ceiling (e.g. 100_000_000 = ~10000×10000) to reject decompression-bomb inputs. Default is ~89 megapixels and raisesDecompressionBombWarning; setImage.MAX_IMAGE_PIXELS = 100_000_000and treat the warning as an error.- Use
verify()before decode if you need cheap header validation:Image.open(path).verify()raises on malformed headers without doing a full decode. - Run user-image processing in a sandboxed worker — a separate process with
RLIMIT_AS, no filesystem access outside/tmp, and a strict timeout. A crashed decoder in the worker doesn't take down the app. - Strip EXIF on upload. EXIF can contain ICC profiles, GPS coordinates, and (historically) script payloads.
ImageOps.exif_transpose(im)then re-save without metadata. - Avoid
Image.open(BytesIO(user_bytes)).convert("RGB").save(target_path)in a privileged process. The decode happens betweenopenandsave; that's where the buffer overflow lives.
Format-specific risk ordering (anecdotal — verify per advisory)
| Format | Risk | Why |
|---|---|---|
| JPEG | Low | libjpeg-turbo is mature; few recent CVEs |
| PNG | Low | zlib is mature |
| WebP | Medium | libwebp had a major 2023 CVE; faster cadence |
| TIFF | High | huge format spec; libtiff CVEs are recurring |
| JPEG2000 | High | OpenJPEG has had multiple CVEs |
| BMP, GIF, EPS | Medium | older format parsers, less actively fuzzed |
| HEIF (via plugin) | Medium | libheif + libde265 add C surface area |
Testing & CI integration
Image-comparison tests need a tolerance — bit-exact comparison fails across libjpeg/libwebp versions and across CPU rounding.
from PIL import Image, ImageChops
def images_match(a: Image.Image, b: Image.Image, threshold: int = 8) -> bool:
if a.size != b.size or a.mode != b.mode:
return False
diff = ImageChops.difference(a, b)
bbox = diff.getbbox()
if bbox is None:
return True
# max channel delta in the diff bbox
max_delta = max(diff.crop(bbox).getextrema(), key=lambda x: x[1])[1]
return max_delta <= threshold
Output: True if every pixel is within threshold of the reference. Tune threshold per format — JPEG round-trips can shift by 4-8/255 even when "identical".
For perceptual comparison (more robust across encoder versions):
pip install scikit-image
Output: (none — exits 0 on success)
from skimage.metrics import structural_similarity as ssim
import numpy as np
score, _ = ssim(np.asarray(a.convert("L")), np.asarray(b.convert("L")), full=True)
assert score > 0.98
Output: SSIM in [0, 1]; > 0.98 is "visually identical" for most photos.
CI pitfalls:
- libjpeg version differences cause 1-2/255 pixel deltas. Use tolerance.
optimize=Trueproduces deterministic output; without it, JPEG encoder may pad differently across runs.- Pillow wheels on aarch64 vs x86_64 occasionally differ at the bit level for WebP encode.
Ecosystem integrations
- NumPy —
numpy.asarray(im)produces a(H, W, C)uint8array;Image.fromarray(arr)is the inverse. Zero-copy in both directions. - OpenCV —
cv2uses BGR ordering; conversion:cv2.cvtColor(np.asarray(im), cv2.COLOR_RGB2BGR). - matplotlib —
plt.imshow(im)accepts a PILImagedirectly;fig.savefig("out.jpg")falls through to Pillow for JPEG/WebP encode. - pandas — no built-in bridge, but a Series of PIL Images renders inline in Jupyter via
_repr_png_(). pillow-heif— HEIC/HEIF support;register_heif_opener()integrates withImage.open.pillow-avif-plugin— AVIF read/write via libavif.pillow-jxl-plugin— JPEG XL support (community).piexif— full-featured EXIF read/write; more complete than Pillow's built-in_getexif.reportlab— embed PIL images directly into PDFs.weasyprint,xhtml2pdf— both use Pillow for image embedding.fastai,torchvision.transforms— both accept PIL images natively and expect them as the canonical input format.
Compatibility matrix
| Pillow | Python floor | Notable |
|---|---|---|
| 11.x | 3.9 | Recent stable; libwebp CVE fixes; modern HEIF plugin support |
| 10.x | 3.8 | dropped Python 3.7; many resampling-constant renames |
| 9.x | 3.7 | mid-line; first to bundle modern libavif/libheif paths |
| 8.x | 3.6 | older; security back-patches no longer issued |
Format support depends on the wheel build:
| Format | Wheels (linux x86_64) | Wheels (macOS arm64) | Wheels (Windows) | Notes |
|---|---|---|---|---|
| JPEG | yes | yes | yes | libjpeg-turbo bundled |
| PNG | yes | yes | yes | zlib bundled |
| TIFF | yes | yes | yes | libtiff bundled |
| WebP | yes | yes | yes | libwebp bundled |
| GIF | yes | yes | yes | built-in |
| BMP, ICO | yes | yes | yes | built-in |
| JPEG2000 | yes | yes | yes | libopenjp2; older ARM builds occasionally lacked it |
| HEIF/HEIC | no — plugin | no — plugin | no — plugin | needs pillow-heif |
| AVIF | partial | partial | partial | full via pillow-avif-plugin |
| JPEG XL | no — plugin | no — plugin | no — plugin | needs community pillow-jxl-plugin |
| RAW (.CR2 etc.) | no | no | no | use rawpy |
When NOT to use this
- GPU-accelerated transforms at scale —
torchvision.transforms.v2on a GPU, orcv2.cuda, will run circles around Pillow for batch-processing thousands of images per second. - Computer-vision algorithms — feature detection, contour finding, optical flow are OpenCV's domain. Pillow is I/O + simple transforms only.
- Video — Pillow handles animated GIFs and multi-page TIFFs; for MP4/MOV/WebM, use
imageio-ffmpeg,pyav, oropencv-python. - Scientific image processing — segmentation, morphology, registration:
scikit-imageandSimpleITKare purpose-built. - Streaming gigantic TIFFs (slide microscopy) — tile-based reads need
tifffile,pyvips, oropenslide. Pillow loads whole frames into memory. - Raw camera files (.CR2, .NEF, .ARW) — install
rawpy(LibRaw binding); Pillow's raw support is limited.
See also
- Python: Pillow — API tutorial, format conversion, EXIF, filters
- Packages: pip-matplotlib — falls through to Pillow for JPEG/WebP output
- Concept: filesystem — image I/O is fundamentally a filesystem concern