Search code examples
pythonimagezoomingpython-imaging-librarycrop

Crop image with settable center and scale in Python PIL


I would like to crop an image using PIL, although it could be some other module. I need the method to crop with a scale factor, ie 1.5 meaning that the output would be 1.5x zoomed in. Additionally, I would need to set the center where it zooms. This means setting x/2,y/2 as the center would zoom straight to the center, but other x,y values would zoom into those pixels.

If anyone knows how to do this I would really appreciate any help.

Right now I have some cropping working with ims = im.crop((int((x-x/i)/2), int((y-y/i)/2), int((x+(x/i))/2), int((y+(y/i))/2))) but that only zooms into the center, and "i" doesn't give a nice scale factor.

Again, that you for your help.


Solution

  • It is just a matter of getting the center and the sizes right.

    1. Determine the center of the spot where you want to crop
    2. Determine the new size using the scale factor
    3. Determine the bounding box of the cropped image

    The following script should do the trick.

    import os.path
    from PIL import Image
    
    def get_img_dir():
        src_dir = os.path.dirname(__file__)
        img_dir = os.path.join(src_dir, '..', 'img')
        return img_dir
    
    def open_img():
        img_dir = get_img_dir()
        img_name = 'amsterdam.jpg'
        full_img_path = os.path.join(img_dir, img_name)
        img = Image.open(full_img_path)
        return img
    
    def crop_image(img, xy, scale_factor):
        '''Crop the image around the tuple xy
    
        Inputs:
        -------
        img: Image opened with PIL.Image
        xy: tuple with relative (x,y) position of the center of the cropped image
            x and y shall be between 0 and 1
        scale_factor: the ratio between the original image's size and the cropped image's size
        '''
        center = (img.size[0] * xy[0], img.size[1] * xy[1])
        new_size = (img.size[0] / scale_factor, img.size[1] / scale_factor)
        left = max (0, (int) (center[0] - new_size[0] / 2))
        right = min (img.size[0], (int) (center[0] + new_size[0] / 2))
        upper = max (0, (int) (center[1] - new_size[1] / 2))
        lower = min (img.size[1], (int) (center[1] + new_size[1] / 2))
        cropped_img = img.crop((left, upper, right, lower))
        return cropped_img
    
    def save_img(img, img_name):
        img_dir = get_img_dir()
        full_img_path = os.path.join(img_dir, img_name)
        img.save(full_img_path)
    
    if __name__ == '__main__':
        ams = open_img()
    
        crop_ams = crop_image(ams, (0.50, 0.50), 0.95)
        save_img(crop_ams, 'crop_amsterdam_01.jpg')
    
        crop_ams = crop_image(ams, (0.25, 0.25), 2.5)
        save_img(crop_ams, 'crop_amsterdam_02.jpg')
    
        crop_ams = crop_image(ams, (0.75, 0.45), 3.5)
        save_img(crop_ams, 'crop_amsterdam_03.jpg')
    

    Original image: amsterdam.jpg

    crop_amsterdam_01.jpg: crop_amsterdam_01.jpg

    crop_amsterdam_02.jpg: crop_amsterdam_02.jpg

    crop_amsterdam_03.jpg: crop_amsterdam_03.jpg