Skip to main content

Account for EXIF orientation in image width/height

ID
281f405
date
2025-12-07 20:11:32+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
099b303
message
Account for EXIF orientation in image width/height
changed files
13 files, 35 additions, 10 deletions

Changed files

CHANGELOG.md (1871) → CHANGELOG.md (2043)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index fee1295..0c0083b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # CHANGELOG
 
+## v17 - 2025-12-07
+
+Account for [EXIF orientation](https://alexwlchan.net/til/2024/photos-can-have-orientation-in-exif/) when getting the width/height of image entities.
+
 ## v16 - 2025-12-06
 
 Don't require defining `list_tags_in_metadata()` in projects that don't use tags.

dev_requirements.txt (2646) → dev_requirements.txt (2646)

diff --git a/dev_requirements.txt b/dev_requirements.txt
index 565f627..1e3e896 100644
--- a/dev_requirements.txt
+++ b/dev_requirements.txt
@@ -47,7 +47,7 @@ javascript-data-files==1.4.1
     # via alexwlchan-chives
 keyring==25.7.0
     # via twine
-librt==0.7.2
+librt==0.7.3
     # via mypy
 markdown-it-py==4.0.0
     # via rich
@@ -87,7 +87,7 @@ pygments==2.19.2
     #   rich
 pyproject-hooks==1.2.0
     # via build
-pytest==9.0.1
+pytest==9.0.2
     # via
     #   alexwlchan-chives
     #   pytest-cov

src/chives/__init__.py (391) → src/chives/__init__.py (391)

diff --git a/src/chives/__init__.py b/src/chives/__init__.py
index 093bc4c..40dd9bd 100644
--- a/src/chives/__init__.py
+++ b/src/chives/__init__.py
@@ -11,4 +11,4 @@ I share across multiple sites.
 
 """
 
-__version__ = "16"
+__version__ = "17"

src/chives/media.py (10418) → src/chives/media.py (10644)

diff --git a/src/chives/media.py b/src/chives/media.py
index 7dcff63..4b649b9 100644
--- a/src/chives/media.py
+++ b/src/chives/media.py
@@ -186,15 +186,19 @@ def create_image_entity(
     """
     Create an ImageEntity for a saved image.
     """
-    from PIL import Image
+    from PIL import Image, ImageOps
 
     with Image.open(path) as im:
+        # Account for EXIF orientation in the dimensions.
+        # See https://alexwlchan.net/til/2024/photos-can-have-orientation-in-exif/
+        transposed_im = ImageOps.exif_transpose(im)
+
         entity: ImageEntity = {
             "type": "image",
             "path": str(path),
             "tint_colour": _get_tint_colour(path, background=background),
-            "width": im.width,
-            "height": im.height,
+            "width": transposed_im.width,
+            "height": transposed_im.height,
         }
 
         if _is_animated(im):

tests/fixtures/media/Landscape_0.jpg (0) → tests/fixtures/media/Landscape_0.jpg (349915)

diff --git a/tests/fixtures/media/Landscape_0.jpg b/tests/fixtures/media/Landscape_0.jpg
new file mode 100644
index 0000000..8518c82
Binary files /dev/null and b/tests/fixtures/media/Landscape_0.jpg differ

tests/fixtures/media/Landscape_1.jpg (0) → tests/fixtures/media/Landscape_1.jpg (347327)

diff --git a/tests/fixtures/media/Landscape_1.jpg b/tests/fixtures/media/Landscape_1.jpg
new file mode 100644
index 0000000..fda1882
Binary files /dev/null and b/tests/fixtures/media/Landscape_1.jpg differ

tests/fixtures/media/Landscape_2.jpg (0) → tests/fixtures/media/Landscape_2.jpg (349209)

diff --git a/tests/fixtures/media/Landscape_2.jpg b/tests/fixtures/media/Landscape_2.jpg
new file mode 100644
index 0000000..d2605f8
Binary files /dev/null and b/tests/fixtures/media/Landscape_2.jpg differ

tests/fixtures/media/Landscape_4.jpg (0) → tests/fixtures/media/Landscape_4.jpg (348052)

diff --git a/tests/fixtures/media/Landscape_4.jpg b/tests/fixtures/media/Landscape_4.jpg
new file mode 100644
index 0000000..d73dee8
Binary files /dev/null and b/tests/fixtures/media/Landscape_4.jpg differ

tests/fixtures/media/Landscape_5.jpg (0) → tests/fixtures/media/Landscape_5.jpg (351275)

diff --git a/tests/fixtures/media/Landscape_5.jpg b/tests/fixtures/media/Landscape_5.jpg
new file mode 100644
index 0000000..975d858
Binary files /dev/null and b/tests/fixtures/media/Landscape_5.jpg differ

tests/fixtures/media/Landscape_6.jpg (0) → tests/fixtures/media/Landscape_6.jpg (352727)

diff --git a/tests/fixtures/media/Landscape_6.jpg b/tests/fixtures/media/Landscape_6.jpg
new file mode 100644
index 0000000..b579b7f
Binary files /dev/null and b/tests/fixtures/media/Landscape_6.jpg differ

tests/fixtures/media/Landscape_7.jpg (0) → tests/fixtures/media/Landscape_7.jpg (351856)

diff --git a/tests/fixtures/media/Landscape_7.jpg b/tests/fixtures/media/Landscape_7.jpg
new file mode 100644
index 0000000..b1e919c
Binary files /dev/null and b/tests/fixtures/media/Landscape_7.jpg differ

tests/fixtures/media/Landscape_8.jpg (0) → tests/fixtures/media/Landscape_8.jpg (352067)

diff --git a/tests/fixtures/media/Landscape_8.jpg b/tests/fixtures/media/Landscape_8.jpg
new file mode 100644
index 0000000..c381db1
Binary files /dev/null and b/tests/fixtures/media/Landscape_8.jpg differ

tests/test_media.py (14290) → tests/test_media.py (14662)

diff --git a/tests/test_media.py b/tests/test_media.py
index d59c75d..8b44ea4 100644
--- a/tests/test_media.py
+++ b/tests/test_media.py
@@ -90,14 +90,31 @@ class TestCreateImageEntity:
         entity = create_image_entity(fixtures_dir / filename)
         assert "has_transparency" not in entity
 
-    def test_accounts_for_exif_orientation(self, fixtures_dir: Path) -> None:
+    # These test files were downloaded from Dave Perrett repo:
+    # https://github.com/recurser/exif-orientation-examples
+
+    @pytest.mark.parametrize(
+        "filename",
+        [
+            "Landscape_0.jpg",
+            "Landscape_1.jpg",
+            "Landscape_2.jpg",
+            "Landscape_3.jpg",
+            "Landscape_4.jpg",
+            "Landscape_5.jpg",
+            "Landscape_6.jpg",
+            "Landscape_7.jpg",
+            "Landscape_8.jpg",
+        ],
+    )
+    def test_accounts_for_exif_orientation(
+        self, fixtures_dir: Path, filename: str
+    ) -> None:
         """
         The dimensions are the display dimensions, which accounts for
         the EXIF orientation.
         """
-        # This test file was downloaded from Dave Perrett repo:
-        # https://github.com/recurser/exif-orientation-examples
-        entity = create_image_entity(fixtures_dir / "Landscape_3.jpg")
+        entity = create_image_entity(fixtures_dir / filename)
         assert (entity["width"], entity["height"]) == (1800, 1200)
 
     def test_animated_image(self, fixtures_dir: Path) -> None: