width, height = Image.open("photo.jpg").size. Also available without decoding pixels:
img.format (JPEG, PNG...), img.mode (RGB, RGBA...) and img.info (DPI, ICC profile and
other metadata). For the file size on disk, use os.path.getsize().
Size, format and mode
from PIL import Image
with Image.open("photo.jpg") as img:
width, height = img.size
print("size: ", width, "x", height)
print("format:", img.format) # file format: JPEG, PNG, WEBP...
print("mode: ", img.mode) # pixel layout: RGB, RGBA, L...
print("bands: ", img.getbands()) # channel names
size: 1600 x 1067
format: JPEG
mode: RGB
bands: ('R', 'G', 'B')
img.width and img.height are shortcuts for the two parts of img.size. The
with block closes the file when you are done, which matters when you process many files.
Everything else you can read without loading the pixels
import os
from PIL import Image
path = "photo.jpg"
with Image.open(path) as img:
print("file size: ", os.path.getsize(path), "bytes")
print("in memory: ", img.width * img.height * len(img.getbands()), "bytes when decoded")
print("dpi: ", img.info.get("dpi"))
print("ICC profile: ", "yes" if img.info.get("icc_profile") else "no")
print("animated: ", getattr(img, "is_animated", False), getattr(img, "n_frames", 1))
print("MIME type: ", Image.MIME.get(img.format))
print("info keys: ", sorted(img.info))
file size: 239040 bytes
in memory: 5121600 bytes when decoded
dpi: None
ICC profile: yes
animated: False 1
MIME type: image/jpeg
info keys: ['icc_profile', 'jfif', 'jfif_density', 'jfif_unit', 'jfif_version']
The decoded size matters for memory: this 233 KB JPEG needs 4.9 MB once decoded, about 21× its size on disk. A 6000×4000 photo takes about 72 MB once decoded, so opening many large images at once adds up quickly.
Phone photos: width and height can be swapped
img.size reports the pixels as stored. Phones often store portrait photos in landscape and add an EXIF tag saying
"rotate when displaying", so the width and height you get are swapped compared with what you see:
from PIL import Image, ImageOps
with Image.open("phone.jpg") as img:
print("stored size: ", img.size)
print("orientation: ", img.getexif().get(0x0112))
print("displayed size:", ImageOps.exif_transpose(img).size)
stored size: (1200, 800)
orientation: 6
displayed size: (800, 1200)
To get the displayed size without decoding the image, check the orientation tag yourself:
from PIL import Image
def displayed_size(path):
"""Width and height as viewers show the image (EXIF rotation applied), without decoding pixels."""
with Image.open(path) as img:
w, h = img.size
if img.getexif().get(0x0112) in (5, 6, 7, 8): # these orientations rotate by 90 degrees
w, h = h, w
return w, h
print(displayed_size("phone.jpg"), displayed_size("photo.jpg"))
(800, 1200) (1600, 1067)
More on orientation in the resizing guide and the EXIF guide.
Scan a whole folder quickly
Because only headers are read, scanning thousands of files takes seconds:
import time
from pathlib import Path
from PIL import Image
start = time.perf_counter()
rows = []
for path in sorted(Path("photos").iterdir()):
with Image.open(path) as img: # header only: no pixels are decoded
rows.append((path.name, img.format, img.mode, img.size, path.stat().st_size // 1024))
elapsed = (time.perf_counter() - start) * 1000
for name, fmt, mode, size, kb in rows:
print(f"{name:<12} {fmt:<5} {mode:<4} {str(size):<13} {kb:>5} KB")
print(f"scanned {len(rows)} files in {elapsed:.1f} ms")
banner.png PNG RGB (900, 600) 539 KB
img_1.jpg JPEG RGB (1600, 1067) 185 KB
img_2.jpg JPEG RGB (1067, 1600) 202 KB
img_3.jpg JPEG RGB (2400, 1350) 441 KB
scanned 4 files in 11.8 ms
How big would it be as JPEG, PNG or WebP?
To know the encoded size before writing anything to disk, save into memory:
import io
from PIL import Image
img = Image.open("photo.jpg")
for fmt, opts in [("JPEG", {"quality": 85}), ("PNG", {}), ("WEBP", {"quality": 80})]:
buf = io.BytesIO()
img.save(buf, fmt, **opts)
print(f"{fmt:<5} would be {buf.tell() / 1024:7.1f} KB")
JPEG would be 188.9 KB
PNG would be 1295.1 KB
WEBP would be 78.3 KB
Why img.format is sometimes None
format describes the file an image was read from. Any image you create or derive (by resizing, converting,
cropping) has no file behind it, so its format is None:
from PIL import Image
img = Image.open("photo.jpg")
print("opened: ", img.format)
small = img.resize((100, 67))
print("resized: ", small.format) # derived images have no file format
opened: JPEG
resized: None
Keep the original's format in a variable if you need to save the result in the same format.
Related
The outputs and images on this page are the real results of running every example with Pillow 12.3.0 on Python 3.13 (Linux) on 2026-10-15. Every example is also re-run automatically on macOS (Darwin 25.6.0, Pillow 12.3.0), Linux (Linux 6.17.0-1022-azure, Pillow 12.3.0), Windows (Windows 2025Server, Pillow 12.3.0); the line under each example shows the result per operating system. The DejaVu Sans font files sit next to each script so font examples behave the same everywhere. Sample photos: NASA/Sam Lott, public domain. Spotted something wrong? Tell us.