imager / image_processor.py
woodmastr's picture
Upload 2 files
386603a verified
raw
history blame
10.4 kB
import argparse
import os
import shutil
from rembg import remove
from PIL import Image
def add_background(image_path, background, output_path, default_color="#FFFFFF"):
"""
Adds a background to an image, with a fallback to a default color if the specified color is not available.
Args:
- image_path (str): Path to the input image with transparent background.
- background (str): Background color (as a name or hex code) or path to a background image file.
- output_path (str): Path where the image with the new background should be saved.
- default_color (str): Fallback color if the specified background color is not valid. Defaults to white.
"""
with Image.open(image_path).convert("RGBA") as foreground:
try:
# Attempt to create a background layer with the specified color or image
if background.startswith("#") or background.isalpha():
# Check if the color name is valid by creating a small test image
Image.new("RGBA", (1, 1), background)
background_layer = Image.new(
"RGBA", foreground.size, background)
else:
# If background is an image file
with Image.open(background).convert("RGBA") as bg_img:
bg_img = bg_img.resize(foreground.size)
background_layer = bg_img
except ValueError:
# If the color is invalid, use the default color
print(
f"Invalid color '{background}'. Using default color '{default_color}'.")
background_layer = Image.new(
"RGBA", foreground.size, default_color)
# Composite the foreground over the background
with Image.alpha_composite(background_layer, foreground) as final_img:
# Convert to RGB to save in formats other than PNG
final_img = final_img.convert("RGB")
final_img.save(output_path)
def autocrop_image(image_path, output_path):
"""
Autocrops an image, focusing on the non-transparent pixels and saves as PNG.
Args:
- image_path (str): Path to the input image.
- output_path (str): Path where the cropped image should be saved.
"""
with Image.open(image_path).convert("RGBA") as image:
bbox = image.getbbox()
if bbox:
cropped_image = image.crop(bbox)
cropped_image.save(output_path, format='PNG')
else:
image.save(output_path, format='PNG')
def process_image(input_path, output_path, crop=False, remove_bg=False, resize=None, padding=0, background=None):
"""
Processes a single image based on the provided options and saves it.
Args:
- input_path (str): Path to the input image.
- output_path (str): Path where the processed image should be saved.
- crop (bool): Whether to autocrop the image.
- remove_bg (bool): Whether to remove the background of the image.
- resize (tuple): Optional dimensions (width, height) to resize the image.
- padding (int): Number of padding pixels to add around the image.
- background (str): Optional background color (hex code or name) or path to an image file to set as the background.
"""
need_processing = crop or resize or remove_bg or background
temp_path = output_path + ".tmp.png"
if remove_bg:
with open(input_path, 'rb') as input_file:
image_data = input_file.read()
image_data = remove(image_data)
with open(temp_path, 'wb') as temp_file:
temp_file.write(image_data)
else:
# Copy original image to temp_path if no background removal
shutil.copy(input_path, temp_path)
if crop:
autocrop_image(temp_path, temp_path)
if resize:
# adjusted_resize = (resize[0],
# resize[1]) if padding else resize
resize_and_pad_image(temp_path, temp_path, resize, padding)
if background:
add_background(temp_path, background, temp_path)
# Finalize the process: move from temp_path to output_path
os.rename(temp_path, output_path)
def resize_and_pad_image(image_path, output_path, dimensions, padding=0):
"""
Resizes an image to fit within specified dimensions (AxB) and adds padding to make it exactly AxB,
ensuring the image content is centered within these dimensions.
Args:
- image_path (str): Path to the input image.
- output_path (str): Path where the resized and padded image should be saved.
- dimensions (tuple): Target dimensions (width, height) in pixels, before adding padding.
- padding (int): Number of padding pixels to add around the image.
"""
target_width, target_height = dimensions
content_width, content_height = target_width - \
2*padding, target_height - 2*padding
with Image.open(image_path) as img:
# Resize the image, preserving the aspect ratio
img.thumbnail((content_width, content_height),
Image.Resampling.LANCZOS)
# Create a new image with the target dimensions and a transparent background
# new image shall include padding spacig
new_img = Image.new("RGBA", dimensions, (255, 255, 255, 0))
# Calculate the position to paste the resized image to center it
paste_position = ((target_width - img.width) // 2,
(target_height - img.height) // 2)
# Paste the resized image onto the new image, centered
new_img.paste(img, paste_position, img if img.mode == 'RGBA' else None)
# Save the output
new_img.save(output_path, format='PNG')
def generate_output_filename(input_path, remove_bg=False, crop=False, resize=None, background=None):
"""
Generates an output filename based on the input path and processing options applied.
Appends specific suffixes based on the operations: '_b' for background removal, '_c' for crop,
and '_bg' if a background is added. It ensures the file extension is '.png'.
Args:
- input_path (str): Path to the input image.
- remove_bg (bool): Indicates if background removal was applied.
- crop (bool): Indicates if autocrop was applied.
- resize (tuple): Optional dimensions (width, height) for resizing the image.
- background (str): Indicates if a background was added (None if not used).
Returns:
- (str): Modified filename with appropriate suffix and '.png' extension.
"""
base, _ = os.path.splitext(os.path.basename(input_path))
suffix = ""
if remove_bg:
suffix += "_b"
if crop:
suffix += "_c"
if resize:
width, height = resize
suffix += f"_{width}x{height}"
if background:
suffix += "_bg" # Append "_bg" if the background option was used
# Ensure the file saves as PNG, accommodating for transparency or added backgrounds
return f"{base}{suffix}.png"
def generate_output_filename2(input_path, remove_bg=False, crop=False, resize=None, padding=0, background=None):
"""
Generates an output filename based on the input path and processing options applied.
Takes into account the effect of padding on the final image size for naming.
"""
base, _ = os.path.splitext(os.path.basename(input_path))
suffix = ""
if remove_bg:
suffix += "_b"
if crop:
suffix += "_c"
if resize:
# Adjust the resize dimensions to reflect the final image size after padding
if padding > 0:
# Adjust dimensions to reflect content size before padding if that's the intent
# Otherwise, add padding to the dimensions to reflect final size including padding
adjusted_width = resize[0] + 2*padding
adjusted_height = resize[1] + 2*padding
suffix += f"_{adjusted_width}x{adjusted_height}"
else:
width, height = resize
suffix += f"_{width}x{height}"
if background:
suffix += "_bg"
return f"{base}{suffix}.png"
# The main and process_images functions remain the same, but ensure to update them to handle the new PNG output correctly.
# Update the process_images and main functions to include the new autocrop functionality
# Ensure to pass the crop argument to process_image and adjust the output filename generation accordingly
def process_images(input_dir="./input", output_dir="./output", crop=False, remove_bg=False, resize=None, padding=0, background=None):
"""
Processes images in the specified directory based on the provided options.
Args:
- input_dir (str): Directory containing the images to be processed.
- output_dir (str): Directory where processed images will be saved.
- crop (bool): Whether to crop the images.
- remove_bg (bool): Whether to remove the background of the images.
- resize (tuple): Optional dimensions (width, height) to resize the image.
- padding (int): Number of padding pixels to add around the image.
- background (str): Optional background color (hex code or name) or path to an image file to set as the background.
"""
processed_input_dir = os.path.join(input_dir, "processed")
os.makedirs(processed_input_dir, exist_ok=True)
os.makedirs(output_dir, exist_ok=True)
inputs = [os.path.join(input_dir, f) for f in os.listdir(
input_dir) if os.path.isfile(os.path.join(input_dir, f))]
# if images are not in the input directory, print a message and return
if not inputs:
print("No images found in the input directory.")
return
for i, input_path in enumerate(inputs, start=1):
filename = os.path.basename(input_path)
output_filename = generate_output_filename(
input_path, remove_bg=remove_bg, crop=crop, resize=resize, background=background)
output_path = os.path.join(output_dir, output_filename)
print(f"Processing image {i}/{len(inputs)}...{filename}")
# Update the call to process_image with all parameters including background
process_image(input_path, output_path, crop=crop, remove_bg=remove_bg,
resize=resize, padding=padding, background=background)
shutil.move(input_path, os.path.join(processed_input_dir, filename))
print("All images have been processed.")