Search code examples
pythonpython-imaging-librarycropimage-scaling

Scale and crop an image in python PIL without exceeding the image dimensions


I am cropping an image using python PIL. Say my image is as such:

enter image description here

This is the simple code snippet I use for cropping:

from PIL import Image
im = Image.open(image)
cropped_image = im.crop((topLeft_x, topLeft_y, bottomRight_x, bottomRight_y))
cropped_image.save("Out.jpg")

The result of this is:

enter image description here

I want to scale out this cropped image keeping the aspect ratio (proportionate width and height) same by say 20% to look something like this without exceeding the image dimensions.

enter image description here

How should I scale out the crop such that the aspect ratio is maintained while not exceeding the image boundary/ dimensions?


Solution

  • You should calculate the center of your crop and use it there on. As an example:

    crop_width = right - left
    crop_height = bottom - top
    crop_center_x = int(left + crop_width/2)
    crop_center_y = (top + crop_height/2)
    

    In this way you will obtain the (x,y) point which corresponds to the center of your crop w.r.t your original image. In that case, you will know that the maximum width for your crop would be the minimum between the center value and the outer bounds of the original image minus the center itself:

    im = Image.open("brad.jpg")
    l = 200
    t = 200
    r = 300
    b = 300
    cropped = im.crop((l, t, r, b))
    

    Which gives you:

    enter image description here

    If you want to "enlarge" it to the maximum starting from the same center, then you will have:

    max_width = min(crop_center_x, im.size[0]-crop_center_x)
    max_height = min(crop_center_y, im.size[1]-crop_center_y)
    new_l = crop_center_x - max_width
    new_t = crop_center_x - max_height
    new_r = crop_center_x + max_width
    new_b = crop_center_x + max_height
    new_crop = im.crop((new_l, new_t, new_r, new_b))
    

    which gives as a result, having the same center:

    enter image description here


    Edit

    If you want to keep the aspect ratio you should retrieve it (the ratio) before and apply the crop only if the resulting size would still fit the original image. As an example, if you want to enlarge it by 20%:

    ratio = crop_height/crop_width
    scale = 20/100
    new_width = int(crop_width + (crop_width*scale))
    
    # Here we are using the previously calculated value for max_width to 
    # determine if the new one would be too large.
    # Note that the width that we calculated here (new_width) refers to both
    # sides of the crop, while the max_width calculated previously refers to
    # one side only; same for height. Sorry for the confusion.
    if max_width < new_width/2:
        new_width = int(2*max_width)
    
    new_height = int(new_width*ratio)
    
    # Do the same for the height, update width if necessary
    if max_height < new_height/2:
        new_height = int(2*max_height)
        new_width = int(new_height/ratio)
    
    adjusted_scale = (new_width - crop_width)/crop_width
    
    if adjusted_scale != scale:
        print("Scale adjusted to: {:.2f}".format(adjusted_scale))
    
    new_l = int(crop_center_x - new_width/2)
    new_r = int(crop_center_x + new_width/2)
    new_t = int(crop_center_y - new_height/2)
    new_b = int(crop_center_y + new_height/2)
    

    Once you have the width and height values the process to get the crop is the same as above.