Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Background color when cropping image with PIL

The good thing with PIL.crop is that if we want to crop outside of the image dimensions, it simply works with:

from PIL import Image
img = Image.open("test.jpg")
img.crop((-10, -20, 1000, 500)).save("output.jpg")

Question: how to change the background color of the added region to white (default: black)?

enter image description here

Note:

  • if possible, I'd like to keep crop, and avoid to have to create a new image and paste the cropped image there like in Crop image, change black area if not not enough photo region in white.

  • Here is a download link to original image: https://i.stack.imgur.com/gtA70.jpg (303x341 pixels)

like image 825
Basj Avatar asked May 25 '18 11:05

Basj


2 Answers

I think it is not possible with one function call due to the relevant C function which seems to zero-out the destination image memory region (see it here: https://github.com/python-pillow/Pillow/blob/master/src/libImaging/Crop.c#L47)

You mentioned as not interested to create new Image and copy over it but i am pasting that kind of solution anyway for reference:

from PIL import Image
img = Image.open("test.jpg")
x1, y1, x2, y2 = -10, -20, 1000, 500  # cropping coordinates
bg = Image.new('RGB', (x2 - x1, y2 - y1), (255, 255, 255))
bg.paste(img, (-x1, -y1))
bg.save("output.jpg")

Output:

enter image description here

like image 108
sardok Avatar answered Sep 19 '22 15:09

sardok


You can do what you intend to after using the expand() function available in ImageOps module of PIL.

from PIL import Image
from PIL import ImageOps
filename = 'C:/Users/Desktop/Maine_Coon_263.jpg'
img = Image.open(filename)

val = 10    #--- pixels to be cropped

#--- a new image with a border of 10 pixels on all sides
#--- also notice fill takes in the color of white as (255, 255, 255)
new_img = ImageOps.expand(img, border = val, fill = (255, 255, 255))

#--- cropping the image above will not result in any black portion
cropped = new_img.crop((val, val, 150, 150))

The crop() function only takes one parameter of how much portion has to be cropped. There is no functionality to handle the situation when a negative value is passed in. Hence upon passing a negative value the image gets padded in black pixels.

Using the expand() function you can set the color of your choice and then go ahead and crop as you wish.

EDIT

In response to your edit, I have something rather naïve in mind but it works.

  • Get the absolute values of all the values to be cropped. You can use numpy.abs().
  • Next the maximum among these values using numpy.max().
  • Finally expand the image using this value and crop accordingly.

This code will help you:

#--- Consider these values in a tuple that are to crop your image 
crop_vals = (-10, -20, 1000, 500)

#--- get maximum value after obtaining the absolute of each
max_val = np.max(np.abs(crop_vals))

#--- add border to the image using this maximum value and crop
new_img = ImageOps.expand(img, border = max_val, fill = (255, 255, 255))
cropped = new_img.crop((max_val - 10, max_val - 20, new_img.size[0], new_img.size[1]))
like image 21
Jeru Luke Avatar answered Sep 19 '22 15:09

Jeru Luke