Upload 2 files
Browse files- gradio_imager.py +60 -0
- image_processor.py +244 -0
gradio_imager.py
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from PIL import Image
|
3 |
+
import tempfile
|
4 |
+
import os
|
5 |
+
from image_processor import process_image
|
6 |
+
|
7 |
+
|
8 |
+
def gradio_interface(image, crop, remove_bg, resize, padding, background):
|
9 |
+
# Convert resize string to tuple (if provided)
|
10 |
+
resize_dimensions = None
|
11 |
+
if resize:
|
12 |
+
try:
|
13 |
+
width, height = map(int, resize.split('x'))
|
14 |
+
resize_dimensions = (width, height)
|
15 |
+
except ValueError:
|
16 |
+
return "Invalid format for resize dimensions. Please use 'AxB'.", "original"
|
17 |
+
|
18 |
+
# Use a temporary file to save the input image from Gradio
|
19 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp_input:
|
20 |
+
image.save(tmp_input, format="PNG")
|
21 |
+
tmp_input_path = tmp_input.name
|
22 |
+
|
23 |
+
# Prepare a temporary file for the output image
|
24 |
+
tmp_output_path = tempfile.mktemp(suffix=".png")
|
25 |
+
|
26 |
+
# Process the image
|
27 |
+
process_image(tmp_input_path, tmp_output_path, crop,
|
28 |
+
remove_bg, resize_dimensions, padding, background)
|
29 |
+
|
30 |
+
# Load and return the processed image
|
31 |
+
processed_image = Image.open(tmp_output_path)
|
32 |
+
|
33 |
+
# Clean up temporary files
|
34 |
+
os.remove(tmp_input_path)
|
35 |
+
os.remove(tmp_output_path)
|
36 |
+
|
37 |
+
return processed_image
|
38 |
+
|
39 |
+
|
40 |
+
# Define the Gradio interface with updated component imports
|
41 |
+
interface = gr.Interface(fn=gradio_interface,
|
42 |
+
inputs=[
|
43 |
+
gr.components.Image(type="pil"),
|
44 |
+
gr.components.Checkbox(label="Crop"),
|
45 |
+
gr.components.Checkbox(
|
46 |
+
label="Remove Background"),
|
47 |
+
gr.components.Textbox(
|
48 |
+
label="Resize (WxH)", placeholder="Example: 100x100"),
|
49 |
+
gr.components.Slider(
|
50 |
+
minimum=0, maximum=200, label="Padding", default=0),
|
51 |
+
gr.components.Textbox(
|
52 |
+
label="Background", placeholder="Color name or hex code")
|
53 |
+
],
|
54 |
+
outputs=gr.components.Image(type="pil"),
|
55 |
+
title="Image Processor",
|
56 |
+
description="Upload an image and select processing options.")
|
57 |
+
|
58 |
+
|
59 |
+
if __name__ == "__main__":
|
60 |
+
interface.launch()
|
image_processor.py
ADDED
@@ -0,0 +1,244 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import argparse
|
2 |
+
import os
|
3 |
+
import shutil
|
4 |
+
from rembg import remove
|
5 |
+
from PIL import Image
|
6 |
+
|
7 |
+
|
8 |
+
def add_background(image_path, background, output_path, default_color="#FFFFFF"):
|
9 |
+
"""
|
10 |
+
Adds a background to an image, with a fallback to a default color if the specified color is not available.
|
11 |
+
|
12 |
+
Args:
|
13 |
+
- image_path (str): Path to the input image with transparent background.
|
14 |
+
- background (str): Background color (as a name or hex code) or path to a background image file.
|
15 |
+
- output_path (str): Path where the image with the new background should be saved.
|
16 |
+
- default_color (str): Fallback color if the specified background color is not valid. Defaults to white.
|
17 |
+
"""
|
18 |
+
with Image.open(image_path).convert("RGBA") as foreground:
|
19 |
+
try:
|
20 |
+
# Attempt to create a background layer with the specified color or image
|
21 |
+
if background.startswith("#") or background.isalpha():
|
22 |
+
# Check if the color name is valid by creating a small test image
|
23 |
+
Image.new("RGBA", (1, 1), background)
|
24 |
+
background_layer = Image.new(
|
25 |
+
"RGBA", foreground.size, background)
|
26 |
+
else:
|
27 |
+
# If background is an image file
|
28 |
+
with Image.open(background).convert("RGBA") as bg_img:
|
29 |
+
bg_img = bg_img.resize(foreground.size)
|
30 |
+
background_layer = bg_img
|
31 |
+
except ValueError:
|
32 |
+
# If the color is invalid, use the default color
|
33 |
+
print(
|
34 |
+
f"Invalid color '{background}'. Using default color '{default_color}'.")
|
35 |
+
background_layer = Image.new(
|
36 |
+
"RGBA", foreground.size, default_color)
|
37 |
+
|
38 |
+
# Composite the foreground over the background
|
39 |
+
with Image.alpha_composite(background_layer, foreground) as final_img:
|
40 |
+
# Convert to RGB to save in formats other than PNG
|
41 |
+
final_img = final_img.convert("RGB")
|
42 |
+
final_img.save(output_path)
|
43 |
+
|
44 |
+
|
45 |
+
def autocrop_image(image_path, output_path):
|
46 |
+
"""
|
47 |
+
Autocrops an image, focusing on the non-transparent pixels and saves as PNG.
|
48 |
+
|
49 |
+
Args:
|
50 |
+
- image_path (str): Path to the input image.
|
51 |
+
- output_path (str): Path where the cropped image should be saved.
|
52 |
+
"""
|
53 |
+
with Image.open(image_path).convert("RGBA") as image:
|
54 |
+
bbox = image.getbbox()
|
55 |
+
if bbox:
|
56 |
+
cropped_image = image.crop(bbox)
|
57 |
+
cropped_image.save(output_path, format='PNG')
|
58 |
+
else:
|
59 |
+
image.save(output_path, format='PNG')
|
60 |
+
|
61 |
+
|
62 |
+
def process_image(input_path, output_path, crop=False, remove_bg=False, resize=None, padding=0, background=None):
|
63 |
+
"""
|
64 |
+
Processes a single image based on the provided options and saves it.
|
65 |
+
|
66 |
+
Args:
|
67 |
+
- input_path (str): Path to the input image.
|
68 |
+
- output_path (str): Path where the processed image should be saved.
|
69 |
+
- crop (bool): Whether to autocrop the image.
|
70 |
+
- remove_bg (bool): Whether to remove the background of the image.
|
71 |
+
- resize (tuple): Optional dimensions (width, height) to resize the image.
|
72 |
+
- padding (int): Number of padding pixels to add around the image.
|
73 |
+
- background (str): Optional background color (hex code or name) or path to an image file to set as the background.
|
74 |
+
"""
|
75 |
+
need_processing = crop or resize or remove_bg or background
|
76 |
+
temp_path = output_path + ".tmp.png"
|
77 |
+
|
78 |
+
if remove_bg:
|
79 |
+
with open(input_path, 'rb') as input_file:
|
80 |
+
image_data = input_file.read()
|
81 |
+
image_data = remove(image_data)
|
82 |
+
with open(temp_path, 'wb') as temp_file:
|
83 |
+
temp_file.write(image_data)
|
84 |
+
else:
|
85 |
+
# Copy original image to temp_path if no background removal
|
86 |
+
shutil.copy(input_path, temp_path)
|
87 |
+
|
88 |
+
if crop:
|
89 |
+
autocrop_image(temp_path, temp_path)
|
90 |
+
|
91 |
+
if resize:
|
92 |
+
# adjusted_resize = (resize[0],
|
93 |
+
# resize[1]) if padding else resize
|
94 |
+
resize_and_pad_image(temp_path, temp_path, resize, padding)
|
95 |
+
|
96 |
+
if background:
|
97 |
+
add_background(temp_path, background, temp_path)
|
98 |
+
|
99 |
+
# Finalize the process: move from temp_path to output_path
|
100 |
+
os.rename(temp_path, output_path)
|
101 |
+
|
102 |
+
|
103 |
+
def resize_and_pad_image(image_path, output_path, dimensions, padding=0):
|
104 |
+
"""
|
105 |
+
Resizes an image to fit within specified dimensions (AxB) and adds padding to make it exactly AxB,
|
106 |
+
ensuring the image content is centered within these dimensions.
|
107 |
+
|
108 |
+
Args:
|
109 |
+
- image_path (str): Path to the input image.
|
110 |
+
- output_path (str): Path where the resized and padded image should be saved.
|
111 |
+
- dimensions (tuple): Target dimensions (width, height) in pixels, before adding padding.
|
112 |
+
- padding (int): Number of padding pixels to add around the image.
|
113 |
+
"""
|
114 |
+
target_width, target_height = dimensions
|
115 |
+
content_width, content_height = target_width - \
|
116 |
+
2*padding, target_height - 2*padding
|
117 |
+
|
118 |
+
with Image.open(image_path) as img:
|
119 |
+
# Resize the image, preserving the aspect ratio
|
120 |
+
img.thumbnail((content_width, content_height),
|
121 |
+
Image.Resampling.LANCZOS)
|
122 |
+
|
123 |
+
# Create a new image with the target dimensions and a transparent background
|
124 |
+
# new image shall include padding spacig
|
125 |
+
|
126 |
+
new_img = Image.new("RGBA", dimensions, (255, 255, 255, 0))
|
127 |
+
|
128 |
+
# Calculate the position to paste the resized image to center it
|
129 |
+
paste_position = ((target_width - img.width) // 2,
|
130 |
+
(target_height - img.height) // 2)
|
131 |
+
|
132 |
+
# Paste the resized image onto the new image, centered
|
133 |
+
new_img.paste(img, paste_position, img if img.mode == 'RGBA' else None)
|
134 |
+
|
135 |
+
# Save the output
|
136 |
+
new_img.save(output_path, format='PNG')
|
137 |
+
|
138 |
+
|
139 |
+
def generate_output_filename(input_path, remove_bg=False, crop=False, resize=None, background=None):
|
140 |
+
"""
|
141 |
+
Generates an output filename based on the input path and processing options applied.
|
142 |
+
Appends specific suffixes based on the operations: '_b' for background removal, '_c' for crop,
|
143 |
+
and '_bg' if a background is added. It ensures the file extension is '.png'.
|
144 |
+
|
145 |
+
Args:
|
146 |
+
- input_path (str): Path to the input image.
|
147 |
+
- remove_bg (bool): Indicates if background removal was applied.
|
148 |
+
- crop (bool): Indicates if autocrop was applied.
|
149 |
+
- resize (tuple): Optional dimensions (width, height) for resizing the image.
|
150 |
+
- background (str): Indicates if a background was added (None if not used).
|
151 |
+
|
152 |
+
Returns:
|
153 |
+
- (str): Modified filename with appropriate suffix and '.png' extension.
|
154 |
+
"""
|
155 |
+
base, _ = os.path.splitext(os.path.basename(input_path))
|
156 |
+
suffix = ""
|
157 |
+
|
158 |
+
if remove_bg:
|
159 |
+
suffix += "_b"
|
160 |
+
if crop:
|
161 |
+
suffix += "_c"
|
162 |
+
if resize:
|
163 |
+
width, height = resize
|
164 |
+
suffix += f"_{width}x{height}"
|
165 |
+
if background:
|
166 |
+
suffix += "_bg" # Append "_bg" if the background option was used
|
167 |
+
|
168 |
+
# Ensure the file saves as PNG, accommodating for transparency or added backgrounds
|
169 |
+
return f"{base}{suffix}.png"
|
170 |
+
|
171 |
+
|
172 |
+
def generate_output_filename2(input_path, remove_bg=False, crop=False, resize=None, padding=0, background=None):
|
173 |
+
"""
|
174 |
+
Generates an output filename based on the input path and processing options applied.
|
175 |
+
Takes into account the effect of padding on the final image size for naming.
|
176 |
+
"""
|
177 |
+
base, _ = os.path.splitext(os.path.basename(input_path))
|
178 |
+
suffix = ""
|
179 |
+
|
180 |
+
if remove_bg:
|
181 |
+
suffix += "_b"
|
182 |
+
if crop:
|
183 |
+
suffix += "_c"
|
184 |
+
if resize:
|
185 |
+
# Adjust the resize dimensions to reflect the final image size after padding
|
186 |
+
if padding > 0:
|
187 |
+
# Adjust dimensions to reflect content size before padding if that's the intent
|
188 |
+
# Otherwise, add padding to the dimensions to reflect final size including padding
|
189 |
+
adjusted_width = resize[0] + 2*padding
|
190 |
+
adjusted_height = resize[1] + 2*padding
|
191 |
+
suffix += f"_{adjusted_width}x{adjusted_height}"
|
192 |
+
else:
|
193 |
+
width, height = resize
|
194 |
+
suffix += f"_{width}x{height}"
|
195 |
+
if background:
|
196 |
+
suffix += "_bg"
|
197 |
+
|
198 |
+
return f"{base}{suffix}.png"
|
199 |
+
|
200 |
+
# The main and process_images functions remain the same, but ensure to update them to handle the new PNG output correctly.
|
201 |
+
|
202 |
+
# Update the process_images and main functions to include the new autocrop functionality
|
203 |
+
# Ensure to pass the crop argument to process_image and adjust the output filename generation accordingly
|
204 |
+
|
205 |
+
|
206 |
+
def process_images(input_dir="./input", output_dir="./output", crop=False, remove_bg=False, resize=None, padding=0, background=None):
|
207 |
+
"""
|
208 |
+
Processes images in the specified directory based on the provided options.
|
209 |
+
|
210 |
+
Args:
|
211 |
+
- input_dir (str): Directory containing the images to be processed.
|
212 |
+
- output_dir (str): Directory where processed images will be saved.
|
213 |
+
- crop (bool): Whether to crop the images.
|
214 |
+
- remove_bg (bool): Whether to remove the background of the images.
|
215 |
+
- resize (tuple): Optional dimensions (width, height) to resize the image.
|
216 |
+
- padding (int): Number of padding pixels to add around the image.
|
217 |
+
- background (str): Optional background color (hex code or name) or path to an image file to set as the background.
|
218 |
+
"""
|
219 |
+
processed_input_dir = os.path.join(input_dir, "processed")
|
220 |
+
os.makedirs(processed_input_dir, exist_ok=True)
|
221 |
+
os.makedirs(output_dir, exist_ok=True)
|
222 |
+
|
223 |
+
inputs = [os.path.join(input_dir, f) for f in os.listdir(
|
224 |
+
input_dir) if os.path.isfile(os.path.join(input_dir, f))]
|
225 |
+
|
226 |
+
# if images are not in the input directory, print a message and return
|
227 |
+
if not inputs:
|
228 |
+
print("No images found in the input directory.")
|
229 |
+
return
|
230 |
+
|
231 |
+
for i, input_path in enumerate(inputs, start=1):
|
232 |
+
filename = os.path.basename(input_path)
|
233 |
+
output_filename = generate_output_filename(
|
234 |
+
input_path, remove_bg=remove_bg, crop=crop, resize=resize, background=background)
|
235 |
+
output_path = os.path.join(output_dir, output_filename)
|
236 |
+
print(f"Processing image {i}/{len(inputs)}...{filename}")
|
237 |
+
|
238 |
+
# Update the call to process_image with all parameters including background
|
239 |
+
process_image(input_path, output_path, crop=crop, remove_bg=remove_bg,
|
240 |
+
resize=resize, padding=padding, background=background)
|
241 |
+
|
242 |
+
shutil.move(input_path, os.path.join(processed_input_dir, filename))
|
243 |
+
|
244 |
+
print("All images have been processed.")
|