# -*- coding: utf-8 -*-
import os
import math
import matplotlib.pyplot as plt
from .colors_and_lines import Color
#%%============================================================================
[docs]def trim_img(
files,
white_margin=True,
pad_width=20,
pad_color='w',
inplace=False,
verbose=True,
show_old_img=False,
show_new_img=False,
forcibly_overwrite=False,
resize=False,
resize_ratio=1.0,
):
'''
Trim the margins of image file(s) on the hard drive, and (optionally)
add padded margins of a specified width and color.
Parameters
----------
files : str or list<str> or tuple<str>
A file name (as Python str) or several file names (as Python list or
tuple) to be trimmed.
white_margin : bool
Whether to treat white color as the margin to be trimmed. If ``True``,
white image margins will be trimmed. If ``False``, black image margins
will be trimmed.
pad_width : float
The amount of white margins to be padded (unit: pixels).
pad_color : str or tuple<float> or list<float>
The color of the padded margin. Valid ``pad_color`` values are color
names recognizable by matplotlib: https://matplotlib.org/tutorials/colors/colors.html
inplace : bool
Whether or not to replace the existing figure file with the trimmed
content.
verbose : bool
Whether or not to print the progress onto the console.
show_old_img : bool
Whether or not to show the old figure in the console.
show_new_img : bool
Whether or not to show the trimmed figure in the console.
forcibly_overwrite : bool
Whether or not to overwrite an image on the hard drive with the same
name. Only applicable when ``inplace`` is ``False``.
resize : bool
Whether to resize the padded image
resize_ratio : float
The image resizing ratio. It has no effect if ``resize` is false.
For example, if it's 0.5, it means resizing to 50% of the original
width and height.
'''
try:
import PIL
import PIL.ImageOps
from PIL import Image
except ImportError:
raise ImportError(
'\nPlease install PIL in order to use `trim_img()`.\n'
'Install with conda (recommended):\n'
' >>> conda install PIL\n'
'To install without conda, refer to:\n'
' http://www.pythonware.com/products/pil/'
)
if not isinstance(files,(list,tuple)):
files = [files]
pad_width = int(pad_width)
for filename in files:
if verbose:
print('Trimming %s...' % filename)
im0 = PIL.Image.open(filename) # load image
im1 = im0.convert('RGB') # convert from RGBA to RGB
if white_margin:
im1 = PIL.ImageOps.invert(im1) # invert color to have black margin
if show_old_img:
plt.imshow(im0)
plt.xticks([])
plt.yticks([])
im2 = im1.crop(im1.getbbox()) # crop the black-color margin
if white_margin:
im2 = PIL.ImageOps.invert(im2) # invert the color back
pad_color_rgb = Color(pad_color).as_rgb(normalize=False)
im3 = PIL.ImageOps.expand(im2, border=pad_width, fill=pad_color_rgb)
if resize:
new_width = im3.width
new_height = im3.height
im3 = im3.resize((new_width, new_height), Image.ANTIALIAS)
if show_new_img:
plt.imshow(im3)
plt.xticks([])
plt.yticks([])
if not inplace:
filename_without_ext, ext = os.path.splitext(filename)
new_filename_ = '%s_trimmed%s' % (filename_without_ext, ext)
if not os.path.exists(new_filename_):
im3.save(new_filename_)
if verbose:
print(' New file created: %s' % new_filename_)
else:
if forcibly_overwrite:
im3.save(new_filename_)
if verbose:
print(' Overwriting existing file: %s' % new_filename_)
else:
print(' New file is not saved, because a file with the '
'same name already exists at "%s".' % new_filename_)
else:
im3.save(filename)
if verbose:
print(' Original image file overwritten.')
#%%============================================================================
[docs]def pad_img(
files, target_aspect_ratio=1.0, pad_color='white', inplace=False,
verbose=True, show_old_img=False, show_new_img=False,
forcibly_overwrite=False, resize=False, new_width_height=(640, 480),
):
"""
Pad empty edges to images so that they meet the target aspect ratio (i.e.,
more square).
Parameters
----------
files : str or list<str> or tuple<str>
A file name (as Python str) or several file names (as Python list or
tuple) to be padded.
target_aspect_ratio : float
The target aspect ratio to convert the original image into. A value
between 0 (exclusive) and 1 (inclusive).
pad_color : str or tuple<float> or list<float>
The color of the padded margin. Valid ``pad_color`` values are color
names recognizable by matplotlib: https://matplotlib.org/tutorials/colors/colors.html
inplace : bool
Whether or not to replace the existing figure file with the padded
content.
verbose : bool
Whether or not to print the progress onto the console.
show_old_img : bool
Whether or not to show the old figure in the console.
show_new_img : bool
Whether or not to show the padded figure in the console.
forcibly_overwrite : bool
Whether or not to overwrite an image on the hard drive with the same
name. Only applicable when ``inplace`` is ``False``.
resize : bool
Whether to resize the padded image
new_width_height : (int, int)
The new image width and height. It has no effect if ``resize` is false,
and there will be an error if the provided aspect ratio doesn't match
``target_aspect_ratio``.
"""
try:
import PIL
import PIL.ImageOps
from PIL import Image
except ImportError:
raise ImportError(
'\nPlease install PIL in order to use `trim_img()`.\n'
'Install with conda (recommended):\n'
' >>> conda install PIL\n'
'To install without conda, refer to:\n'
' http://www.pythonware.com/products/pil/'
)
if target_aspect_ratio > 1.0 :
raise ValueError('`target_aspect_ratio` should be <= 1.0.')
if not isinstance(files,(list,tuple)):
files = [files]
for filename in files:
if verbose:
print('Padding %s...' % filename)
im0 = PIL.Image.open(filename) # load image
if show_old_img:
plt.imshow(im0)
plt.xticks([])
plt.yticks([])
width = im0.size[0]
height = im0.size[1]
print(f'Original image resolution: {width} x {height}')
if width >= height: # landscape layout
print('Input image is in landscape layout')
if width * target_aspect_ratio > height: # image too wide
print('Image too wide')
padded_width = width
padded_height = width * target_aspect_ratio
pad_length_single_side = int(padded_height - height) // 2
upper_left_corner_coord = (0, pad_length_single_side)
else:
print('Image too narrow')
padded_height = height
padded_width = height / target_aspect_ratio
pad_length_single_side = int(padded_width - width) // 2
upper_left_corner_coord = (pad_length_single_side, 0)
# END IF
else: # portrait layout
print('Input image is in portrait layout')
if height * target_aspect_ratio > width: # image too narrow
print('Image too narrow')
padded_height = height
padded_width = height * target_aspect_ratio
pad_length_single_side = int(padded_width - width) // 2
upper_left_corner_coord = (pad_length_single_side, 0)
else:
print('Image too wide')
padded_width = width
padded_height = width / target_aspect_ratio
pad_length_single_side = int(padded_height - height) // 2
upper_left_corner_coord = (0, pad_length_single_side)
# END IF
# END IF
new_img_size = (int(padded_width), int(padded_height))
pad_color_rgb = Color(pad_color).as_rgb(normalize=False)
im1 = PIL.Image.new('RGB', new_img_size, color=pad_color_rgb)
im1.paste(im0, box=upper_left_corner_coord)
print(f'After padding: {new_img_size}')
if resize:
new_width, new_height = new_width_height
shorter_side = min(new_width, new_height)
longer_side = max(new_width, new_height)
if not math.isclose(shorter_side / longer_side, target_aspect_ratio):
raise ValueError('The new dimension must match `target_aspect_ratio`')
im1 = im1.resize((new_width, new_height), Image.ANTIALIAS)
if show_new_img:
plt.imshow(im1)
plt.xticks([])
plt.yticks([])
if not inplace:
filename_without_ext, ext = os.path.splitext(filename)
new_filename_ = '%s_padded%s' % (filename_without_ext, ext)
if not os.path.exists(new_filename_):
im1.save(new_filename_)
if verbose:
print(' New file created: %s' % new_filename_)
else:
if forcibly_overwrite:
im1.save(new_filename_)
if verbose:
print(' Overwriting existing file: %s' % new_filename_)
else:
print(' New file is not saved, because a file with the '
'same name already exists at "%s".' % new_filename_)
else:
im1.save(filename)
if verbose:
print(' Original image file overwritten.')