woodmastr commited on
Commit
a6e0ade
·
verified ·
1 Parent(s): be90a2f

Update image_processor.py

Browse files
Files changed (1) hide show
  1. image_processor.py +288 -103
image_processor.py CHANGED
@@ -1,106 +1,291 @@
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 apply_standard_settings(setting):
9
- """Returns the parameters for the selected standard setting."""
10
- settings_dict = {
11
- "S light": (True, True, "240x240", 48, "whitesmoke"),
12
- "M light": (True, True, "480x480", 96, "whitesmoke"),
13
- "L light": (True, True, "960x960", 128, "whitesmoke"),
14
- "S dark": (True, True, "240x240", 48, "#2A373D"),
15
- "M dark": (True, True, "480x480", 96, "#2A373D"),
16
- "L dark": (True, True, "960x960", 128, "#2A373D"),
17
- }
18
- # Default to no special settings
19
- return settings_dict.get(setting, (None, None, None, None, None))
20
-
21
-
22
- def settings_description(crop, remove_bg, resize, padding, background):
23
- """Generate an HTML text description of the current settings in a smaller font and list format."""
24
- description = f"""
25
- <ul style="font-size:small;">
26
- <li>Crop: {crop}</li>
27
- <li>Remove Background: {remove_bg}</li>
28
- <li>Resize: {resize if resize else 'No resize'}</li>
29
- <li>Padding: {padding}</li>
30
- <li>Background: {background}</li>
31
- </ul>
32
- """
33
- return description
34
-
35
-
36
- def gradio_interface(image, standard_settings, crop=False, remove_bg=False, resize=None, padding=0, background="white"):
37
- # Apply standard settings if selected and not "None"
38
- if image is None:
39
- # Load the standard image from the specified path if no image is uploaded
40
- standard_image_path = './data/examples/supermario.png'
41
- image = Image.open(standard_image_path)
42
-
43
- if standard_settings and standard_settings != "None":
44
- crop, remove_bg, resize, padding, background = apply_standard_settings(
45
- standard_settings)
46
-
47
- # Generate settings description
48
- applied_settings = settings_description(
49
- crop, remove_bg, resize, padding, background)
50
-
51
- # Convert resize string to tuple (if provided)
52
- resize_dimensions = None
53
- if resize:
54
  try:
55
- width, height = map(int, resize.split('x'))
56
- resize_dimensions = (width, height)
57
  except ValueError:
58
- return "Invalid format for resize dimensions. Please use 'WxH'.", "original", applied_settings
59
- # Process the image directly
60
- processed_image = process_image(
61
- image, crop, remove_bg, resize_dimensions, padding, background)
62
-
63
- # Generate settings description
64
- applied_settings = settings_description(
65
- crop, remove_bg, resize, padding, background)
66
-
67
- return processed_image, applied_settings
68
-
69
-
70
- example_images = [
71
- [os.path.join("data", "examples", "supermario.png"),
72
- "S light", True, True, "480x420", 10, "whitesmoke"],
73
- [os.path.join("data", "examples",
74
- "depositphotos_520707962-stock-photo-fujifilm-s10-body-black-fujifilm.jpg"), "None", True, True, "480x320", 48, "blue"],
75
- [os.path.join("data", "examples", "batman_b_c_320x280_bg.png"),
76
- "None", True, True, "360x360", 48, "yellow"],
77
-
78
-
79
- ]
80
-
81
- # Define the Gradio interface
82
- interface = gr.Interface(fn=gradio_interface,
83
- inputs=[
84
- gr.components.Image(
85
- type="pil", label="Input Image"),
86
- gr.components.Radio(choices=[
87
- "None", "S light", "M light", "L light", "S dark", "M dark", "L dark"], label="Settings"),
88
- gr.components.Checkbox(label="Crop"),
89
- gr.components.Checkbox(label="Remove Background"),
90
- gr.components.Textbox(
91
- label="Resize (WxH)", placeholder="Example: 100x100"),
92
- gr.components.Slider(
93
- minimum=0, maximum=200, label="Padding"),
94
- gr.components.Textbox(
95
- label="Background", placeholder="Color name or hex code")
96
- ],
97
- outputs=[
98
- gr.components.Image(type="pil"),
99
- gr.components.HTML(label="Applied Settings")
100
- ],
101
- examples=example_images,
102
- title="IMAGER ___ Image Processor",
103
- description="Upload an image and select processing options or choose a standard setting. Supports crop, autoremove background, resize, add padding, and set the background color.",)
104
-
105
- if __name__ == "__main__":
106
- interface.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
 
 
2
  import os
3
+ import shutil
4
+ from rembg import remove
5
+ from PIL import Image
6
+ import io
7
+
8
+
9
+ def add_background(image, background, default_color="#FFFFFF"):
10
+ """
11
+ Adds a background to an image, with a fallback to a default color if the specified background is not available.
12
+
13
+ Args:
14
+ - image (PIL.Image.Image): Image with a transparent background.
15
+ - background (str or PIL.Image.Image): Background color (as a hex code) or a PIL Image to be used as background.
16
+ - default_color (str): Fallback color if the specified background is not valid. Defaults to white.
17
+
18
+ Returns:
19
+ - PIL.Image.Image: The image with the new background.
20
+ """
21
+ foreground = image.convert("RGBA")
22
+
23
+ if isinstance(background, str) and (background.startswith("#") or background.isalpha()):
24
+ # Background is a color
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  try:
26
+ Image.new("RGBA", (1, 1), background) # Test if valid color
27
+ background_layer = Image.new("RGBA", foreground.size, background)
28
  except ValueError:
29
+ print(
30
+ f"Invalid color '{background}'. Using default color '{default_color}'.")
31
+ background_layer = Image.new(
32
+ "RGBA", foreground.size, default_color)
33
+ elif isinstance(background, Image.Image):
34
+ # Background is an image
35
+ bg_img = background.convert("RGBA")
36
+ background_layer = bg_img.resize(foreground.size)
37
+ else:
38
+ # Fallback to default color
39
+ background_layer = Image.new("RGBA", foreground.size, default_color)
40
+
41
+ final_img = Image.alpha_composite(
42
+ background_layer, foreground).convert("RGB")
43
+
44
+ return final_img
45
+
46
+
47
+ def autocrop_image(image):
48
+ """
49
+ Autocrops an image, focusing on the non-transparent pixels.
50
+
51
+ Args:
52
+ - image (PIL.Image.Image): Image to be autocropped.
53
+
54
+ Returns:
55
+ - PIL.Image.Image: The autocropped image.
56
+ """
57
+ bbox = image.getbbox()
58
+ if bbox:
59
+ return image.crop(bbox)
60
+ return image
61
+
62
+
63
+ def remove_bg_func(image):
64
+ """
65
+ Removes the background from an image using the rembg library.
66
+
67
+ Args:
68
+ - image (PIL.Image.Image): Image object from which to remove the background.
69
+
70
+ Returns:
71
+ - PIL.Image.Image: New image object with the background removed.
72
+ """
73
+ # Convert the PIL Image to bytes
74
+ img_byte_arr = io.BytesIO()
75
+ image.save(img_byte_arr, format='PNG')
76
+ img_byte_arr = img_byte_arr.getvalue()
77
+
78
+ # Use rembg to remove the background
79
+ result_bytes = remove(img_byte_arr)
80
+
81
+ # Convert the result bytes back to a PIL Image
82
+ result_image = Image.open(io.BytesIO(result_bytes))
83
+
84
+ return result_image
85
+
86
+
87
+ def process_image(image_data, crop=False, remove_bg=False, resize=None, padding=0, background=None):
88
+ """
89
+ Processes a single image based on the provided options.
90
+
91
+ Args:
92
+ - image_data (PIL.Image.Image): The input image.
93
+ - crop (bool): Whether to autocrop the image.
94
+ - remove_bg (bool): Whether to remove the background of the image.
95
+ - resize (tuple): Optional dimensions (width, height) to resize the image.
96
+ - padding (int): Number of padding pixels to add around the image.
97
+ - background (str): Optional background color (hex code or name) or path to an image file to set as the background.
98
+
99
+ Returns:
100
+ - PIL.Image.Image: The processed image.
101
+ """
102
+ # Assume image_data is a PIL.Image.Image object
103
+
104
+ if remove_bg:
105
+ # Assuming remove_bg function returns a PIL image
106
+ image_data = remove_bg_func(image_data)
107
+
108
+ if crop:
109
+ # Assuming autocrop_image function modifies the image in place or returns a new PIL image
110
+ image_data = autocrop_image(image_data)
111
+
112
+ if resize:
113
+ # Assuming resize_and_pad_image function modifies the image in place or returns a new PIL image
114
+ image_data = resize_and_pad_image(image_data, resize, padding)
115
+
116
+ if background:
117
+ # Assuming add_background function modifies the image in place or returns a new PIL image
118
+ image_data = add_background(image_data, background)
119
+
120
+ return image_data
121
+
122
+
123
+ def resize_and_pad_image(image, dimensions, padding=0):
124
+ """
125
+ Resizes an image to fit the specified dimensions and adds padding.
126
+
127
+ Args:
128
+ - image (PIL.Image.Image): Image object to be resized and padded.
129
+ - dimensions (tuple): Target dimensions (width, height).
130
+ - padding (int): Padding to add around the resized image.
131
+
132
+ Returns:
133
+ - PIL.Image.Image: Resized and padded image object.
134
+ """
135
+ target_width, target_height = dimensions
136
+ content_width, content_height = target_width - \
137
+ 2*padding, target_height - 2*padding
138
+
139
+ # Determine new size, preserving aspect ratio
140
+ img_ratio = image.width / image.height
141
+ target_ratio = content_width / content_height
142
+
143
+ if target_ratio > img_ratio:
144
+ new_height = content_height
145
+ new_width = int(new_height * img_ratio)
146
+ else:
147
+ new_width = content_width
148
+ new_height = int(new_width / img_ratio)
149
+
150
+ # Resize the image
151
+ resized_img = image.resize(
152
+ (new_width, new_height), Image.Resampling.LANCZOS)
153
+
154
+ # Create a new image with the target dimensions and a transparent background
155
+ new_img = Image.new(
156
+ "RGBA", (target_width, target_height), (255, 255, 255, 0))
157
+
158
+ # Calculate the position to paste the resized image to center it
159
+ paste_position = ((target_width - new_width) // 2,
160
+ (target_height - new_height) // 2)
161
+
162
+ # Paste the resized image onto the new image, centered
163
+ new_img.paste(resized_img, paste_position,
164
+ resized_img if resized_img.mode == 'RGBA' else None)
165
+
166
+ return new_img
167
+
168
+
169
+ def generate_output_filename(input_path, remove_bg=False, crop=False, resize=None, background=None):
170
+ """
171
+ Generates an output filename based on the input path and processing options applied.
172
+ Appends specific suffixes based on the operations: '_b' for background removal, '_c' for crop,
173
+ and '_bg' if a background is added. It ensures the file extension is '.png'.
174
+
175
+ Args:
176
+ - input_path (str): Path to the input image.
177
+ - remove_bg (bool): Indicates if background removal was applied.
178
+ - crop (bool): Indicates if autocrop was applied.
179
+ - resize (tuple): Optional dimensions (width, height) for resizing the image.
180
+ - background (str): Indicates if a background was added (None if not used).
181
+
182
+ Returns:
183
+ - (str): Modified filename with appropriate suffix and '.png' extension.
184
+ """
185
+ base, _ = os.path.splitext(os.path.basename(input_path))
186
+ suffix = ""
187
+
188
+ if remove_bg:
189
+ suffix += "_b"
190
+ if crop:
191
+ suffix += "_c"
192
+ if resize:
193
+ width, height = resize
194
+ suffix += f"_{width}x{height}"
195
+ if background:
196
+ suffix += "_bg" # Append "_bg" if the background option was used
197
+
198
+ # Ensure the file saves as PNG, accommodating for transparency or added backgrounds
199
+ return f"{base}{suffix}.png"
200
+
201
+
202
+ # The main and process_images functions remain the same, but ensure to update them to handle the new PNG output correctly.
203
+
204
+ # Update the process_images and main functions to include the new autocrop functionality
205
+ # Ensure to pass the crop argument to process_image and adjust the output filename generation accordingly
206
+
207
+
208
+ def process_images2(input_dir="./input", output_dir="./output", crop=False, remove_bg=False, resize=None, padding=0, background=None):
209
+ """
210
+ Processes images in the specified directory based on the provided options.
211
+
212
+ Args:
213
+ - input_dir (str): Directory containing the images to be processed.
214
+ - output_dir (str): Directory where processed images will be saved.
215
+ - crop (bool): Whether to crop the images.
216
+ - remove_bg (bool): Whether to remove the background of the images.
217
+ - resize (tuple): Optional dimensions (width, height) to resize the image.
218
+ - padding (int): Number of padding pixels to add around the image.
219
+ - background (str): Optional background color (hex code or name) or path to an image file to set as the background.
220
+ """
221
+ processed_input_dir = os.path.join(input_dir, "processed")
222
+ os.makedirs(processed_input_dir, exist_ok=True)
223
+ os.makedirs(output_dir, exist_ok=True)
224
+
225
+ inputs = [os.path.join(input_dir, f) for f in os.listdir(
226
+ input_dir) if os.path.isfile(os.path.join(input_dir, f))]
227
+
228
+ # if images are not in the input directory, print a message and return
229
+ if not inputs:
230
+ print("No images found in the input directory.")
231
+ return
232
+
233
+ for i, input_path in enumerate(inputs, start=1):
234
+ filename = os.path.basename(input_path)
235
+ output_filename = generate_output_filename(
236
+ input_path, remove_bg=remove_bg, crop=crop, resize=resize, background=background)
237
+ output_path = os.path.join(output_dir, output_filename)
238
+ print(f"Processing image {i}/{len(inputs)}...{filename}")
239
+
240
+ # Update the call to process_image with all parameters including background
241
+ process_image(input_path, output_path, crop=crop, remove_bg=remove_bg,
242
+ resize=resize, padding=padding, background=background)
243
+
244
+ shutil.move(input_path, os.path.join(processed_input_dir, filename))
245
+
246
+ print("All images have been processed.")
247
+
248
+
249
+ def process_images(input_dir="./input", output_dir="./output", crop=False, remove_bg=False, resize=None, padding=0, background=None):
250
+ """
251
+ Processes images in the specified directory based on the provided options.
252
+ """
253
+ processed_input_dir = os.path.join(input_dir, "processed")
254
+ os.makedirs(processed_input_dir, exist_ok=True)
255
+ os.makedirs(output_dir, exist_ok=True)
256
+
257
+ inputs = [os.path.join(input_dir, f) for f in os.listdir(
258
+ input_dir) if os.path.isfile(os.path.join(input_dir, f))]
259
+
260
+ if not inputs:
261
+ print("No images found in the input directory.")
262
+ return
263
+
264
+ for i, input_path in enumerate(inputs, start=1):
265
+ try:
266
+ with Image.open(input_path) as img:
267
+ # Define filename here, before it's used
268
+ filename = os.path.basename(input_path)
269
+
270
+ # Process the image
271
+ processed_img = process_image(
272
+ img, crop=crop, remove_bg=remove_bg, resize=resize, padding=padding, background=background)
273
+
274
+ # Generate output filename based on processing parameters
275
+ output_filename = generate_output_filename(
276
+ filename, remove_bg=remove_bg, crop=crop, resize=resize, background=background)
277
+ output_path = os.path.join(output_dir, output_filename)
278
+
279
+ # Save the processed image to the output directory
280
+ processed_img.save(output_path)
281
+
282
+ print(
283
+ f"Processed image {i}/{len(inputs)}: {filename} -> {output_filename}")
284
+
285
+ # Optionally move the processed input image to a "processed" subdirectory
286
+ shutil.move(input_path, os.path.join(
287
+ processed_input_dir, filename))
288
+ except Exception as e:
289
+ print(f"Error processing image {input_path}: {e}")
290
+
291
+ print("All images have been processed.")