Rotate, transpose and EXIF orientation

Pillow · Practical · 10 min

Phone photos often appear sideways after processing because the orientation lives in metadata, not in the pixels.

im.rotate(angle) rotates counter-clockwise by any angle in degrees around the centre, keeping the original canvas size, so corners get clipped. Pass expand=True to grow the canvas to fit, fillcolor= for the uncovered area and resample=Image.Resampling.BICUBIC for smoother edges.

For exact 90-degree steps and mirrors, use im.transpose(...) with Image.Transpose.ROTATE_90, ROTATE_180, ROTATE_270, FLIP_LEFT_RIGHT or FLIP_TOP_BOTTOM. These are lossless pixel moves with no resampling. ImageOps.mirror and ImageOps.flip are shortcuts.

Cameras usually store pixels in the sensor's native orientation and record how to display them in the EXIF Orientation tag (values 1 to 8). Viewers honour it; Pillow does not automatically. If you resize or save without handling it, the image ends up sideways and, worse, the tag may be stripped.

ImageOps.exif_transpose(im) reads the tag, rotates and flips the pixels accordingly, and returns a new image with the orientation reset to normal. Call it right after opening user photos, before resizing or cropping:

im = ImageOps.exif_transpose(Image.open(path))

Pass in_place=True to modify the image instead of returning a copy. It is harmless on images without the tag.

Example

from PIL import Image, ImageOps

im = Image.new("RGB", (300, 100), "white")     # wide image
im.paste("red", (0, 0, 50, 100))               # red stripe on the left

print(im.rotate(90).size)                      # (300, 100) canvas kept, clipped
print(im.rotate(90, expand=True).size)         # (100, 300)
print(im.transpose(Image.Transpose.ROTATE_90).size)    # (100, 300) lossless

flipped = ImageOps.mirror(im)
print(flipped.getpixel((299, 50)))             # (255, 0, 0) stripe moved right

exif = im.getexif()
exif[0x0112] = 6                               # Orientation: rotate 90 CW to view
im.save("phone.jpg", exif=exif)

raw = Image.open("phone.jpg")
print(raw.size, raw.getexif().get(0x0112))     # (300, 100) 6
fixed = ImageOps.exif_transpose(raw)
print(fixed.size, fixed.getexif().get(0x0112)) # (100, 300) None -- applied

Remember

  • rotate is counter-clockwise and keeps the canvas unless expand=True
  • transpose does exact, lossless 90-degree turns and flips
  • Phone photos store orientation in EXIF; Pillow doesn't apply it automatically
  • Call ImageOps.exif_transpose right after opening user images

Watch out for

  • Generating thumbnails from phone photos without exif_transpose, producing sideways images.

Check yourself

  1. A portrait phone photo opens in Pillow as 4032x3024 (landscape). What should you do before resizing?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: pillow.readthedocs.io/en/stable/reference/ImageOps.html#PIL.ImageOps.exif_transpose