52

I have a large number of images of a fixed size (say 500*500). I want to write a python script which will resize them to a fixed size (say 800*800) but will keep the original image at the center and fill the excess area with a fixed color (say black).

I am using PIL. I can resize the image using the resize function now, but that changes the aspect ratio. Is there any way to do this?

Nihar Sarangi
  • 4,845
  • 8
  • 27
  • 32

8 Answers8

69

You can create a new image with the desired new size, and paste the old image in the center, then saving it. If you want, you can overwrite the original image (are you sure? ;o)

import Image

old_im = Image.open('someimage.jpg')
old_size = old_im.size

new_size = (800, 800)
new_im = Image.new("RGB", new_size)   ## luckily, this is already black!
box = tuple((n - o) // 2 for n, o in zip(new_size, old_size))
new_im.paste(old_im, box)

new_im.show()
# new_im.save('someimage.jpg')

You can also set the color of the new border with a third argument of Image.new() (for example: Image.new("RGB", new_size, "White"))

Jean-Francois T.
  • 11,549
  • 7
  • 68
  • 107
heltonbiker
  • 26,657
  • 28
  • 137
  • 252
  • Hi @heltonbiker, can you explain this code to me: ((new_size[0]-old_size[0])/2, (new_size[1]-old_size[1])/2)) – timman May 15 '20 at 12:11
  • `new_size[0]-old_size[0]` is the difference between both sizes. If you device that by two, you have the left padding (and you get an equivalent padding on the right side). – WhyNotHugo Jul 11 '20 at 02:53
  • ``tuple((n - o) // 2 for n, o in zip(new_size, old_size))`` is genius I've been staring at that line for the past 7 minutes. Thanks so much for this! – nchopra Jan 09 '23 at 10:11
59

Yes, there is.

Make something like this:

from PIL import Image, ImageOps
ImageOps.expand(Image.open('original-image.png'),border=300,fill='black').save('imaged-with-border.png')

You can write the same at several lines:

from PIL import Image, ImageOps
img = Image.open('original-image.png')
img_with_border = ImageOps.expand(img,border=300,fill='black')
img_with_border.save('imaged-with-border.png')

And you say that you have a list of images. Then you must use a cycle to process all of them:

from PIL import Image, ImageOps
for i in list-of-images:
  img = Image.open(i)
  img_with_border = ImageOps.expand(img,border=300,fill='black')
  img_with_border.save('bordered-%s' % i)
Sean Mooney
  • 169
  • 1
  • 1
  • 11
Igor Chubin
  • 61,765
  • 13
  • 122
  • 144
  • 3
    Interesting. Would it be possible to choose different borders for top-bottom and left-right, so the new image size can be the parameter, instead of the border size? – heltonbiker Jun 21 '12 at 17:01
  • Thanks, can I make the x and y values of the border different... something like say (100,50)? – Nihar Sarangi Jun 21 '12 at 17:02
  • 7
    okay found a work around, `ImageOps.expand(Image.open('original-image.png'),border=(300,500),fill='black').save('imaged-with-border.png') ` – Nihar Sarangi Jun 21 '12 at 17:04
  • 2
    ImageOps.expand seems to work in most situations, though it's been giving me trouble on large images (5500x5500 pixels). For some reason the images come out greyscale, while the exact same code running on smaller images works perfectly. Given that, I posted an answer below about using Image.crop, which doesn't seem to have the same limitation/bug. – kevinmicke Oct 13 '16 at 00:53
26

Alternatively, if you are using OpenCV, they have a function called copyMakeBorder that allows you to add padding to any of the sides of an image. Beyond solid colors, they've also got some cool options for fancy borders like reflecting or extending the image.

import cv2

img = cv2.imread('image.jpg')

color = [101, 52, 152] # 'cause purple!

# border widths; I set them all to 150
top, bottom, left, right = [150]*4

img_with_border = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)

Example results of cv2.copyMakeBorder function

Sources: OpenCV border tutorial and OpenCV 3.1.0 Docs for copyMakeBorder

jdhao
  • 24,001
  • 18
  • 134
  • 273
pirt
  • 1,153
  • 13
  • 21
  • 3
    I think you should add `cv2.BORDER_CONSTANT` before defining `value=color` as in http://docs.opencv.org/3.1.0/d3/df2/tutorial_py_basic_ops.html – Guillem Cucurull Feb 24 '17 at 11:38
  • In order to reproduce the last picture, where the pixels at the border of the image are replicated, you can just use `cv2.BORDER_REPLICATE` as border type and you don't need to specify color value, like this: `img_with_border = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_REPLICATE)` – tsveti_iko Jan 16 '19 at 11:07
5

PIL's crop method can actually handle this for you by using numbers that are outside the bounding box of the original image, though it's not explicitly stated in the documentation. Negative numbers for left and top will add black pixels to those edges, while numbers greater than the original width and height for right and bottom will add black pixels to those edges.

This code accounts for odd pixel sizes:

from PIL import Image

with Image.open('/path/to/image.gif') as im:
    old_size = im.size
    new_size = (800, 800)

    if new_size > old_size:
        # Set number of pixels to expand to the left, top, right,
        # and bottom, making sure to account for even or odd numbers
        if old_size[0] % 2 == 0:
            add_left = add_right = (new_size[0] - old_size[0]) // 2
        else:
            add_left = (new_size[0] - old_size[0]) // 2
            add_right = ((new_size[0] - old_size[0]) // 2) + 1

        if old_size[1] % 2 == 0:
            add_top = add_bottom = (new_size[1] - old_size[1]) // 2
        else:
            add_top = (new_size[1] - old_size[1]) // 2
            add_bottom = ((new_size[1] - old_size[1]) // 2) + 1

        left = 0 - add_left
        top = 0 - add_top
        right = old_size[0] + add_right
        bottom = old_size[1] + add_bottom

        # By default, the added pixels are black
        im = im.crop((left, top, right, bottom))

Instead of the 4-tuple, you could instead use a 2-tuple to add the same number of pixels on the left/right and top/bottom, or a 1-tuple to add the same number of pixels to all sides.

kevinmicke
  • 5,038
  • 2
  • 20
  • 22
2

It is important to consider old dimension, new dimension and their difference here. If the difference is odd (not even), you will need to specify slightly different values for left, top, right and bottom borders.

Assume the old dimension is ow,oh and new one is nw,nh. So, this would be the answer:

import Image, ImageOps
img = Image.open('original-image.png')
deltaw=nw-ow
deltah=nh-oh
ltrb_border=(deltaw/2,deltah/2,deltaw-(deltaw/2),deltah-(deltah/2))
img_with_border = ImageOps.expand(img,border=ltrb_border,fill='black')
img_with_border.save('imaged-with-border.png')
RF Vision
  • 21
  • 3
1

You can load the image with scipy.misc.imread as a numpy array. Then create an array with the desired background with numpy.zeros((height, width, channels)) and paste the image at the desired location:

import numpy as np
import scipy.misc

im = scipy.misc.imread('foo.jpg', mode='RGB')
height, width, channels = im.shape

# make canvas
im_bg = np.zeros((height, width, channels))
im_bg = (im_bg + 1) * 255  # e.g., make it white

# Your work: Compute where it should be
pad_left = ...
pad_top = ...

im_bg[pad_top:pad_top + height,
      pad_left:pad_left + width,
      :] = im
# im_bg is now the image with the background.
Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
  • 1
    Shouldn't im_bg be something like `(height+(2*pad_top), width+(2*pad_left), channels)` otherwise the target image is the same size as the source image so there is no space for the border? – user1676300 Jan 15 '20 at 12:41
0
ximg = Image.open(qpath)
xwid,xhgt = func_ResizeImage(ximg)
qpanel_3 = tk.Frame(Body,width=xwid+10,height=xhgt+10,bg='white',bd=5)
ximg = ximg.resize((xwid,xhgt),Image.ANTIALIAS) 
ximg = ImageTk.PhotoImage(ximg) 
panel = tk.Label(qpanel_3,image=ximg)     
panel.image = ximg 
panel.grid(row = 2)
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
0
from PIL import Image
from PIL import ImageOps
img = Image.open("dem.jpg").convert("RGB")

This part will add black borders at the sides (10% of width)

img_side = ImageOps.expand(img, border=(int(0.1*img.size[0]),0,int(0.1*img.size[0]),0), fill=(0,0,0))
img_side.save("sunset-sides.jpg") 

This part will add black borders at the bottom & top (10% of height)

img_updown = ImageOps.expand(img, border=(0,int(0.1*img.size[1]),0,int(0.1*img.size[1])), fill=(0,0,0))
img_updown.save("sunset-top_bottom.jpg")

This part will add black borders at the bottom,top & sides (10% of height-width)

img_updown_side = ImageOps.expand(img, border=(int(0.1*img.size[0]),int(0.1*img.size[1]),int(0.1*img.size[0]),int(0.1*img.size[1])), fill=(0,0,0))
img_updown_side.save("sunset-all_sides.jpg")
img.close()
img_side.close()
img_updown.close()
img_updown_side.close()
Prajot Kuvalekar
  • 5,128
  • 3
  • 21
  • 32