Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Handle image transparency when thumbnailing. #9473

Merged
merged 7 commits into from
Mar 9, 2021
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions changelog.d/9473.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix long-standing bug when generating thumbnails for some images with transparency: `TypeError: cannot unpack non-iterable int object`.
11 changes: 8 additions & 3 deletions synapse/rest/media/v1/thumbnailer.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,14 @@ def aspect(self, max_width: int, max_height: int) -> Tuple[int, int]:
def _resize(self, width: int, height: int) -> Image:
# 1-bit or 8-bit color palette images need converting to RGB
# otherwise they will be scaled using nearest neighbour which
# looks awful
if self.image.mode in ["1", "P"]:
self.image = self.image.convert("RGB")
# looks awful.
#
# If the image has transparency, use RGBA instead.
if self.image.mode in ["1", "L", "P"]:
mode = "RGB"
if self.image.info.get("transparency", None) is not None:
mode = "RGBA"
self.image = self.image.convert(mode)
return self.image.resize((width, height), Image.ANTIALIAS)

def scale(self, width: int, height: int, output_type: str) -> BytesIO:
Expand Down
29 changes: 21 additions & 8 deletions tests/rest/media/v1/test_media_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def test_ensure_media_is_in_local_cache(self):
self.assertEqual(test_body, body)


@attr.s
@attr.s(slots=True, frozen=True)
class _TestImage:
"""An image for testing thumbnailing with the expected results

Expand All @@ -117,13 +117,15 @@ class _TestImage:
test should just check for success.
expected_scaled: The expected bytes from scaled thumbnailing, or None if
test should just check for a valid image returned.
expected_found: True if the file should exist on the server, or False if
a 404 is expected.
"""

data = attr.ib(type=bytes)
content_type = attr.ib(type=bytes)
extension = attr.ib(type=bytes)
expected_cropped = attr.ib(type=Optional[bytes])
expected_scaled = attr.ib(type=Optional[bytes])
expected_cropped = attr.ib(type=Optional[bytes], default=None)
expected_scaled = attr.ib(type=Optional[bytes], default=None)
expected_found = attr.ib(default=True, type=bool)


Expand Down Expand Up @@ -153,6 +155,21 @@ class _TestImage:
),
),
),
# small png with transparency.
(
_TestImage(
unhexlify(
b"89504e470d0a1a0a0000000d49484452000000010000000101000"
b"00000376ef9240000000274524e5300010194fdae0000000a4944"
b"4154789c636800000082008177cd72b60000000049454e44ae426"
b"082"
),
b"image/png",
b".png",
# Note that we don't check the output since it varies across
# different versions of Pillow.
),
),
# small lossless webp
(
_TestImage(
Expand All @@ -162,8 +179,6 @@ class _TestImage:
),
b"image/webp",
b".webp",
None,
None,
),
),
# an empty file
Expand All @@ -172,9 +187,7 @@ class _TestImage:
b"",
b"image/gif",
b".gif",
None,
None,
False,
expected_found=False,
),
),
],
Expand Down