diff --git a/Tests/images/imageops_pad_h_0.jpg b/Tests/images/imageops_pad_h_0.jpg new file mode 100644 index 000000000..f9fcb1cdb Binary files /dev/null and b/Tests/images/imageops_pad_h_0.jpg differ diff --git a/Tests/images/imageops_pad_h_1.jpg b/Tests/images/imageops_pad_h_1.jpg new file mode 100644 index 000000000..4b9b9ebc4 Binary files /dev/null and b/Tests/images/imageops_pad_h_1.jpg differ diff --git a/Tests/images/imageops_pad_h_2.jpg b/Tests/images/imageops_pad_h_2.jpg new file mode 100644 index 000000000..2c8224892 Binary files /dev/null and b/Tests/images/imageops_pad_h_2.jpg differ diff --git a/Tests/images/imageops_pad_v_0.jpg b/Tests/images/imageops_pad_v_0.jpg new file mode 100644 index 000000000..caf435796 Binary files /dev/null and b/Tests/images/imageops_pad_v_0.jpg differ diff --git a/Tests/images/imageops_pad_v_1.jpg b/Tests/images/imageops_pad_v_1.jpg new file mode 100644 index 000000000..4a6698e91 Binary files /dev/null and b/Tests/images/imageops_pad_v_1.jpg differ diff --git a/Tests/images/imageops_pad_v_2.jpg b/Tests/images/imageops_pad_v_2.jpg new file mode 100644 index 000000000..792952bcd Binary files /dev/null and b/Tests/images/imageops_pad_v_2.jpg differ diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 9c4da2463..ce8a6629d 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -24,6 +24,9 @@ class TestImageOps(PillowTestCase): ImageOps.colorize(hopper("L"), (0, 0, 0), (255, 255, 255)) ImageOps.colorize(hopper("L"), "black", "white") + ImageOps.pad(hopper("L"), (128, 128)) + ImageOps.pad(hopper("RGB"), (128, 128)) + ImageOps.crop(hopper("L"), 1) ImageOps.crop(hopper("RGB"), 1) @@ -70,6 +73,27 @@ class TestImageOps(PillowTestCase): newimg = ImageOps.fit(hopper("RGB").resize((100, 1)), (35, 35)) self.assertEqual(newimg.size, (35, 35)) + def test_pad(self): + # Same ratio + im = hopper() + new_size = (im.width * 2, im.height * 2) + new_im = ImageOps.pad(im, new_size) + self.assertEqual(new_im.size, new_size) + + for label, color, new_size in [ + ("h", None, (im.width * 4, im.height * 2)), + ("v", "#f00", (im.width * 2, im.height * 4)) + ]: + for i, centering in enumerate([(0,0), (0.5, 0.5), (1, 1)]): + new_im = ImageOps.pad(im, new_size, + color=color, centering=centering) + self.assertEqual(new_im.size, new_size) + + target = Image.open( + "Tests/images/imageops_pad_"+label+"_"+str(i)+".jpg") + self.assert_image_similar(new_im, target, 6) + + def test_pil163(self): # Division by zero in equalize if < 255 pixels in image (@PIL163) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 9b470062a..9f516bac1 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -221,6 +221,50 @@ def colorize(image, black, white, mid=None, blackpoint=0, return _lut(image, red + green + blue) +def pad(image, size, method=Image.NEAREST, color=None, centering=(0.5, 0.5)): + """ + Returns a sized and padded version of the image, expanded to fill the + requested aspect ratio and size. + + :param image: The image to size and crop. + :param size: The requested output size in pixels, given as a + (width, height) tuple. + :param method: What resampling method to use. Default is + :py:attr:`PIL.Image.NEAREST`. + :param color: The background color of the padded image. + :param centering: Control the position of the original image within the + padded version. + (0.5, 0.5) will keep the image centered + (0, 0) will keep the image aligned to the top left + (1, 1) will keep the image aligned to the bottom + right + :return: An image. + """ + + im_ratio = image.width / image.height + dest_ratio = float(size[0]) / size[1] + + if im_ratio == dest_ratio: + out = image.resize(size, resample=method) + else: + out = Image.new(image.mode, size, color) + if im_ratio > dest_ratio: + new_height = int(image.height / image.width * size[0]) + if new_height != size[1]: + image = image.resize((size[0], new_height), resample=method) + + y = int((size[1] - new_height) * max(0, min(centering[1], 1))) + out.paste(image, (0, y)) + else: + new_width = int(image.width / image.height * size[1]) + if new_width != size[0]: + image = image.resize((new_width, size[1]), resample=method) + + x = int((size[0] - new_width) * max(0, min(centering[0], 1))) + out.paste(image, (x, 0)) + return out + + def crop(image, border=0): """ Remove border from image. The same amount of pixels are removed