Handle image transparency better when thumbnailing. (#9473)

Properly uses RGBA mode for 1- and 8-bit images with transparency
(instead of RBG mode).
This commit is contained in:
Patrick Cloke 2021-03-09 07:37:09 -05:00 committed by GitHub
parent 3ce650057d
commit 075c16b410
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 30 additions and 11 deletions

1
changelog.d/9473.bugfix Normal file
View File

@ -0,0 +1 @@
Fix long-standing bug when generating thumbnails for some images with transparency: `TypeError: cannot unpack non-iterable int object`.

View File

@ -96,9 +96,14 @@ class Thumbnailer:
def _resize(self, width: int, height: int) -> Image: def _resize(self, width: int, height: int) -> Image:
# 1-bit or 8-bit color palette images need converting to RGB # 1-bit or 8-bit color palette images need converting to RGB
# otherwise they will be scaled using nearest neighbour which # otherwise they will be scaled using nearest neighbour which
# looks awful # looks awful.
if self.image.mode in ["1", "P"]: #
self.image = self.image.convert("RGB") # 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) return self.image.resize((width, height), Image.ANTIALIAS)
def scale(self, width: int, height: int, output_type: str) -> BytesIO: def scale(self, width: int, height: int, output_type: str) -> BytesIO:

View File

@ -105,7 +105,7 @@ class MediaStorageTests(unittest.HomeserverTestCase):
self.assertEqual(test_body, body) self.assertEqual(test_body, body)
@attr.s @attr.s(slots=True, frozen=True)
class _TestImage: class _TestImage:
"""An image for testing thumbnailing with the expected results """An image for testing thumbnailing with the expected results
@ -117,13 +117,15 @@ class _TestImage:
test should just check for success. test should just check for success.
expected_scaled: The expected bytes from scaled thumbnailing, or None if expected_scaled: The expected bytes from scaled thumbnailing, or None if
test should just check for a valid image returned. 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) data = attr.ib(type=bytes)
content_type = attr.ib(type=bytes) content_type = attr.ib(type=bytes)
extension = attr.ib(type=bytes) extension = attr.ib(type=bytes)
expected_cropped = attr.ib(type=Optional[bytes]) expected_cropped = attr.ib(type=Optional[bytes], default=None)
expected_scaled = attr.ib(type=Optional[bytes]) expected_scaled = attr.ib(type=Optional[bytes], default=None)
expected_found = attr.ib(default=True, type=bool) expected_found = attr.ib(default=True, type=bool)
@ -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 # small lossless webp
( (
_TestImage( _TestImage(
@ -162,8 +179,6 @@ class _TestImage:
), ),
b"image/webp", b"image/webp",
b".webp", b".webp",
None,
None,
), ),
), ),
# an empty file # an empty file
@ -172,9 +187,7 @@ class _TestImage:
b"", b"",
b"image/gif", b"image/gif",
b".gif", b".gif",
None, expected_found=False,
None,
False,
), ),
), ),
], ],