woodmastr commited on
Commit
386603a
1 Parent(s): 06d9c6f

Upload 2 files

Browse files
Files changed (2) hide show
  1. gradio_imager.py +60 -0
  2. 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.")