Skip to main content

Merge pull request #22 from alexwlchan/fix-srgbify-rotation-bug

ID
45b10d5
date
2024-03-28 07:14:32+00:00
author
Alex Chan <alex@alexwlchan.net>
parents
3607dbb, f9b6c1e
message
Merge pull request #22 from alexwlchan/fix-srgbify-rotation-bug

Make sure srgbify doesn't rotate images when it strips out EXIF
changed files
3 files, 41 additions, 9 deletions

Changed files

images/examples/taylorswift.jpg (0) → images/examples/taylorswift.jpg (3939779)

diff --git a/images/examples/taylorswift.jpg b/images/examples/taylorswift.jpg
new file mode 100644
index 0000000..b3885f6
Binary files /dev/null and b/images/examples/taylorswift.jpg differ

images/srgbify.py (1846) → images/srgbify.py (2475)

diff --git a/images/srgbify.py b/images/srgbify.py
index 503d016..08f164c 100755
--- a/images/srgbify.py
+++ b/images/srgbify.py
@@ -6,13 +6,20 @@ This is particularly useful for screenshots on macOS, which are taken
 with the display's colour profile (e.g. Display LCD or Display P3), but
 which I want to convert to sRGB for converting on the web.
 
+Note: this is a potentially destructive script.  Don't run this on images
+you care about if you don't have a backup!
+
+*   It overwrites the original image file.
+*   It strips out EXIF metadata.
+
 Based on https://github.com/python-pillow/Pillow/issues/1662
 """
 
 import io
 import sys
+import typing
 
-from PIL import Image, ImageCms
+from PIL import Image, ImageCms, ImageOps
 from PIL.ImageCms import PyCMSError
 from pillow_heif import register_heif_opener
 
@@ -20,7 +27,7 @@ from pillow_heif import register_heif_opener
 register_heif_opener()
 
 
-def convert_image_to_srgb(im: Image) -> Image:
+def convert_image_to_srgb(im: Image) -> typing.Union[Image, None]:
     """
     Convert an image to sRGB and return a new Image instance.
     """
@@ -28,10 +35,19 @@ def convert_image_to_srgb(im: Image) -> Image:
 
     # If this image doesn't have a colour profile, we're done.
     if icc_profile is None:
-        return im
+        return None
+
+    # If the image has an EXIF orientation tag, it will be stripped out
+    # upon saving (like all EXIF metadata).
+    #
+    # To avoid any weird rotation issues, bake the rotation into the image.
+    # See https://github.com/python-pillow/Pillow/issues/4703#issuecomment-645219973
+    # or the associated test.
+    #
+    # See https://github.com/alexwlchan/scripts/issues/21
+    im = ImageOps.exif_transpose(im)
 
     # Otherwise, convert the image to an sRGB colour profile and return that.
-
     try:
         return ImageCms.profileToProfile(
             im,
@@ -39,7 +55,6 @@ def convert_image_to_srgb(im: Image) -> Image:
             outputProfile=ImageCms.createProfile("sRGB"),
         )
     except PyCMSError as err:
-        print(err.args[0].args)
         if (
             im.mode == "L"
             and b"GRAYXYZ" in icc_profile
@@ -65,7 +80,7 @@ if __name__ == "__main__":
         im = Image.open(path)
         out_im = convert_image_to_srgb(im)
 
-        if out_im != im:
+        if out_im is not None:
             out_im.save(path)
 
         print(path)

images/test_srgbify.py (1762) → images/test_srgbify.py (2378)

diff --git a/images/test_srgbify.py b/images/test_srgbify.py
index aed3d10..a4172d2 100644
--- a/images/test_srgbify.py
+++ b/images/test_srgbify.py
@@ -1,4 +1,5 @@
 import filecmp
+import pathlib
 
 from PIL import Image
 
@@ -13,9 +14,7 @@ def test_it_ignores_an_image_which_is_already_srgb():
     """
     im = Image.open("images/examples/Shoes-sRGB.jpg")
 
-    new_im = convert_image_to_srgb(im)
-
-    assert new_im is im
+    assert convert_image_to_srgb(im) is None
 
 
 def test_it_converts_a_display_p3_image_to_srgb(tmp_path):
@@ -65,3 +64,21 @@ def test_it_converts_images_with_a_grey_profile():
     new_im = convert_image_to_srgb(im)
 
     assert new_im.mode == "RGB"
+
+
+def test_it_preserves_rotation_from_exif_orientation(tmp_path: pathlib.Path):
+    """
+    This is based on a photo exported from my Apple Photos Library
+    which was rotated by 90 degrees upon transformation.
+
+    This was caused by an image with an EXIF orientation tag that was
+    being stripped on save.  I found a solution in the Pillow issue tracker.
+
+    See https://github.com/alexwlchan/scripts/issues/21
+    See https://github.com/python-pillow/Pillow/issues/4703
+    """
+    im = Image.open("images/examples/taylorswift.jpg")
+
+    new_im = convert_image_to_srgb(im)
+
+    assert new_im.size == (3024, 4032)