| Format | Read | Write | Worth knowing |
|---|---|---|---|
| JPEG | Yes | Yes | No alpha. Loses a little on every save. quality and optimize are the options that matter |
| PNG | Yes | Yes | Lossless, supports alpha. compress_level trades time for size |
| WEBP | Yes | Yes | Alpha, animation, lossy and lossless. Usually smaller than both of the above |
| GIF | Yes | Yes | 256 colours, animation. Use save_all and append_images for frames |
| TIFF | Yes | Yes | Many internal variants. Some compressions need libtiff built in |
| BMP | Yes | Yes | Simple and enormous. Fine as an intermediate |
| ICO | Yes | Yes | Holds several sizes in one file. Pass sizes= when saving |
| No | Yes | Write only. Pillow cannot open a PDF, only produce one from images | |
| PPM, PGM, PBM | Yes | Yes | Trivially simple formats, useful for debugging |
| Format | Read | Write | Condition |
|---|---|---|---|
| HEIC, HEIF | With a plugin | With a plugin | pip install pillow-heif. This is what iPhones produce |
| AVIF | Recent versions | Recent versions | Needs a build with libavif. Check before relying on it |
| JPEG 2000 | Yes | Yes | Needs openjpeg at build time |
| SVG | No | No | A vector text format. Rasterise it with cairosvg first |
| RAW, CR2, NEF | No | No | Camera raw. Use rawpy, then hand the array to Pillow |
| PSD | Flattened only | No | Reads the composite image, not the layers |
| EPS | With Ghostscript | Yes | Reading needs Ghostscript installed on the system |
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.
from PIL import features
print(features.check("webp"))
print(features.check_codec("jpg_2000"))
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.
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.