Skip to content

Add support for image orientation - #148

Merged
FireMasterK merged 5 commits into
masterfrom
image-rotation
Oct 10, 2026
Merged

FireMasterK merged 5 commits into
masterfrom
image-rotation

Conversation

@FireMasterK

@FireMasterK FireMasterK commented Oct 10, 2026 •

Copy link
Copy Markdown

Replaces #110 (head fork TheZ3ro:image-rotation is not pushable by this repo's maintainers, so the reviewed changes land here instead).


Fixes python-openxml#540

Modern image formats (PNG, JPEG, Tiff, etc) embed the orientation in Exif metadata.
When rotating or displaying an image, software like Word, PowerPoint, macOS Preview, Android Google Photo, etc do not rotate the actual pixel data but instead change the orientation metadata integer (https://exifstrip.com/guides/orientation-tag-explained)

This is governed in OpenXML with the a:xfrm (transform) tag. This tag can contain the rot, flipV, and flipH attributes. By mapping the 1-8 orientation values to the respective rot and flip attributes it's possible to display images that were rotated by merely changing the Exif metadata with the correct orientation.

This PR adds support for image orientation.

By default it gets orientation from Exif metadata. If orientation metadata is not available it falls back to "Normal" orientation (like now, defined by raw pixel data).

It is also possible to force a given orientation programmatically by:

document.add_picture(path_to_picture, width=Inches(3), orientation=EXIF_ORIENTATION.ROTATE_90))
last_paragraph = document.paragraphs[-1]
last_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER

Review fixes (commit 8ac3261)

Addresses the CodeRabbit review on #110:

  • src/docx/image/jpeg.py: both Exif/Jfif constructors now select the EXIF-specific APP1 marker (_JfifMarkers.exif_app1) so a preceding non-EXIF APP1 segment (e.g. XMP) can no longer override EXIF orientation; regression test builds a JFIF stream with non-EXIF APP1 before EXIF APP1 and asserts the later marker's orientation survives.
  • src/docx/parts/story.py: resolve AUTO orientation before scaling and cross-map width/height when orientation.swaps_axes, so aspect-ratio scaling runs in native image axes while wp:extent is swapped exactly once.
  • src/docx/shape.py: InlineShape.width/height setters apply the same crossed-axis mapping when a:xfrm/@rot is 90°/270°, keeping explicit dimensions correct after wp:extent axis swapping.
  • Tests: width-only, height-only and both-dimensions regression coverage across axis-swapping and non-swapping orientations; orientation-propagation fixtures now use non-default values so regressions to the default fail.

All 1656 tests pass; ruff check/format unchanged from baseline.

Made with Cursor

Summary by CodeRabbit

  • New Features
    • Added automatic handling of embedded orientation in JPEG, PNG, and TIFF images when adding pictures to documents.
    • Added an optional orientation setting for inserted pictures; automatic detection is enabled by default.
    • Picture sizing now accounts for rotation when setting width or height.

@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 48 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3af76dc8-4a62-44e5-9513-0b39319f6b3b

📥 Commits

Reviewing files that changed from the base of the PR and between 8c91d81 and 4f60aa4.


📒 Files selected for processing (3)
  • src/docx/document.py
  • src/docx/image/jpeg.py
  • tests/image/test_png.py

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9dd5ec78-8c31-4ede-b623-c6cf63624540



📥 Commits

Reviewing files that changed from the base of the PR and between 8ac3261 and 8c91d81.




📒 Files selected for processing (11)
  • src/docx/document.py
  • src/docx/image/constants.py
  • src/docx/image/tiff.py
  • src/docx/oxml/shape.py
  • src/docx/oxml/simpletypes.py
  • src/docx/text/run.py
  • tests/image/test_image.py
  • tests/image/test_jpeg.py
  • tests/image/test_tiff.py
  • tests/test_document.py
  • tests/text/test_run.py



🚧 Files skipped from review as they are similar to previous changes (1)
  • src/docx/oxml/shape.py



Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.





📝 Walkthrough
📝 Walkthrough
📝 Walkthrough

Walkthrough

Image headers now expose orientation values from JPEG, PNG, and TIFF data. Picture APIs accept an orientation option and pass it to DrawingML transforms. Picture dimensions account for rotations that swap axes.

Changes

Image Orientation

Layer / File(s) Summary
Read image orientation
src/docx/enum/shape.py, src/docx/image/constants.py, src/docx/image/image.py, src/docx/image/jpeg.py, src/docx/image/png.py, src/docx/image/tiff.py, tests/image/*
Image headers store orientation values. JPEG, PNG, and TIFF parsers read orientation, with a default of 1 when no value is present. Tests cover orientation propagation and absent values.
Pass orientation to picture creation
src/docx/document.py, src/docx/text/run.py, src/docx/parts/story.py, src/docx/oxml/shape.py, src/docx/oxml/simpletypes.py, tests/oxml/test_shape.py, tests/parts/test_story.py, tests/test_document.py, tests/text/test_run.py
Picture APIs accept an orientation option that defaults to AUTO. Picture creation resolves AUTO from the image, adjusts dimensions for axis-swapping orientations, and sets rotation and flip attributes.
Update dimensions after rotation
src/docx/shape.py, tests/test_shape.py
Width and height setters update native picture dimensions on the axes that match the transform rotation.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature · Severity of issue fixed: Low

Sequence Diagram(s)

sequenceDiagram
  participant Document
  participant Run
  participant StoryPart
  participant CT_Inline
  participant CT_Picture
  Document->>Run: pass orientation to add_picture
  Run->>StoryPart: pass orientation to new_pic_inline
  StoryPart->>CT_Inline: pass resolved orientation and dimensions
  CT_Inline->>CT_Picture: set rotation and flip attributes
Loading




Merge Risk: ⚪ Minimal · up to 8c91d

The change adds image orientation support through the picture APIs and the Exif parsers. No actionable merge-blocking risk was found in the supplied context.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 34.92% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 126 functions across 21 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: adding support for image orientation based on Exif metadata and programmatic selection.
Linked Issues check Passed Issue #540 requires images added with Document.add_picture(..., width=...) to retain the intended orientation. The PR reads Exif orientation for JPEG, PNG, and TIFF images, defaults to normal orient…
Out of Scope Changes check Passed The source changes implement image-orientation parsing, the public orientation option, DrawingML transforms, and dimension handling. The test changes verify these behaviors. The reviewed summary estab…





✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR






🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR



🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR





  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

FireMasterK and others added 3 commits October 10, 2026 19:46
Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	tests/image/test_jpeg.py
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@FireMasterK
FireMasterK merged commit d41b921 into master Oct 10, 2026
4 checks passed
@FireMasterK
FireMasterK deleted the image-rotation branch October 10, 2026 21:42
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.

Problem with rotation when adding images

2 participants