99"""
1010
1111import logging
12+ import math
1213import fitz
1314from ..common .Collection import Collection
1415from ..common .share import BlockType
@@ -93,6 +94,24 @@ def clip_page_to_dict(
9394 )
9495 return self ._to_raw_dict (pix , bbox )
9596
97+ @staticmethod
98+ def _get_image_rotation (matrix ) -> int :
99+ """Extract rotation angle (0, 90, 180, 270) from image transform matrix.
100+
101+ Handles 90-degree multiples only. Based on PyMuPDF Matrix format:
102+ fitz.Matrix(90) -> (0, 1, -1, 0, ...), Matrix(180) -> (-1, 0, 0, -1, ...).
103+ """
104+ if matrix is None :
105+ return 0
106+ try :
107+ a , b , c , d = matrix .a , matrix .b , matrix .c , matrix .d
108+ except AttributeError :
109+ return 0
110+ # Round to nearest 90 degrees using atan2
111+ angle_rad = math .atan2 (b , a )
112+ angle_deg = round (math .degrees (angle_rad ) / 90 ) * 90
113+ return int (angle_deg % 360 )
114+
96115 def extract_images (self , clip_image_res_ratio : float = 3.0 ):
97116 """Extract normal images with ``Page.get_images()``.
98117
@@ -117,16 +136,23 @@ def extract_images(self, clip_image_res_ratio: float = 3.0):
117136 # extract the equivalent image by clipping the union page region for now.
118137 # https://github.com/dothinking/pdf2docx/issues/123
119138
120- # step 1: collect images: [(bbox, item), ..., ]
139+ # step 1: collect images: [(bbox, item, image_rotation ), ..., ]
121140 ic = Collection ()
122141 for item in self ._page .get_images (full = True ):
123142 item = list (item )
124143 item [- 1 ] = 0
144+ xref = item [0 ]
125145
126146 # find all occurrences referenced to this image
127147 rects = self ._page .get_image_rects (item )
148+ # get per-image transform (rotation) when available (PyMuPDF 1.19+)
149+ rects_with_transform = []
150+ try :
151+ rects_with_transform = self ._page .get_image_rects (xref , transform = True )
152+ except (TypeError , AttributeError ):
153+ pass
128154 unrotated_page_bbox = self ._page .cropbox # note the difference to page.rect
129- for bbox in rects :
155+ for i , bbox in enumerate ( rects ) :
130156 # ignore small images
131157 if bbox .get_area () <= 4 :
132158 continue
@@ -135,8 +161,15 @@ def extract_images(self, clip_image_res_ratio: float = 3.0):
135161 if not unrotated_page_bbox .intersects (bbox ):
136162 continue
137163
138- # collect images
139- ic .append ((bbox , item ))
164+ # extract per-image rotation from transform matrix
165+ image_rotation = 0
166+ if i < len (rects_with_transform ):
167+ entry = rects_with_transform [i ]
168+ if isinstance (entry , (list , tuple )) and len (entry ) >= 2 :
169+ matrix_t = entry [1 ]
170+ image_rotation = self ._get_image_rotation (matrix_t )
171+
172+ ic .append ((bbox , item , image_rotation ))
140173
141174 # step 2: group by intersection
142175 fun = lambda a , b : a [0 ].intersects (b [0 ])
@@ -148,14 +181,14 @@ def extract_images(self, clip_image_res_ratio: float = 3.0):
148181 # clip page with the union bbox of all intersected images
149182 if len (group ) > 1 :
150183 clip_bbox = fitz .Rect ()
151- for bbox , item in group :
184+ for bbox , item , _ in group :
152185 clip_bbox |= bbox
153186 raw_dict = self .clip_page_to_dict (
154187 clip_bbox , False , clip_image_res_ratio
155188 )
156189
157190 else :
158- bbox , item = group [0 ]
191+ bbox , item , image_rotation = group [0 ]
159192
160193 # Regarding images consist of alpha values only, the turquoise color shown in
161194 # the PDF is not part of the image, but part of PDF background.
@@ -182,10 +215,12 @@ def extract_images(self, clip_image_res_ratio: float = 3.0):
182215 # recover image, e.g., handle image with mask, or CMYK color space
183216 pix = self ._recover_pixmap (doc , item )
184217
185- # rotate image with opencv if page is rotated
218+ # rotate image: apply inverse of per-image transform, then page rotation
219+ # (PyMuPDF matrix maps image->page; correct pixmap with inverse: apply -angle)
186220 raw_dict = self ._to_raw_dict (pix , bbox )
187- if rotation :
188- raw_dict ["image" ] = self ._rotate_image (pix , - rotation )
221+ total_rotation = (rotation or 0 ) - image_rotation
222+ if total_rotation :
223+ raw_dict ["image" ] = self ._rotate_image (pix , total_rotation )
189224
190225 images .append (raw_dict )
191226
0 commit comments