Title: Resize an image so it has desired margins in Python and PIL
I'm writing a new book and I'll probably have to self-publish it, so I need to handle little details like making the pictures look nice. In particular, I want all of the pictures to have the same size margin. Rather than carefully (and tediously) resizing every picture by hand, I decided to write this program.
Use the File menu's Open command to load an image file. Use the Margin text box to indicate how big you want the margins to be.
Set the Cutoff value to the minimum brightness that that represents a background color. The program looks at the sum of a pixel's red, green, and blue components and compares it to this value to determine whether the pixel is part of the background. In this example, I use the value 765 = 3 × 255 so only pure white pixels are considered part of the background. (I take screen shots above a white background so this works for me. If you some other background color, or perhaps an alpha value of 0, you'll need to change the logic.)
After you have the margins adjusted the way you want them, use the File menu's Save As command to save the result.
In addition to handling a bunch of user interface drudgery, the program performs two main tasks: giving the image margins and displaying the result.
Giving the Image Margins
When you load an image or change the Margin or Cutoff value, the program gets the Margin and Cutoff values, and then calls the following set_image_margins method to
give the image margins.
def set_image_margins(image, cutoff, margin):
'''Size the image so it has margins of the indicated size.'''
# Convert to RGB mode.
image = image.convert('RGB')
# Load the pixel data.
pixels = image.load()
# Find the non-white pixel bounds.
xmax = 0
xmin = image.width
ymax = 0
ymin = image.height
for y in range(image.height):
for x in range(image.width):
r, g, b = pixels[x, y]
if r + g + b < cutoff:
if x < xmin: xmin = x
if x > xmax: xmax = x
if y < ymin: ymin = y
if y > ymax: ymax = y
# Crop to include just the bounding area.
image = image.crop((xmin, ymin, xmax + 1, ymax + 1))
# Make the new image.
wid = 2 * margin + image.width
hgt = 2 * margin + image.height
new_image = Image.new('RGB', (wid, hgt), 'white')
# Paste the cropped image onto the new image.
new_image.paste(image, (margin, margin))
# Return the result.
return new_image
The function first converts the image to RGB mode in case it's something else. It then calls the image's load method so it can work with the image's pixels more quickly.
Next, the code loops through the image's pixels to find the minimum and maximum coordinates where pixels are not part of the background. The function crops the image so it just barely shows all of the non-background pixels.
The code then creates a new image (with the clever name new_image) that is the size of the cropped image plus the desired margins. It pastes the cropped image onto the new image with its upper left corner positioned at (margin, margin) so it is centered and surrounded by the margins.
The code then returns the result.
Displaying the Result
The program displays the marginated image so you can get a feel for what it looks like. There's really no need for you to see the image at full scale, however, so the program sizes the image to fit on the window. The following resize_image function resizes the image without distorting it.
def resize_image(image, wid, hgt):
'''Resize the image without distorting it.'''
# Calculate the image's new size.
image_wid = image.width
image_hgt = image.height
if wid / hgt > image_wid / image_hgt:
# The image is too wide and short. Make it skinnier.
wid = int(image_wid / image_hgt * hgt)
else:
# The image is too tall and thin. Make it shorter.
hgt = int(wid / (image_wid / image_hgt))
# Resize the image.
return image.resize((wid, hgt))
The function first gets the image's current width and height. It then compares the aspect ratios (width to height ratio) of the desired size and the image's current size.
If the desired size is too short and wide, the code calculates a new desired width to make the area skinnier so it matches the image's aspect ratio. Similarly, if the desired size is too tall and thin, the code calculates a new height to make the area shorter so it matches the image's aspect ratio.
After it has adjusted the desired width and height, the code calls the image's resize method to make it fit the desired size. It then returns the resized result.
Conclusion
The program performs the usual amount of tkinter toil, but the set_image_margins and resize_image methods do all of the interesting work.
For my book, I'm setting the image margins to 10 pixels. I'm also using MS Word to add additional spacing around the images as needed.
Download the example to experiment with it and to see additional details.
|