Decoding a 50-megapixel photo to make a 200-pixel thumbnail wastes seconds and hundreds of megabytes if done naively.
Memory is roughly width x height x bands bytes once decoded: a 8000x6000 RGB photo needs about 144 MB, before any copies.
Pillow helps in several ways:
- Lazy open:
Image.openreads only the header, so checkingsize,formatandmodeof thousands of files is cheap. Reject oversize files before decoding. - `im.draft(mode, size)` (JPEG and some others): asks the decoder to decode at a reduced scale (1/2, 1/4 or 1/8) directly using the JPEG's DCT structure. It must be called before the image is loaded.
thumbnail()callsdraftautomatically, which is why it is so fast on JPEGs. - `im.reduce(factor)`: fast integer downscaling by box averaging;
resize(..., reducing_gap=2.0)uses it internally before the precise resampling step, trading a little quality for large speedups. - Cropping doesn't save decoding:
im.crop()on a freshly opened file callsload()and decodes the whole image first, so crop after downscaling when you can.
Close files you open in loops. Opened-but-unloaded images keep file handles; after load(), single-frame images close them automatically, but multi-frame ones stay open. with Image.open(p) as im: is the reliable pattern.
Image.MAX_IMAGE_PIXELS guards against decompression bombs and will stop absurdly large images; don't just set it to None for untrusted input.
Example
import time
from PIL import Image
Image.effect_noise((4000, 3000), 40).convert("RGB").save("big.jpg", quality=90)
with Image.open("big.jpg") as im:
print(im.size, im.mode) # (4000, 3000) RGB -- header only
t = time.perf_counter()
with Image.open("big.jpg") as im:
full = im.resize((400, 300)) # decodes all 12 megapixels
t_full = time.perf_counter() - t
t = time.perf_counter()
with Image.open("big.jpg") as im:
im.draft("RGB", (400, 300)) # decode at 1/8 scale
print(im.size) # (500, 375)
fast = im.resize((400, 300))
t_draft = time.perf_counter() - t
print(t_draft < t_full) # True, typically several times faster
print(Image.open("big.jpg").reduce(4).size) # (1000, 750)Remember
- Opening is lazy: inspect size and format before decoding
- JPEG draft mode decodes directly at 1/2, 1/4 or 1/8 scale; thumbnail uses it
- reduce() and reducing_gap speed up big downscales
- Use
with Image.open(...)in loops to release file handles
Watch out for
- Calling draft() after the image has already been loaded, when it has no effect.