3
\$\begingroup\$

This code has the function of removing worthless borders from the original image and then:

  1. Generate a 1080x1080 image (Instagram default) to be used as a blurred background.
  2. Generate an image with a width of 1080 and the height adjusted accordingly to maintain the correct aspect ratio.
  3. Superimpose 2. on the blurred background (1.)

I would like a review of the quality of the methods I used to reach the final result.

Original image example:

enter image description here

Code to Review:

from PIL import Image, ImageFilter, ImageChops
import numpy
import cv2

def remove_border(file_img):
    im = Image.open(file_img)
    bg = Image.new("RGB", im.size, im.getpixel((0,0)))
    diff = ImageChops.difference(im.convert("RGB"), bg)
    diff = ImageChops.add(diff, diff, 2.0, -30)
    bbox = diff.getbbox()
    if bbox:
        return im.crop(bbox)

def resize_blur(img_blur,sizers):
    img_blur = img_blur.resize(sizers, resample=Image.Resampling.LANCZOS)
    img_blur = img_blur.filter(ImageFilter.GaussianBlur(10))
    return img_blur

def resize_width_main(img_border,size_width):
    img_width = img_border
    basewidth = size_width
    wpercent = (basewidth/float(img_width.size[0]))
    hsize = int((float(img_width.size[1])*float(wpercent)))
    img_width = img_width.resize((basewidth,hsize), Image.Resampling.LANCZOS)
    return img_width

def center_overlay(name_file,overlay,background):
    img = numpy.asarray(overlay)
    img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
    h, w = img.shape[:2]
    back = numpy.asarray(background)
    back = cv2.cvtColor(back, cv2.COLOR_RGB2BGR)
    hh, ww = back.shape[:2]
    yoff = round((hh-h)/2)
    xoff = round((ww-w)/2)
    result = back.copy()
    result[yoff:yoff+h, xoff:xoff+w] = img
    cv2.imwrite(name_file, result)

def main():
    img_border = remove_border('resized_download.png')
    img_blur = resize_blur(img_border, (1080,1080))
    img_width = resize_width_main(img_border, 1080)
    center_overlay('resized.png', img_width, img_blur)

if __name__ == '__main__':
    main()

Final image example:

enter image description here

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

After upgrading to Pillow 9.1.1: I still think your imports could use some love, because there's ambiguity between Image the submodule and Image the class. (PIL chose poorly when naming the submodule.)

By convention Numpy is imported by import numpy as np. However, I am going to suggest that you not drop down to Numpy or cv at all: instead stay in Pillow and use .paste().

Add PEP484 type hints.

I suggest that you decouple your processing methods from file I/O so that they only operate on in-memory images and arrays.

This:

if bbox:
    return im.crop(bbox)

implicitly returns None if the condition is not met, but your upper code is not written to handle that. Either just drop the if and bear the consequences, or check if bbox is None and raise.

It's important for many of your function calls such as add() that you pass arguments by name, particularly for bare-literal numbers such as scale=2, offset=-30.

img_width = img_border is confusing. First of all it's an unneeded alias, and second of all it isn't a width: it's an image ("wide image", not "image width"). I would just remove this and use the original variable.

Float promotion is automatic, so this:

int((float(img_width.size[1])*float(wpercent)))

doesn't need the float() casts. You would benefit from tuple-unpacking the size here similar to what you do elsewhere.

Suggested

from PIL import ImageChops, ImageFilter
from PIL.Image import Image, Resampling
import PIL.Image


def remove_border(image: Image) -> Image:
    bg = PIL.Image.new(mode="RGB", size=image.size, color=image.getpixel((0, 0)))
    diff = ImageChops.difference(image, bg)
    diff = ImageChops.add(image1=diff, image2=diff, scale=2, offset=-30)
    bbox = diff.getbbox()
    return image.crop(bbox)


def resize_blur(blur_img: Image, sizers: tuple[int, int]) -> Image:
    return (
        blur_img
        .resize(sizers, resample=Resampling.LANCZOS)
        .filter(ImageFilter.GaussianBlur(10))
    )


def resize_width_main(border_img: Image, size_width: int) -> Image:
    width, height = border_img.size[:2]
    w_percent = size_width / width
    h_size = round(height * w_percent)
    return border_img.resize((size_width, h_size), Resampling.LANCZOS)


def center_overlay(overlay_img: Image, blur_img: Image) -> None:
    box = [
        round((xb - xo)/2)
        for xb, xo in zip(blur_img.size, overlay_img.size)
    ][:2]
    blur_img.paste(im=overlay_img, box=box)


def main() -> None:
    image = PIL.Image.open('resized_download.png')
    border_img = remove_border(image)
    blur_img = resize_blur(border_img, (1080, 1080))
    overlay_img = resize_width_main(border_img, 1080)
    center_overlay(overlay_img, blur_img)
    blur_img.save('resized.png')


if __name__ == '__main__':
    main()
\$\endgroup\$
2
  • \$\begingroup\$ Hi @Reinderien thanks for support, about LANCZOS when i use this method i get this message: DeprecationWarning: LANCZOS is deprecated and will be removed in Pillow 10 (2023-07-01). Use Resampling.LANCZOS instead. from PIL.Image import Image, LANCZOSthat's why my code is the way you thought it was different. \$\endgroup\$ Jun 27, 2022 at 1:07
  • 1
    \$\begingroup\$ OK, that makes sense. I'm on Pillow 9.0 so that explains the difference. \$\endgroup\$
    – Reinderien
    Jun 27, 2022 at 1:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.