|
#!/usr/bin/env python3 |
|
|
|
import os |
|
import sys |
|
from PIL import Image |
|
|
|
|
|
def pad_image( |
|
input_path: str, ratio_str: str, min_padding_percent: float, output_path: str |
|
): |
|
""" |
|
Pad a single image to match a given aspect ratio, ensuring |
|
at least 'min_padding_percent' of the largest original dimension |
|
is added as padding on each side. PNG images are padded with |
|
transparent pixels; other formats are padded with white. |
|
""" |
|
|
|
# Parse the aspect ratio, e.g. '4:3' -> w_ratio=4, h_ratio=3 |
|
w_ratio_str, h_ratio_str = ratio_str.split(":") |
|
w_ratio = float(w_ratio_str) |
|
h_ratio = float(h_ratio_str) |
|
|
|
# Open the original image |
|
with Image.open(input_path) as im: |
|
original_mode = im.mode |
|
original_width, original_height = im.size |
|
|
|
# Determine required padding in pixels based on the longest edge |
|
longest_edge = max(original_width, original_height) |
|
min_pad = int(round((longest_edge * min_padding_percent) / 100.0)) |
|
|
|
# Minimum required final dimensions: |
|
min_final_width = original_width + 2 * min_pad |
|
min_final_height = original_height + 2 * min_pad |
|
|
|
# The final image must have an aspect ratio = w_ratio / h_ratio |
|
# We find the smallest W x H that satisfies: |
|
# W / H = (w_ratio / h_ratio), |
|
# W >= min_final_width, |
|
# H >= min_final_height. |
|
|
|
# Start by matching min_final_width |
|
final_width = min_final_width |
|
# Derive final_height from ratio |
|
final_height = int(round(final_width * (h_ratio / w_ratio))) |
|
|
|
# If final_height is too small, bump it up and recalc final_width |
|
if final_height < min_final_height: |
|
final_height = min_final_height |
|
final_width = int(round(final_height * (w_ratio / h_ratio))) |
|
|
|
# Decide on the background: |
|
# If the output format will be PNG, we can preserve transparency; |
|
# otherwise we'll use white. |
|
ext = os.path.splitext(input_path)[1].lower() |
|
if ext in (".png", ".gif", ".webp"): |
|
# RGBA with alpha channel for transparent padding |
|
mode = "RGBA" |
|
background_color = (0, 0, 0, 0) # fully transparent |
|
else: |
|
# RGB with white background |
|
mode = "RGB" |
|
background_color = (255, 255, 255) |
|
|
|
# Create new image for the padded result |
|
new_im = Image.new(mode, (final_width, final_height), background_color) |
|
|
|
# Compute top-left corner at which to paste the original |
|
x_offset = (final_width - original_width) // 2 |
|
y_offset = (final_height - original_height) // 2 |
|
|
|
# Convert original if needed (e.g., if new_im is RGBA and original is RGB) |
|
if mode == "RGBA" and im.mode != "RGBA": |
|
im = im.convert("RGBA") |
|
elif mode == "RGB" and im.mode not in ("RGB", "L"): |
|
im = im.convert("RGB") |
|
|
|
# Paste original image into the padded (centered) area |
|
new_im.paste(im, (x_offset, y_offset)) |
|
|
|
# Save the image to the output path |
|
new_im.save(output_path) |
|
|
|
|
|
def main(): |
|
if len(sys.argv) != 5: |
|
print( |
|
"Usage: python pad_images.py <input_dir> <aspect_ratio> <min_padding_percent> <output_dir>" |
|
) |
|
sys.exit(1) |
|
|
|
input_dir = sys.argv[1] |
|
aspect_ratio = sys.argv[2] # e.g. '4:3' |
|
min_padding = float(sys.argv[3]) # e.g. 10 |
|
output_dir = sys.argv[4] |
|
|
|
# Create output directory if necessary |
|
if not os.path.exists(output_dir): |
|
os.makedirs(output_dir) |
|
|
|
# Loop over all files in the input directory |
|
for filename in os.listdir(input_dir): |
|
# Only consider files with typical image extensions |
|
if filename.lower().endswith( |
|
(".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp") |
|
): |
|
input_path = os.path.join(input_dir, filename) |
|
output_path = os.path.join(output_dir, filename) |
|
try: |
|
pad_image(input_path, aspect_ratio, min_padding, output_path) |
|
print(f"Padded image saved: {output_path}") |
|
except Exception as e: |
|
print(f"Error processing {input_path}: {e}") |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |