All formats

Pillow

LanguagePython 3
LicenseMIT-CMU (permissive, HPND-style)
Repositorygithub.com/python-pillow/Pillow
DocumentationJPEG in Pillow's format docs
Depends onlibjpeg-turbo (bundled in the PyPI wheels)
Read / writeyes / yes; baseline and progressive, CMYK, 4:4:4 / 4:2:2 / 4:2:0, custom quantization tables; Exif, ICC, XMP, comments

Pillow (the maintained fork of PIL) is the most used imaging library for Python. For JPEG it exposes almost every libjpeg-turbo option through keyword arguments of Image.save, reads and writes Exif through Image.getexif(), and supports draft mode: decoding at ½, ¼ or 1/8 scale, which makes thumbnails of large photos very fast.

Pillow doesn’t apply the Exif orientation when it opens a file; call ImageOps.exif_transpose. When saving, metadata isn’t copied unless you pass it (exif=, icc_profile=).

Installation#

Shell
pip install pillow

Basic usage: open, fix rotation, resize and save#

Python
from PIL import Image, ImageOps

with Image.open("photo.jpg") as im:
    print(im.format, im.size, im.mode)  # JPEG (4032, 3024) RGB
    im = ImageOps.exif_transpose(im)    # apply the Exif orientation
    im.thumbnail((1200, 1200))          # resize in place, keep aspect ratio
    im.save("out.jpg", quality=85, optimize=True, progressive=True)

How to save a JPEG with a specific quality and chroma subsampling#

Python
im.save(
    "out.jpg",
    quality=90,          # 1-95 recommended; 75 is the default
    subsampling=0,       # 0 = 4:4:4, 1 = 4:2:2, 2 = 4:2:0 (or "4:4:4", "4:2:2", "4:2:0")
    optimize=True,       # optimal Huffman tables
    progressive=True,
)

When re-saving a JPEG you opened, quality="keep" reuses its quantization tables and subsampling, which keeps the quality close to the original.

How to read Exif data (camera, date, GPS)#

Python
from PIL import Image, ExifTags

with Image.open("photo.jpg") as im:
    exif = im.getexif()

print("Camera:", exif.get(ExifTags.Base.Make), exif.get(ExifTags.Base.Model))
print("Orientation:", exif.get(ExifTags.Base.Orientation))

sub = exif.get_ifd(ExifTags.IFD.Exif)          # DateTimeOriginal, exposure, ISO live here
print("Taken:", sub.get(ExifTags.Base.DateTimeOriginal))
print("Exposure:", sub.get(ExifTags.Base.ExposureTime))

gps = exif.get_ifd(ExifTags.IFD.GPSInfo)
if gps:
    print("Lat:", gps.get(ExifTags.GPS.GPSLatitudeRef), gps.get(ExifTags.GPS.GPSLatitude))
    print("Lon:", gps.get(ExifTags.GPS.GPSLongitudeRef), gps.get(ExifTags.GPS.GPSLongitude))

for tag_id, value in exif.items():               # all IFD0 tags with names
    print(ExifTags.TAGS.get(tag_id, tag_id), value)

GPS coordinates are (degrees, minutes, seconds) tuples of rationals.

How to keep Exif and the ICC profile when saving#

Python
from PIL import Image, ImageOps

with Image.open("photo.jpg") as im:
    icc = im.info.get("icc_profile")
    im = ImageOps.exif_transpose(im)   # also resets the orientation tag in im.getexif()
    im.thumbnail((1600, 1600))
    im.save("out.jpg", quality=85, exif=im.getexif(), icc_profile=icc)

To strip metadata, just don’t pass exif and icc_profile.

How to make thumbnails of large JPEGs quickly#

thumbnail calls draft, which tells libjpeg to decode at a reduced scale. A 24-megapixel photo then decodes at 1/8 size:

Python
with Image.open("big.jpg") as im:
    im.thumbnail((300, 300), Image.Resampling.LANCZOS)
    im.save("thumb.jpg", quality=80)

Call im.draft("RGB", (w, h)) yourself when you need a reduced image without thumbnail.

How to convert PNG with transparency to JPEG#

JPEG has no alpha: paste on a background color, or convert("RGB") makes transparent areas black:

Python
from PIL import Image

with Image.open("logo.png") as im:
    im = im.convert("RGBA")
    bg = Image.new("RGB", im.size, "white")
    bg.paste(im, mask=im.getchannel("A"))
    bg.save("logo.jpg", quality=90)

Tips#