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)
|
||||
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)
|
||||
cropped_image = image
|
||||
crop_size = None
|
||||
@@ -204,6 +213,44 @@ class ImageProcessor:
|
||||
self.logger.debug(f"Could not extract EXIF orientation: {e}")
|
||||
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(
|
||||
self, image: Image.Image, orientation: int
|
||||
) -> Image.Image:
|
||||
|
||||
Reference in New Issue
Block a user