File size and visual quality depend almost entirely on the format and options you choose at save time.
im.save(fp, format=None, **options) writes the image. Without format, Pillow uses the file extension; when saving to a file object such as io.BytesIO, you must pass format=.
Choosing a format:
- JPEG: lossy, small, for photos. No alpha.
quality=from 1 to 95 (default 75; above 95 just grows files),optimize=True,progressive=True.subsampling=0keeps full colour resolution for sharp text or graphics. - PNG: lossless, supports alpha and palettes; best for screenshots, diagrams and masks.
optimize=Trueandcompress_level=0..9trade CPU for size, never quality. - WebP: lossy or lossless (
lossless=True), with alpha, usually smaller than JPEG and PNG. - GIF: palette-based, mainly for simple animation.
- TIFF: archival and scientific, many compressions; AVIF is supported in recent Pillow versions.
Options are format specific; unknown keywords are mostly ignored, so check the format docs. Saving a re-opened JPEG again loses quality every time (generation loss), so keep a lossless master if you edit repeatedly.
Image.registered_extensions() maps extensions to formats, and features.check("webp") tells you whether optional codecs are available in your build.
For web uploads and APIs, saving to io.BytesIO() gives you bytes without touching disk.
Example
import io
from PIL import Image
im = Image.effect_noise((400, 300), 60).convert("RGB") # noisy test photo
sizes = {}
for q in (30, 75, 95):
buf = io.BytesIO()
im.save(buf, format="JPEG", quality=q, optimize=True)
sizes[q] = buf.tell()
print(sizes[30] < sizes[75] < sizes[95]) # True: quality costs bytes
png = io.BytesIO()
im.save(png, format="PNG", optimize=True)
webp = io.BytesIO()
im.save(webp, format="WEBP", quality=80)
print(png.tell() > webp.tell()) # True for photo-like content
im.save("photo.jpg", quality=85, progressive=True) # format from extension
print(Image.open("photo.jpg").format) # JPEG
print(Image.registered_extensions()[".webp"]) # WEBPRemember
- The extension picks the format; pass
format=for file objects - JPEG for photos (quality 1-95), PNG for lossless/alpha, WebP for both
- Options are format-specific: quality, optimize, progressive, lossless
- Re-saving JPEGs compounds quality loss
Watch out for
- Saving to an
io.BytesIOwithoutformat=, which raises because there is no extension to infer from.