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 -- appliedRemember
rotateis counter-clockwise and keeps the canvas unlessexpand=Truetransposedoes exact, lossless 90-degree turns and flips- Phone photos store orientation in EXIF; Pillow doesn't apply it automatically
- Call
ImageOps.exif_transposeright after opening user images
Watch out for
- Generating thumbnails from phone photos without exif_transpose, producing sideways images.