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 deletionsCHANGELOG.mddev_requirements.txtsrc/chives/__init__.pysrc/chives/media.pytests/fixtures/media/Landscape_0.jpgtests/fixtures/media/Landscape_1.jpgtests/fixtures/media/Landscape_2.jpgtests/fixtures/media/Landscape_4.jpgtests/fixtures/media/Landscape_5.jpgtests/fixtures/media/Landscape_6.jpgtests/fixtures/media/Landscape_7.jpgtests/fixtures/media/Landscape_8.jpgtests/test_media.py
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: