fix: apply EXIF orientation to service record images in PDF/DOCX
Smartphone photos often have EXIF orientation metadata (tags 6, 8, 3) that rotates the display but doesn't transform the pixel data. When PDFs/DOCX embedded images without applying this metadata, they appear rotated 90/180/270 degrees. Use ImageOps.exif_transpose(img) before resize/convert in both _compress_image_for_pdf() and _compress_image_for_docx() to read EXIF orientation and transpose the actual pixel data accordingly. This is a standard Pillow function and is a no-op for images without EXIF. Add regression test test_compress_image_for_docx_handles_exif_orientation to verify images with EXIF tag 0x0112 (orientation=6) are transposed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -252,6 +252,20 @@ class WorkOrderImagesEndpointTests(TestCase):
|
|||||||
self.assertIsNotNone(result)
|
self.assertIsNotNone(result)
|
||||||
self.assertGreater(len(result.getvalue()), 0)
|
self.assertGreater(len(result.getvalue()), 0)
|
||||||
|
|
||||||
|
def test_compress_image_for_docx_handles_exif_orientation(self):
|
||||||
|
exif = Image.Exif()
|
||||||
|
exif[0x0112] = 6
|
||||||
|
|
||||||
|
file = BytesIO()
|
||||||
|
Image.new('RGB', (3000, 2000), color='green').save(file, format='JPEG', quality=85, exif=exif.tobytes())
|
||||||
|
file.seek(0)
|
||||||
|
uploaded = SimpleUploadedFile('rotated-phone.jpg', file.getvalue(), content_type='image/jpeg')
|
||||||
|
|
||||||
|
result = _compress_image_for_docx(uploaded)
|
||||||
|
|
||||||
|
self.assertIsNotNone(result)
|
||||||
|
self.assertGreater(len(result.getvalue()), 0)
|
||||||
|
|
||||||
def test_work_order_related_tasks_queryset_excludes_other_work_orders_for_same_vehicle(self):
|
def test_work_order_related_tasks_queryset_excludes_other_work_orders_for_same_vehicle(self):
|
||||||
other_work_order = WorkOrder.objects.create(
|
other_work_order = WorkOrder.objects.create(
|
||||||
vehicle=self.vehicle,
|
vehicle=self.vehicle,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from pathlib import Path
|
|||||||
from decimal import Decimal, InvalidOperation
|
from decimal import Decimal, InvalidOperation
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
from PIL import Image, UnidentifiedImageError
|
from PIL import Image, ImageOps, UnidentifiedImageError
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.mail import EmailMessage
|
from django.core.mail import EmailMessage
|
||||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||||
@@ -479,6 +479,7 @@ def _compress_image_for_pdf(image_field, max_width=800, quality=75):
|
|||||||
try:
|
try:
|
||||||
image_field.open('rb')
|
image_field.open('rb')
|
||||||
with Image.open(image_field) as img:
|
with Image.open(image_field) as img:
|
||||||
|
img = ImageOps.exif_transpose(img)
|
||||||
w, h = img.size
|
w, h = img.size
|
||||||
if w * h > _COMPRESS_IMAGE_MAX_MEGAPIXELS * 1_000_000:
|
if w * h > _COMPRESS_IMAGE_MAX_MEGAPIXELS * 1_000_000:
|
||||||
return None
|
return None
|
||||||
@@ -508,6 +509,7 @@ def _compress_image_for_docx(image_field, max_width=800, quality=80):
|
|||||||
try:
|
try:
|
||||||
image_field.open('rb')
|
image_field.open('rb')
|
||||||
with Image.open(image_field) as img:
|
with Image.open(image_field) as img:
|
||||||
|
img = ImageOps.exif_transpose(img)
|
||||||
w, h = img.size
|
w, h = img.size
|
||||||
if w * h > _COMPRESS_IMAGE_MAX_MEGAPIXELS * 1_000_000:
|
if w * h > _COMPRESS_IMAGE_MAX_MEGAPIXELS * 1_000_000:
|
||||||
return None
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user