mirror of
https://github.com/zulip/zulip.git
synced 2025-11-10 08:56:10 +00:00
upload: Replace exif_rotate with Pillow exif_transpose.
Fixes #18599. Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
committed by
Tim Abbott
parent
6289803368
commit
14f0594795
@@ -25,7 +25,7 @@ from django.http import HttpRequest
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from jinja2.utils import Markup as mark_safe
|
from jinja2.utils import Markup as mark_safe
|
||||||
from PIL import ExifTags, Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
from PIL.GifImagePlugin import GifImageFile
|
from PIL.GifImagePlugin import GifImageFile
|
||||||
from PIL.Image import DecompressionBombError
|
from PIL.Image import DecompressionBombError
|
||||||
|
|
||||||
@@ -103,33 +103,10 @@ class BadImageError(JsonableError):
|
|||||||
code = ErrorCode.BAD_IMAGE
|
code = ErrorCode.BAD_IMAGE
|
||||||
|
|
||||||
|
|
||||||
name_to_tag_num = {name: num for num, name in ExifTags.TAGS.items()}
|
|
||||||
|
|
||||||
# https://stackoverflow.com/a/6218425
|
|
||||||
def exif_rotate(image: Image) -> Image:
|
|
||||||
if not hasattr(image, "_getexif"):
|
|
||||||
return image
|
|
||||||
exif_data = image._getexif()
|
|
||||||
if exif_data is None:
|
|
||||||
return image
|
|
||||||
|
|
||||||
exif_dict = dict(exif_data.items())
|
|
||||||
orientation = exif_dict.get(name_to_tag_num["Orientation"])
|
|
||||||
|
|
||||||
if orientation == 3:
|
|
||||||
return image.rotate(180, expand=True)
|
|
||||||
elif orientation == 6:
|
|
||||||
return image.rotate(270, expand=True)
|
|
||||||
elif orientation == 8:
|
|
||||||
return image.rotate(90, expand=True)
|
|
||||||
|
|
||||||
return image
|
|
||||||
|
|
||||||
|
|
||||||
def resize_avatar(image_data: bytes, size: int = DEFAULT_AVATAR_SIZE) -> bytes:
|
def resize_avatar(image_data: bytes, size: int = DEFAULT_AVATAR_SIZE) -> bytes:
|
||||||
try:
|
try:
|
||||||
im = Image.open(io.BytesIO(image_data))
|
im = Image.open(io.BytesIO(image_data))
|
||||||
im = exif_rotate(im)
|
im = ImageOps.exif_transpose(im)
|
||||||
im = ImageOps.fit(im, (size, size), Image.ANTIALIAS)
|
im = ImageOps.fit(im, (size, size), Image.ANTIALIAS)
|
||||||
except OSError:
|
except OSError:
|
||||||
raise BadImageError(_("Could not decode image; did you upload an image file?"))
|
raise BadImageError(_("Could not decode image; did you upload an image file?"))
|
||||||
@@ -145,7 +122,7 @@ def resize_avatar(image_data: bytes, size: int = DEFAULT_AVATAR_SIZE) -> bytes:
|
|||||||
def resize_logo(image_data: bytes) -> bytes:
|
def resize_logo(image_data: bytes) -> bytes:
|
||||||
try:
|
try:
|
||||||
im = Image.open(io.BytesIO(image_data))
|
im = Image.open(io.BytesIO(image_data))
|
||||||
im = exif_rotate(im)
|
im = ImageOps.exif_transpose(im)
|
||||||
im.thumbnail((8 * DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_SIZE), Image.ANTIALIAS)
|
im.thumbnail((8 * DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_SIZE), Image.ANTIALIAS)
|
||||||
except OSError:
|
except OSError:
|
||||||
raise BadImageError(_("Could not decode image; did you upload an image file?"))
|
raise BadImageError(_("Could not decode image; did you upload an image file?"))
|
||||||
@@ -202,7 +179,7 @@ def resize_emoji(image_data: bytes, size: int = DEFAULT_EMOJI_SIZE) -> bytes:
|
|||||||
)
|
)
|
||||||
return resize_gif(im, size) if should_resize else image_data
|
return resize_gif(im, size) if should_resize else image_data
|
||||||
else:
|
else:
|
||||||
im = exif_rotate(im)
|
im = ImageOps.exif_transpose(im)
|
||||||
im = ImageOps.fit(im, (size, size), Image.ANTIALIAS)
|
im = ImageOps.fit(im, (size, size), Image.ANTIALIAS)
|
||||||
out = io.BytesIO()
|
out = io.BytesIO()
|
||||||
im.save(out, format=image_format)
|
im.save(out, format=image_format)
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.5 KiB |
@@ -51,7 +51,6 @@ from zerver.lib.upload import (
|
|||||||
ZulipUploadBackend,
|
ZulipUploadBackend,
|
||||||
delete_export_tarball,
|
delete_export_tarball,
|
||||||
delete_message_image,
|
delete_message_image,
|
||||||
exif_rotate,
|
|
||||||
resize_avatar,
|
resize_avatar,
|
||||||
resize_emoji,
|
resize_emoji,
|
||||||
sanitize_name,
|
sanitize_name,
|
||||||
@@ -2153,38 +2152,6 @@ class UploadSpaceTests(UploadSerializeMixin, ZulipTestCase):
|
|||||||
self.assert_length(data2, self.realm.currently_used_upload_space_bytes())
|
self.assert_length(data2, self.realm.currently_used_upload_space_bytes())
|
||||||
|
|
||||||
|
|
||||||
class ExifRotateTests(ZulipTestCase):
|
|
||||||
def test_image_do_not_rotate(self) -> None:
|
|
||||||
# Image does not have _getexif method.
|
|
||||||
with get_test_image_file("img.png") as f, Image.open(f) as img:
|
|
||||||
result = exif_rotate(img)
|
|
||||||
self.assertEqual(result, img)
|
|
||||||
|
|
||||||
# Image with no exif data.
|
|
||||||
with get_test_image_file("img_no_exif.jpg") as f, Image.open(f) as img:
|
|
||||||
result = exif_rotate(img)
|
|
||||||
self.assertEqual(result, img)
|
|
||||||
|
|
||||||
# Orientation of the image is 1.
|
|
||||||
with get_test_image_file("img.jpg") as f, Image.open(f) as img:
|
|
||||||
result = exif_rotate(img)
|
|
||||||
self.assertEqual(result, img)
|
|
||||||
|
|
||||||
def test_image_rotate(self) -> None:
|
|
||||||
with mock.patch("PIL.Image.Image.rotate") as rotate:
|
|
||||||
with get_test_image_file("img_orientation_3.jpg") as f, Image.open(f) as img:
|
|
||||||
exif_rotate(img)
|
|
||||||
rotate.assert_called_with(180, expand=True)
|
|
||||||
|
|
||||||
with get_test_image_file("img_orientation_6.jpg") as f, Image.open(f) as img:
|
|
||||||
exif_rotate(img)
|
|
||||||
rotate.assert_called_with(270, expand=True)
|
|
||||||
|
|
||||||
with get_test_image_file("img_orientation_8.jpg") as f, Image.open(f) as img:
|
|
||||||
exif_rotate(img)
|
|
||||||
rotate.assert_called_with(90, expand=True)
|
|
||||||
|
|
||||||
|
|
||||||
class DecompressionBombTests(ZulipTestCase):
|
class DecompressionBombTests(ZulipTestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|||||||
Reference in New Issue
Block a user