fix: transform crop_bounds from EXIF-applied to raw image space
Gemini analyzes images with EXIF orientation applied (e.g., 90° CW rotation), returning crop_bounds in that coordinate space. We need to transform them back to raw image coordinates before cropping. For EXIF orientation 6 (Rotate 90 CW): - Raw: 4032×3024 (landscape) - Displayed (after EXIF): 3024×4032 (portrait) - Transform portrait coords back to landscape before cropping This fixes the issue where crop was applied to wrong image region.
This commit is contained in:
@@ -88,6 +88,15 @@ class ImageProcessor:
|
|||||||
# Extract EXIF orientation (but don't apply yet)
|
# Extract EXIF orientation (but don't apply yet)
|
||||||
exif_orientation = self._extract_exif_orientation(image)
|
exif_orientation = self._extract_exif_orientation(image)
|
||||||
|
|
||||||
|
# Transform crop_bounds from EXIF-applied space to raw space
|
||||||
|
if crop_bounds and exif_orientation and exif_orientation > 1:
|
||||||
|
crop_bounds = self._transform_crop_bounds_by_exif(
|
||||||
|
crop_bounds, exif_orientation, original_size[0], original_size[1]
|
||||||
|
)
|
||||||
|
msg = f"[CROP] EXIF {exif_orientation} transform applied: {crop_bounds}"
|
||||||
|
self.logger.info(msg)
|
||||||
|
print(f">>> {msg}")
|
||||||
|
|
||||||
# Smart cropping (on raw, un-rotated image so crop_bounds match AI analysis)
|
# Smart cropping (on raw, un-rotated image so crop_bounds match AI analysis)
|
||||||
cropped_image = image
|
cropped_image = image
|
||||||
crop_size = None
|
crop_size = None
|
||||||
@@ -204,6 +213,44 @@ class ImageProcessor:
|
|||||||
self.logger.debug(f"Could not extract EXIF orientation: {e}")
|
self.logger.debug(f"Could not extract EXIF orientation: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _transform_crop_bounds_by_exif(
|
||||||
|
self, crop_bounds: Dict, orientation: int, raw_width: int, raw_height: int
|
||||||
|
) -> Dict:
|
||||||
|
"""
|
||||||
|
Transform crop_bounds from EXIF-applied space to raw image space.
|
||||||
|
|
||||||
|
Gemini analyzes images with EXIF applied, returning crop_bounds in that space.
|
||||||
|
We need to transform them back to raw image coordinates before cropping.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
crop_bounds: {x, y, width, height} in EXIF-applied space
|
||||||
|
orientation: EXIF orientation value (1-8)
|
||||||
|
raw_width: raw image width (before EXIF)
|
||||||
|
raw_height: raw image height (before EXIF)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Transformed crop_bounds in raw image space
|
||||||
|
"""
|
||||||
|
if orientation == 1:
|
||||||
|
# No rotation, use as-is
|
||||||
|
return crop_bounds
|
||||||
|
elif orientation == 6:
|
||||||
|
# Rotate 90 CW: raw (w×h) appears as displayed (h×w)
|
||||||
|
# Transform from displayed (h×w) back to raw (w×h)
|
||||||
|
x, y, w, h = crop_bounds['x'], crop_bounds['y'], crop_bounds['width'], crop_bounds['height']
|
||||||
|
# After 90° CW: (x,y) in raw → (raw_height - y, x) in displayed
|
||||||
|
# Reverse: (x,y) in displayed → (y, raw_width - x) in raw
|
||||||
|
return {
|
||||||
|
'x': y,
|
||||||
|
'y': raw_width - x - w,
|
||||||
|
'width': h,
|
||||||
|
'height': w
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# Other orientations: return as-is (TODO: implement if needed)
|
||||||
|
self.logger.warning(f"EXIF orientation {orientation} not supported for crop_bounds transform, using as-is")
|
||||||
|
return crop_bounds
|
||||||
|
|
||||||
def _rotate_by_orientation(
|
def _rotate_by_orientation(
|
||||||
self, image: Image.Image, orientation: int
|
self, image: Image.Image, orientation: int
|
||||||
) -> Image.Image:
|
) -> Image.Image:
|
||||||
|
|||||||
Reference in New Issue
Block a user