[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky]
[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

Title: Make examples of EXIF orientations in Python

[An image in eight different orientations]

The example Get an image's EXIF data including orientation in Python explains how you can get an image's EXIF orientation and that begs the question, "How can you get samples of the different orientations?" You can take pictures while rotating your phone to get examples of the image transformations that only require rotations (and I've done that; it works), but I don't know how to take pictures that are stored with reflection.

Fortunately, it's not hard to create that kind of image. The trick is, when you save an image file, to use the save method's exif parameter. It's not exactly obvious how to create the EXIF information, but you can copy it from another image (like the one you loaded before transforming it) and then modify its entries as needed.

Names to IDs

The EXIF data is stored in a dictionary that maps EXIF tags to values. To save space, the tags are numeric values that don't mean much to a human. You can use PIL's ExifTags.TAGS dictionary to map IDs to their names. This example uses the following method to do the reverse: mapping EXIF field names to their IDs.

def get_exif_name_dict(): '''Return a dictionary mapping EXIF names to IDs.''' exif_name_dict = {name: id for id, name in ExifTags.TAGS.items()} return exif_name_dict

This code uses a dictionary comprehension to convert the ID:Name pairs in the ExifTags.TAGS dictionary to a new dictionary that maps names to IDs. For example, ExifTags.TAGS[256] is the string ImageWidth, so exif_name_dict['ImageWidth'] is 256.

After creating the inverse dictionary, the function returns it.

Making Samples

Here's the main program's code.

# Load the original, normally oriented image. with Image.open('loki.jpg') as image: # Load the image and save versions with different orientations. # Get a dictionary mapping EXIF tag names to IDs. exif_name_dict = get_exif_name_dict() orientation_code = exif_name_dict['Orientation'] image_width_code = exif_name_dict['ImageWidth'] image_length_code = exif_name_dict['ImageLength'] # Make it smaller. scale = 200 / max(image.width, image.height) small_wid = int(scale * image.width) small_hgt = int(scale * image.height) with image.resize((small_wid, small_hgt)) as small_image: wid = 200 with Image.new('RGB', (wid, wid), 'white') as square_image: x = int((wid - small_image.width) / 2) y = int((wid - small_image.height) / 2) square_image.paste(small_image, (x, y)) # Get its EXIF data. exif_data = image.getexif() exif_data[image_width_code] = 200 exif_data[image_length_code] = 200 # Transforms: orientation, horizontal flip, rotation rotations = [ # (0, False, 0), # Unknown (1, False, 0), # TopLeft (2, True, 0), # TopRight (3, False, -180), # BottomRight (4, True, -180), # BottomLeft (5, True, -270), # LeftTop (6, False, -270), # RightTop (7, True, -90), # RightBottom (8, False, -90), # LeftBottom ] for orientation, flip, angle in rotations: # Save a copy with this orientation. print(exif_orientations[orientation]) # Transform the image. new_image = square_image.copy() if flip: new_image = new_image.transpose(Image.FLIP_LEFT_RIGHT) new_image = new_image.rotate(angle, expand=True) # Set the EXIF orientation. exif_data[orientation_code] = orientation # Save the new image. new_image.save(f'loki{orientation}.jpg', exif=exif_data)

This code first loads the image file loki.jpg. It then calls exif_name_dict to get the inverse EXIF ID dictionary and gets the IDs for Orientation, ImageWidth, and ImageLength.

Next, the program resizes the image so it fits within a 200 × 200 pixel image. This is just to save disk space so the sample images aren't huge. (I also use MS Paint to reduce the size of the original image loki.jpg. Interestingly, MS Paint did not update the image's EXIF data so it still showed the original ImageWidth and ImageLength. Another example of how inconsistent EXIF data can be.)

The code then gets the original image's EXIF data. You need to get this from the original image because the smaller image was created by PIL so it has no EXIF data. The code sets the EXIF data's ImageWidth and ImageLength properties to 200 because that's the size of the smaller image that we will save into various files.

The code then defines the flips and rotations it needs to perform to transform the original image into the various orientations. See the picture at the top of the post to figure out which flips and rotations are necessary.

Now the program loops through those flips/rotation values. It creates a new copy of the small image and flips and rotates it as necessary. It sets the image's EXIF Orientation value and saves the image file, passing the save method the EXIF data. You can use the example Get an image's EXIF data including orientation in Python to verify the image's orientations.

Conclusion

Normally you don't need to write an image's EXIF orientation data. You just load the image from a file and then you can read the orientation to figure out how to process the image.

In fact, handling orientations is even easier than that. In my next post, I'll show how you can use PIL to automatically put an image in the normal TopLeft orientation.

Download the example to experiment with it and to see additional details.

© 2025 Rocky Mountain Computer Consulting, Inc. All rights reserved.