media/thumbnailer: Better quality for 1-bit / 8-bit color palette images (#2142)

Pillow will use nearest neighbour as the resampling algorithm if the
source image is either 1-bit or a color palette using 8 bits. If we
convert to RGB before scaling, we'll probably get a better result.
This commit is contained in:
Robert Swain 2019-10-04 10:34:52 +02:00 committed by Richard van der Hoff
parent 66537e10ce
commit 39b40d6d99
2 changed files with 12 additions and 3 deletions

1
changelog.d/2142.feature Normal file
View file

@ -0,0 +1 @@
Improve quality of thumbnails for 1-bit/8-bit color palette images.

View file

@ -82,13 +82,21 @@ class Thumbnailer(object):
else: else:
return (max_height * self.width) // self.height, max_height return (max_height * self.width) // self.height, max_height
def _resize(self, width, height):
# 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")
return self.image.resize((width, height), Image.ANTIALIAS)
def scale(self, width, height, output_type): def scale(self, width, height, output_type):
"""Rescales the image to the given dimensions. """Rescales the image to the given dimensions.
Returns: Returns:
BytesIO: the bytes of the encoded image ready to be written to disk BytesIO: the bytes of the encoded image ready to be written to disk
""" """
scaled = self.image.resize((width, height), Image.ANTIALIAS) scaled = self._resize(width, height)
return self._encode_image(scaled, output_type) return self._encode_image(scaled, output_type)
def crop(self, width, height, output_type): def crop(self, width, height, output_type):
@ -107,13 +115,13 @@ class Thumbnailer(object):
""" """
if width * self.height > height * self.width: if width * self.height > height * self.width:
scaled_height = (width * self.height) // self.width scaled_height = (width * self.height) // self.width
scaled_image = self.image.resize((width, scaled_height), Image.ANTIALIAS) scaled_image = self._resize(width, scaled_height)
crop_top = (scaled_height - height) // 2 crop_top = (scaled_height - height) // 2
crop_bottom = height + crop_top crop_bottom = height + crop_top
cropped = scaled_image.crop((0, crop_top, width, crop_bottom)) cropped = scaled_image.crop((0, crop_top, width, crop_bottom))
else: else:
scaled_width = (height * self.width) // self.height scaled_width = (height * self.width) // self.height
scaled_image = self.image.resize((scaled_width, height), Image.ANTIALIAS) scaled_image = self._resize(scaled_width, height)
crop_left = (scaled_width - width) // 2 crop_left = (scaled_width - width) // 2
crop_right = width + crop_left crop_right = width + crop_left
cropped = scaled_image.crop((crop_left, 0, crop_right, height)) cropped = scaled_image.crop((crop_left, 0, crop_right, height))