Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions backend/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def _validate_uploaded_file_sync(file: UploadFile) -> Optional[Image.Image]:
# Additional content validation: Try to open with PIL to ensure it's a valid image
try:
img = Image.open(file.file)
original_format = img.format # Capture format before operations
# Optimization: Skip img.verify() to avoid full file read.
# Corrupt files will fail during resize or subsequent processing.

Expand All @@ -101,7 +102,11 @@ def _validate_uploaded_file_sync(file: UploadFile) -> Optional[Image.Image]:

# Save resized image back to file
output = io.BytesIO()
img.save(output, format=img.format or 'JPEG', quality=85)

# Determine save format (default to PNG for RGBA to avoid JPEG error)
save_format = original_format or ('PNG' if img.mode == 'RGBA' else 'JPEG')

img.save(output, format=save_format, quality=85)
output.seek(0)

Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

After resizing, img is a new PIL object whose format is typically None. Even though you preserve original_format for saving back into file.file, you still return the resized img with img.format=None, which can later cause downstream code (e.g., helpers that default to JPEG when image.format is missing) to attempt JPEG encoding and crash again for RGBA images. Consider restoring img.format (and/or returning a reopened image from the resized bytes) so callers receive an image with a correct format.

Suggested change
# Ensure the returned image reports the correct format
img.format = save_format

Copilot uses AI. Check for mistakes.
# Replace file content
Expand Down Expand Up @@ -168,6 +173,7 @@ def process_uploaded_image_sync(file: UploadFile) -> io.BytesIO:

try:
img = Image.open(file.file)
original_format = img.format # Capture format before operations

# Resize if needed
if img.width > 1024 or img.height > 1024:
Expand All @@ -177,14 +183,17 @@ def process_uploaded_image_sync(file: UploadFile) -> io.BytesIO:
img = img.resize((new_width, new_height), Image.Resampling.BILINEAR)

# Strip EXIF
img_no_exif = Image.new(img.mode, img.size)
img_no_exif.paste(img)
# Optimization: Clear info dictionary instead of creating new image and pasting (avoids full copy)
if hasattr(img, 'info'):
img.info.clear()

# Save to BytesIO
output = io.BytesIO()
# Preserve format or default to JPEG
fmt = img.format or 'JPEG'
img_no_exif.save(output, format=fmt, quality=85)

# Preserve format or default to JPEG, handling RGBA edge case
save_format = original_format or ('PNG' if img.mode == 'RGBA' else 'JPEG')

img.save(output, format=save_format, quality=85)
Comment on lines 185 to +196
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

The PR description mentions verifying changes with tests/test_image_optimization.py, but that test file isn’t included in this PR. Since the EXIF-stripping and format-preserving resize behavior is security/behaviorally significant, please add/keep automated tests that cover (1) large RGBA PNG resize path and (2) metadata/EXIF removal, so regressions are caught.

Copilot uses AI. Check for mistakes.
output.seek(0)

return output
Expand Down
Loading