13

How to cut off the blank border area of a PNG image and shrink it to its minimum size using Python?

Tim Sylvester
  • 22,413
  • 2
  • 74
  • 92
jack
  • 16,231
  • 34
  • 99
  • 122

7 Answers7

35

PIL's getbbox is working for me

im.getbbox() => 4-tuple or None

Calculates the bounding box of the non-zero regions in the image. The bounding box is returned as a 4-tuple defining the left, upper, right, and lower pixel coordinate. If the image is completely empty, this method returns None.

Code Sample that I tried, I have tested with bmp, but it should work for png too.

import Image
im = Image.open("test.bmp")
im.size  # (364, 471)
im.getbbox()  # (64, 89, 278, 267)
im2 = im.crop(im.getbbox())
im2.size  # (214, 178)
im2.save("test2.bmp")
Csa77
  • 559
  • 10
  • 19
YOU
  • 114,140
  • 31
  • 183
  • 213
  • It seems it does not work for PNG files (made with MSPaint for example). Maybe because of transparence or something else? – Basj May 22 '20 at 08:14
5

I had the same problem today. Here is my solution to crop the transparent borders. Just throw this script in your folder with your batch .png files:

from PIL import Image
import numpy as np
from os import listdir

def crop(png_image_name):
    pil_image = Image.open(png_image_name)
    np_array = np.array(pil_image)
    blank_px = [255, 255, 255, 0]
    mask = np_array != blank_px
    coords = np.argwhere(mask)
    x0, y0, z0 = coords.min(axis=0)
    x1, y1, z1 = coords.max(axis=0) + 1
    cropped_box = np_array[x0:x1, y0:y1, z0:z1]
    pil_image = Image.fromarray(cropped_box, 'RGBA')
    print(pil_image.width, pil_image.height)
    pil_image.save(png_image_name)
    print(png_image_name)

for f in listdir('.'):
    if f.endswith('.png'):
        crop(f)
5

Here is ready-to-use solution:

import numpy as np
from PIL import Image

def bbox(im):
    a = np.array(im)[:,:,:3]  # keep RGB only
    m = np.any(a != [255, 255, 255], axis=2)
    coords = np.argwhere(m)
    y0, x0, y1, x1 = *np.min(coords, axis=0), *np.max(coords, axis=0)
    return (x0, y0, x1+1, y1+1)

im = Image.open('test.png')
print(bbox(im))  # (33, 12, 223, 80)
im2 = im.crop(bbox(im))
im2.save('test_cropped.png')

Example input (download link if you want to try):

enter image description here

Output:

enter image description here

Basj
  • 36,818
  • 81
  • 313
  • 561
3

https://gist.github.com/3141140

import Image
import sys
import glob

# Trim all png images with alpha in a folder
# Usage "python PNGAlphaTrim.py ../someFolder"

try:
    folderName = sys.argv[1]
except :
    print "Usage: python PNGPNGAlphaTrim.py ../someFolder"
    sys.exit(1)

filePaths = glob.glob(folderName + "/*.png") #search for all png images in the folder

for filePath in filePaths:
    image=Image.open(filePath)
    image.load()

    imageSize = image.size
    imageBox = image.getbbox()

    imageComponents = image.split()

    if len(imageComponents) < 4: continue #don't process images without alpha

    rgbImage = Image.new("RGB", imageSize, (0,0,0))
    rgbImage.paste(image, mask=imageComponents[3])
    croppedBox = rgbImage.getbbox()

    if imageBox != croppedBox:
        cropped=image.crop(croppedBox)
        print filePath, "Size:", imageSize, "New Size:",croppedBox
        cropped.save(filePath)
noj
  • 139
  • 1
  • 5
0

You can use PIL to find rows and cols of your image that are made up purely of your border color.

Using this information, you can easily determine the extents of the inlaid image.

PIL again will then allow you to crop the image to remove the border.

Frank Krueger
  • 67,384
  • 46
  • 159
  • 207
0

I think it's necessary to supplement @Frank Krueger's answer. He makes a good point, but it doesn't include how to properly crop extra border color out of an image. I found that here. Specifically, I found this useful:

from PIL import Image, ImageChops

def trim(im):
    bg = Image.new(im.mode, im.size, im.getpixel((0,0)))
    diff = ImageChops.difference(im, bg)
    diff = ImageChops.add(diff, diff, 2.0, -100)
    bbox = diff.getbbox()
    if bbox:
        return im.crop(bbox)

im = Image.open("bord3.jpg")
im = trim(im)
im.show()
Community
  • 1
  • 1
AaronJPung
  • 897
  • 1
  • 15
  • 30
0

The other answers did not work for me while writing a Blender script (cannot use PIL), so maybe someone else will find this useful.

import numpy as np

def crop(crop_file):
    """crop the image, removing invisible borders"""
    image = bpy.data.images.load(crop_file, check_existing=False)
    w, h = image.size

    print("Original size: " + str(w) + " x " + str(h))

    linear_pixels = image.pixels[:]
    pixels4d = np.reshape(linear_pixels, (h, w, 4))
    
    mask = pixels4d [:,:,3] != 0.
    coords = np.argwhere(mask)
    y0, x0 = coords.min(axis=0)
    y1, x1 = coords.max(axis=0) + 1
    cropped_box = pixels4d[y0:y1, x0:x1, :]
    
    w1, h1 = x1 - x0, y1 - y0
    print("Crop size: " + str(w1) + " x " + str(h1))
    
    temp_image = bpy.data.images.new(crop_file, alpha=True, width=w1, height=h1)
    temp_image.pixels[:] = cropped_box.ravel()
    temp_image.filepath_raw = crop_file
    temp_image.file_format = 'PNG'
    temp_image.alpha_mode = 'STRAIGHT'
    temp_image.save()