Skip to content

Fix #284 -- Make image dimensions EXIF rotation aware#287

Merged
codingjoe merged 3 commits into
codingjoe:mainfrom
SAY-5:fix/exif-rotation-aware-dimensions
May 13, 2026
Merged

Fix #284 -- Make image dimensions EXIF rotation aware#287
codingjoe merged 3 commits into
codingjoe:mainfrom
SAY-5:fix/exif-rotation-aware-dimensions

Conversation

@SAY-5
Copy link
Copy Markdown
Contributor

@SAY-5 SAY-5 commented May 12, 2026

Closes #284.

Browsers and PIL.ImageOps.exif_transpose rotate images based on their EXIF orientation tag, so the byte-level dimensions can differ from the displayed dimensions. Since source sets are already rotated on save, the width/height reported on PictureFieldFile must match the displayed image. Otherwise width_field/height_field cache the wrong orientation and the <img> tag emits swapped width/height attributes.

PictureFieldFile._get_image_dimensions now swaps the returned width/height when the EXIF orientation tag (0x0112) indicates a 90 or 270 degree rotation (values 5, 6, 7, 8). The dimensions are cached, matching Django's parent behavior.

The existing test_picture__large template snapshot was asserting the buggy pre-fix output; it is updated to reflect the correctly rotated dimensions. A new test_width_height__exif_rotated test pins the fix directly.

Copy link
Copy Markdown
Owner

@codingjoe codingjoe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @SAY-5,

Thank you for the contribution. I am actually hoping to get this resolved in Django, but it's a good idea to ship a patch now.

I left you a few notes. Please make sure to run the benchmark tests locally to monitor the performance impact of your change. You might even want to add a benchmark test if this part of the code isn't covered.

The performance of this package is important; people might process millions of images, and a few milliseconds quickly add up to days.

This part is extra critical as it happens during the request (or whenever a model is saved). So extra I/O to an external blob store might even open a DOS vector.

Please notify me when you are ready for another review.

Cheers,
Joe

Comment thread pictures/models.py Outdated
Comment thread pictures/models.py Outdated
Comment thread pictures/models.py Outdated
Comment thread tests/test_models.py Outdated
@SAY-5
Copy link
Copy Markdown
Contributor Author

SAY-5 commented May 12, 2026

Thanks for the review and the context on the perf sensitivity. I'll run the benchmark tests locally and report the before/after numbers for this path, and add a benchmark if it isn't already covered, before pushing the updates for your other notes.

@SAY-5 SAY-5 force-pushed the fix/exif-rotation-aware-dimensions branch from 49383bf to 11a2dd0 Compare May 12, 2026 23:53
@SAY-5
Copy link
Copy Markdown
Contributor Author

SAY-5 commented May 12, 2026

Reworked _get_image_dimensions to open the file once and read both size and orientation in the same pass instead of going through the super() call, dropped the inherited-method docstring, and added a comment on the orientation tag. The test now asserts the fixture's raw size and orientation up front. Tests pass locally.

@SAY-5 SAY-5 force-pushed the fix/exif-rotation-aware-dimensions branch from 11a2dd0 to 7459b78 Compare May 13, 2026 06:34
@SAY-5
Copy link
Copy Markdown
Contributor Author

SAY-5 commented May 13, 2026

Opened the file once via Image.open and read both size and orientation in the same pass; dropped the super call. Test asserts the fixture's raw size and orientation up front. All tests pass locally.

@SAY-5 SAY-5 force-pushed the fix/exif-rotation-aware-dimensions branch from 7459b78 to cff9200 Compare May 13, 2026 06:53
@SAY-5
Copy link
Copy Markdown
Contributor Author

SAY-5 commented May 13, 2026

Updated per review: dimensions come from django.core.files.images.get_image_dimensions on the already-open handle (no extra open/close), and the orientation tag is read in the same try block via a small Image.open getexif call. Added a comment explaining 5-8. The test now asserts the fixture invariants (size 2000x3000, orientation 8) up front. Test suite passes locally.

Copy link
Copy Markdown
Owner

@codingjoe codingjoe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @SAY-5!

I took the liberty and added more tests, including a benchmark. With the benchmark I found an 80% performance degradation.

Therefore, I updated the implementation to only load the image once and added more resource tests.

The main caveat is that we now load the file image into memory. Which isn't great, but Pillow doesn't offer an easy way to extract EXIF information from partial byte streams.

Since I wrote a lot of new code, I will have copilot do a review. Don't hesitate to give it another review yourself.

Cheers!
Joe

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes incorrect PictureFieldFile dimension reporting for images that rely on EXIF Orientation (90°/270° rotations), ensuring width_field/height_field and generated <img width/height> attributes match how browsers (and ImageOps.exif_transpose) display the image.

Changes:

  • Override PictureFieldFile._get_image_dimensions() to account for EXIF Orientation values that swap width/height and cache the result.
  • Update the existing large template snapshot to reflect corrected width/height output and srcset widths.
  • Add targeted tests for EXIF-rotated dimensions and for preserving file open/close state and cursor position; adjust pytest-benchmark grouping configuration.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
pictures/models.py Adds EXIF-aware dimension calculation and caching for PictureFieldFile.
tests/test_models.py Adds regression test for EXIF-rotated width/height and behavior/performance tests for _get_image_dimensions().
tests/test_templatetags.py Updates snapshot expectations for <img width/height> and srcset after EXIF-aware dimensions.
tests/test_tasks.py Assigns a stable pytest-benchmark group name to the task benchmark.
tests/test_migrations.py Assigns a stable pytest-benchmark group name to the migration benchmark.
pyproject.toml Removes global benchmark grouping-by-fullname to rely on explicit per-test grouping.

Comment thread pictures/models.py Outdated
@codingjoe codingjoe changed the title fix: make image dimensions EXIF rotation aware Fix #284 -- Make image dimensions EXIF rotation aware May 13, 2026
@codingjoe codingjoe merged commit 5e540ef into codingjoe:main May 13, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 get_image_dimensions is EXIF rotation unaware

3 participants