Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pillow create thumbnail by cropping instead of preserving aspect ratio

By default, the thumbnail method preserves aspect ratio which may result in inconsistently sized thumbnails.

Image.thumbnail(size, resample=3) Make this image into a thumbnail. This method modifies the image to contain a thumbnail version of itself, no larger than the given size. This method calculates an appropriate thumbnail size to preserve the aspect of the image, calls the draft() method to configure the file reader (where applicable), and finally resizes the image.

I want it to crop the image so that the thumbnail fills the entire canvas provided, such that the image isn't warped. For example, Image.thumbnail((200, 200), Image.ANTIALIAS) on an image that's 640 by 480 would crop to the center 480 by 480, then scale to exactly 200 by 200 (not 199 or 201). How can this be done?

like image 760
davidtgq Avatar asked May 02 '17 09:05

davidtgq


People also ask

How do I crop an image in OpenCV?

There is no specific function for cropping using OpenCV, NumPy array slicing is what does the job. Every image that is read in, gets stored in a 2D array (for each color channel). Simply specify the height and width (in pixels) of the area to be cropped. And it's done!


1 Answers

This is easiest to do in two stages: crop first, then generate a thumbnail. Cropping to a given aspect ratio is common enough that there really should be a function for it in PILLOW, but as far as I know there isn't. Here's a simple implementation, monkey-patched onto the Image class:

from PIL import Image

class _Image(Image.Image):

    def crop_to_aspect(self, aspect, divisor=1, alignx=0.5, aligny=0.5):
        """Crops an image to a given aspect ratio.
        Args:
            aspect (float): The desired aspect ratio.
            divisor (float): Optional divisor. Allows passing in (w, h) pair as the first two arguments.
            alignx (float): Horizontal crop alignment from 0 (left) to 1 (right)
            aligny (float): Vertical crop alignment from 0 (left) to 1 (right)
        Returns:
            Image: The cropped Image object.
        """
        if self.width / self.height > aspect / divisor:
            newwidth = int(self.height * (aspect / divisor))
            newheight = self.height
        else:
            newwidth = self.width
            newheight = int(self.width / (aspect / divisor))
        img = self.crop((alignx * (self.width - newwidth),
                         aligny * (self.height - newheight),
                         alignx * (self.width - newwidth) + newwidth,
                         aligny * (self.height - newheight) + newheight))
        return img

Image.Image.crop_to_aspect = _Image.crop_to_aspect

Given this, you can just write

cropped = img.crop_to_aspect(200,200)
cropped.thumbnail((200, 200), Image.ANTIALIAS)
like image 138
Uri Granta Avatar answered Sep 20 '22 11:09

Uri Granta