Home / Library / PIL / Handbook / Formats

Format support

What Pillow opens, what it saves, and what to watch for in each.

The ones you will actually use

FormatReadWriteWorth knowing
JPEGYesYesNo alpha. Loses a little on every save. quality and optimize are the options that matter
PNGYesYesLossless, supports alpha. compress_level trades time for size
WEBPYesYesAlpha, animation, lossy and lossless. Usually smaller than both of the above
GIFYesYes256 colours, animation. Use save_all and append_images for frames
TIFFYesYesMany internal variants. Some compressions need libtiff built in
BMPYesYesSimple and enormous. Fine as an intermediate
ICOYesYesHolds several sizes in one file. Pass sizes= when saving
PDFNoYesWrite only. Pillow cannot open a PDF, only produce one from images
PPM, PGM, PBMYesYesTrivially simple formats, useful for debugging

Common, with a condition attached

FormatReadWriteCondition
HEIC, HEIFWith a pluginWith a pluginpip install pillow-heif. This is what iPhones produce
AVIFRecent versionsRecent versionsNeeds a build with libavif. Check before relying on it
JPEG 2000YesYesNeeds openjpeg at build time
SVGNoNoA vector text format. Rasterise it with cairosvg first
RAW, CR2, NEFNoNoCamera raw. Use rawpy, then hand the array to Pillow
PSDFlattened onlyNoReads the composite image, not the layers
EPSWith GhostscriptYesReading needs Ghostscript installed on the system

Asking Pillow itself

Builds differ, so the reliable answer is from the copy you have installed:

from PIL import Image, features

print(features.pilinfo())

Image.init()
print(sorted(Image.OPEN.keys()))
print(sorted(Image.SAVE.keys()))

pilinfo prints the version, the compiled in libraries and which codecs are present. It is the first thing to run when a format works on your machine and not on the server.

Checking one feature

from PIL import features

print(features.check("webp"))
print(features.check_codec("jpg_2000"))

The format is not the file extension

im = Image.open("photo.jpg")
print(im.format)

im.format is what the file really is, read from its header. Plenty of files are named .jpg and are actually PNG, which matters when something downstream believes the name. See cannot identify image file.

Saving animations

frames[0].save("out.gif", save_all=True, append_images=frames[1:],
               duration=80, loop=0)

The same pattern works for animated WEBP and for multi page TIFF and PDF.