woodmastr commited on
Commit
94cb743
1 Parent(s): 786cdff

Synced repo using 'sync_with_huggingface' Github Action

Browse files
Files changed (5) hide show
  1. addbg.py +71 -0
  2. gradio_imager.py +106 -0
  3. image_processor.py +278 -0
  4. imager.py +47 -0
  5. requirements.txt +2 -0
addbg.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import argparse
3
+ from PIL import Image, ImageStat, ImageEnhance
4
+
5
+
6
+ def adjust_background_brightness(bg_image, mode):
7
+ """
8
+ Adjusts the brightness of the background image based on the specified mode.
9
+ Args:
10
+ - bg_image (Image): The PIL Image object of the background.
11
+ - mode (str): The mode for background adjustment ('lighter', 'darker').
12
+ Returns:
13
+ - Image: The adjusted PIL Image object.
14
+ """
15
+ enhancer = ImageEnhance.Brightness(bg_image)
16
+ if mode == 'lighter':
17
+ return enhancer.enhance(1.5) # Increase brightness
18
+ elif mode == 'darker':
19
+ return enhancer.enhance(0.5) # Decrease brightness
20
+ return bg_image # Return unmodified if mode is not recognized
21
+
22
+
23
+ def generate_complementary_background(image_path, guidance):
24
+ """
25
+ Generates a complementary background image based on guidance.
26
+ Args:
27
+ - image_path (str): Path to the input image.
28
+ - guidance (str): Guidance for background generation ('lighter', 'darker', 'color').
29
+ Returns:
30
+ - Image: A PIL Image object of the generated background.
31
+ """
32
+ with Image.open(image_path) as img:
33
+ # Assuming a simplistic approach to generate a solid color background
34
+ background = Image.new(
35
+ 'RGB', img.size, (255, 255, 255) if guidance == 'lighter' else (0, 0, 0))
36
+
37
+ # Adjust the background based on guidance
38
+ if guidance in ['lighter', 'darker']:
39
+ background = adjust_background_brightness(background, guidance)
40
+ # For color or other guidance, additional logic can be implemented
41
+
42
+ return background
43
+
44
+
45
+ def blend_images(foreground_path, output_path, guidance):
46
+ """
47
+ Blends an input image with a dynamically generated background image based on guidance.
48
+ Args:
49
+ - foreground_path (str): Path to the input image with a transparent background.
50
+ - output_path (str): Path where the blended image will be saved.
51
+ - guidance (str): Guidance for background generation.
52
+ """
53
+ background = generate_complementary_background(foreground_path, guidance)
54
+ with Image.open(foreground_path).convert("RGBA") as foreground:
55
+ background = background.convert("RGBA")
56
+ blended_image = Image.alpha_composite(background, foreground)
57
+ blended_image.convert("RGB").save(output_path, "PNG")
58
+
59
+
60
+ if __name__ == "__main__":
61
+ parser = argparse.ArgumentParser(
62
+ description="Blends an input image with a dynamically generated background image based on guidance.")
63
+ parser.add_argument(
64
+ "foreground", help="Path to the input image with a transparent background.")
65
+ parser.add_argument("output", nargs='?', default="out.png",
66
+ help="Path where the blended image will be saved. Defaults to 'out.png'.")
67
+ parser.add_argument("--guidance", choices=['lighter', 'darker', 'color'], default='darker',
68
+ help="Guidance for the type of background to generate ('lighter', 'darker', 'color'). Defaults to 'darker'.")
69
+
70
+ args = parser.parse_args()
71
+ blend_images(args.foreground, args.output, args.guidance)
gradio_imager.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()
image_processor.py ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ Args:
13
+ - image (PIL.Image.Image): Image with a transparent background.
14
+ - background (str or PIL.Image.Image): Background color (as a hex code) or a PIL Image to be used as background.
15
+ - default_color (str): Fallback color if the specified background is not valid. Defaults to white.
16
+ Returns:
17
+ - PIL.Image.Image: The image with the new background.
18
+ """
19
+ foreground = image.convert("RGBA")
20
+
21
+ if isinstance(background, str) and (background.startswith("#") or background.isalpha()):
22
+ # Background is a color
23
+ try:
24
+ Image.new("RGBA", (1, 1), background) # Test if valid color
25
+ background_layer = Image.new("RGBA", foreground.size, background)
26
+ except ValueError:
27
+ print(
28
+ f"Invalid color '{background}'. Using default color '{default_color}'.")
29
+ background_layer = Image.new(
30
+ "RGBA", foreground.size, default_color)
31
+ elif isinstance(background, Image.Image):
32
+ # Background is an image
33
+ bg_img = background.convert("RGBA")
34
+ background_layer = bg_img.resize(foreground.size)
35
+ else:
36
+ # Fallback to default color
37
+ background_layer = Image.new("RGBA", foreground.size, default_color)
38
+
39
+ final_img = Image.alpha_composite(
40
+ background_layer, foreground).convert("RGB")
41
+
42
+ return final_img
43
+
44
+
45
+ def autocrop_image(image):
46
+ """
47
+ Autocrops an image, focusing on the non-transparent pixels.
48
+ Args:
49
+ - image (PIL.Image.Image): Image to be autocropped.
50
+ Returns:
51
+ - PIL.Image.Image: The autocropped image.
52
+ """
53
+ bbox = image.getbbox()
54
+ if bbox:
55
+ return image.crop(bbox)
56
+ return image
57
+
58
+
59
+ def remove_bg_func(image):
60
+ """
61
+ Removes the background from an image using the rembg library.
62
+ Args:
63
+ - image (PIL.Image.Image): Image object from which to remove the background.
64
+ Returns:
65
+ - PIL.Image.Image: New image object with the background removed.
66
+ """
67
+ # Convert the PIL Image to bytes
68
+ img_byte_arr = io.BytesIO()
69
+ image.save(img_byte_arr, format='PNG')
70
+ img_byte_arr = img_byte_arr.getvalue()
71
+
72
+ # Use rembg to remove the background
73
+ result_bytes = remove(img_byte_arr)
74
+
75
+ # Convert the result bytes back to a PIL Image
76
+ result_image = Image.open(io.BytesIO(result_bytes))
77
+
78
+ return result_image
79
+
80
+
81
+ def process_image(image_data, crop=False, remove_bg=False, resize=None, padding=0, background=None):
82
+ """
83
+ Processes a single image based on the provided options.
84
+ Args:
85
+ - image_data (PIL.Image.Image): The input image.
86
+ - crop (bool): Whether to autocrop the image.
87
+ - remove_bg (bool): Whether to remove the background of the image.
88
+ - resize (tuple): Optional dimensions (width, height) to resize the image.
89
+ - padding (int): Number of padding pixels to add around the image.
90
+ - background (str): Optional background color (hex code or name) or path to an image file to set as the background.
91
+ Returns:
92
+ - PIL.Image.Image: The processed image.
93
+ """
94
+ # Assume image_data is a PIL.Image.Image object
95
+
96
+ if remove_bg:
97
+ # Assuming remove_bg function returns a PIL image
98
+ image_data = remove_bg_func(image_data)
99
+
100
+ if crop:
101
+ # Assuming autocrop_image function modifies the image in place or returns a new PIL image
102
+ image_data = autocrop_image(image_data)
103
+
104
+ if resize:
105
+ # Assuming resize_and_pad_image function modifies the image in place or returns a new PIL image
106
+ image_data = resize_and_pad_image(image_data, resize, padding)
107
+
108
+ if background:
109
+ # Assuming add_background function modifies the image in place or returns a new PIL image
110
+ image_data = add_background(image_data, background)
111
+
112
+ return image_data
113
+
114
+
115
+ def resize_and_pad_image(image, dimensions, padding=0):
116
+ """
117
+ Resizes an image to fit the specified dimensions and adds padding.
118
+ Args:
119
+ - image (PIL.Image.Image): Image object to be resized and padded.
120
+ - dimensions (tuple): Target dimensions (width, height).
121
+ - padding (int): Padding to add around the resized image.
122
+ Returns:
123
+ - PIL.Image.Image: Resized and padded image object.
124
+ """
125
+ target_width, target_height = dimensions
126
+ content_width, content_height = target_width - \
127
+ 2*padding, target_height - 2*padding
128
+
129
+ # Determine new size, preserving aspect ratio
130
+ img_ratio = image.width / image.height
131
+ target_ratio = content_width / content_height
132
+
133
+ if target_ratio > img_ratio:
134
+ new_height = content_height
135
+ new_width = int(new_height * img_ratio)
136
+ else:
137
+ new_width = content_width
138
+ new_height = int(new_width / img_ratio)
139
+
140
+ # Resize the image
141
+ resized_img = image.resize(
142
+ (new_width, new_height), Image.Resampling.LANCZOS)
143
+
144
+ # Create a new image with the target dimensions and a transparent background
145
+ new_img = Image.new(
146
+ "RGBA", (target_width, target_height), (255, 255, 255, 0))
147
+
148
+ # Calculate the position to paste the resized image to center it
149
+ paste_position = ((target_width - new_width) // 2,
150
+ (target_height - new_height) // 2)
151
+
152
+ # Paste the resized image onto the new image, centered
153
+ new_img.paste(resized_img, paste_position,
154
+ resized_img if resized_img.mode == 'RGBA' else None)
155
+
156
+ return new_img
157
+
158
+
159
+ def generate_output_filename(input_path, remove_bg=False, crop=False, resize=None, background=None):
160
+ """
161
+ Generates an output filename based on the input path and processing options applied.
162
+ Appends specific suffixes based on the operations: '_b' for background removal, '_c' for crop,
163
+ and '_bg' if a background is added. It ensures the file extension is '.png'.
164
+ Args:
165
+ - input_path (str): Path to the input image.
166
+ - remove_bg (bool): Indicates if background removal was applied.
167
+ - crop (bool): Indicates if autocrop was applied.
168
+ - resize (tuple): Optional dimensions (width, height) for resizing the image.
169
+ - background (str): Indicates if a background was added (None if not used).
170
+ Returns:
171
+ - (str): Modified filename with appropriate suffix and '.png' extension.
172
+ """
173
+ base, _ = os.path.splitext(os.path.basename(input_path))
174
+ suffix = ""
175
+
176
+ if remove_bg:
177
+ suffix += "_b"
178
+ if crop:
179
+ suffix += "_c"
180
+ if resize:
181
+ width, height = resize
182
+ suffix += f"_{width}x{height}"
183
+ if background:
184
+ suffix += "_bg" # Append "_bg" if the background option was used
185
+
186
+ # Ensure the file saves as PNG, accommodating for transparency or added backgrounds
187
+ return f"{base}{suffix}.png"
188
+
189
+
190
+ # The main and process_images functions remain the same, but ensure to update them to handle the new PNG output correctly.
191
+
192
+ # Update the process_images and main functions to include the new autocrop functionality
193
+ # Ensure to pass the crop argument to process_image and adjust the output filename generation accordingly
194
+
195
+
196
+ def process_images2(input_dir="./input", output_dir="./output", crop=False, remove_bg=False, resize=None, padding=0, background=None):
197
+ """
198
+ Processes images in the specified directory based on the provided options.
199
+ Args:
200
+ - input_dir (str): Directory containing the images to be processed.
201
+ - output_dir (str): Directory where processed images will be saved.
202
+ - crop (bool): Whether to crop the images.
203
+ - remove_bg (bool): Whether to remove the background of the images.
204
+ - resize (tuple): Optional dimensions (width, height) to resize the image.
205
+ - padding (int): Number of padding pixels to add around the image.
206
+ - background (str): Optional background color (hex code or name) or path to an image file to set as the background.
207
+ """
208
+ processed_input_dir = os.path.join(input_dir, "processed")
209
+ os.makedirs(processed_input_dir, exist_ok=True)
210
+ os.makedirs(output_dir, exist_ok=True)
211
+
212
+ inputs = [os.path.join(input_dir, f) for f in os.listdir(
213
+ input_dir) if os.path.isfile(os.path.join(input_dir, f))]
214
+
215
+ # if images are not in the input directory, print a message and return
216
+ if not inputs:
217
+ print("No images found in the input directory.")
218
+ return
219
+
220
+ for i, input_path in enumerate(inputs, start=1):
221
+ filename = os.path.basename(input_path)
222
+ output_filename = generate_output_filename(
223
+ input_path, remove_bg=remove_bg, crop=crop, resize=resize, background=background)
224
+ output_path = os.path.join(output_dir, output_filename)
225
+ print(f"Processing image {i}/{len(inputs)}...{filename}")
226
+
227
+ # Update the call to process_image with all parameters including background
228
+ process_image(input_path, output_path, crop=crop, remove_bg=remove_bg,
229
+ resize=resize, padding=padding, background=background)
230
+
231
+ shutil.move(input_path, os.path.join(processed_input_dir, filename))
232
+
233
+ print("All images have been processed.")
234
+
235
+
236
+ def process_images(input_dir="./input", output_dir="./output", crop=False, remove_bg=False, resize=None, padding=0, background=None):
237
+ """
238
+ Processes images in the specified directory based on the provided options.
239
+ """
240
+ processed_input_dir = os.path.join(input_dir, "processed")
241
+ os.makedirs(processed_input_dir, exist_ok=True)
242
+ os.makedirs(output_dir, exist_ok=True)
243
+
244
+ inputs = [os.path.join(input_dir, f) for f in os.listdir(
245
+ input_dir) if os.path.isfile(os.path.join(input_dir, f))]
246
+
247
+ if not inputs:
248
+ print("No images found in the input directory.")
249
+ return
250
+
251
+ for i, input_path in enumerate(inputs, start=1):
252
+ try:
253
+ with Image.open(input_path) as img:
254
+ # Define filename here, before it's used
255
+ filename = os.path.basename(input_path)
256
+
257
+ # Process the image
258
+ processed_img = process_image(
259
+ img, crop=crop, remove_bg=remove_bg, resize=resize, padding=padding, background=background)
260
+
261
+ # Generate output filename based on processing parameters
262
+ output_filename = generate_output_filename(
263
+ filename, remove_bg=remove_bg, crop=crop, resize=resize, background=background)
264
+ output_path = os.path.join(output_dir, output_filename)
265
+
266
+ # Save the processed image to the output directory
267
+ processed_img.save(output_path)
268
+
269
+ print(
270
+ f"Processed image {i}/{len(inputs)}: {filename} -> {output_filename}")
271
+
272
+ # Optionally move the processed input image to a "processed" subdirectory
273
+ shutil.move(input_path, os.path.join(
274
+ processed_input_dir, filename))
275
+ except Exception as e:
276
+ print(f"Error processing image {input_path}: {e}")
277
+
278
+ print("All images have been processed.")
imager.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ # Ensure this is the function intended for directory processing
3
+ from image_processor import process_images
4
+
5
+
6
+ def main():
7
+ parser = argparse.ArgumentParser(
8
+ description="Process multiple images from a directory with options for cropping, background removal, resizing, and adding a background.")
9
+ parser.add_argument("-i", "--input_dir", type=str, required=True,
10
+ help="Directory containing the images to be processed.")
11
+ parser.add_argument("-o", "--output_dir", type=str, required=True,
12
+ help="Directory where processed images will be saved.")
13
+ parser.add_argument("-c", "--crop", action="store_true",
14
+ help="Crop the images.")
15
+ parser.add_argument("-b", "--background_removal", action="store_true",
16
+ help="Remove the background from the images.")
17
+ parser.add_argument("-r", "--resize", type=str,
18
+ help="Resize the image to fit within AxB pixels while maintaining aspect ratio. Format: 'AxB'")
19
+ parser.add_argument("-p", "--padding", type=int, default=0,
20
+ help="Number of padding pixels to add around the image.")
21
+ parser.add_argument("-bg", "--background", type=str, default=None,
22
+ help="Add a background to the image. Accepts color names, hex codes, or paths to image files.")
23
+
24
+ args = parser.parse_args()
25
+
26
+ resize_dimensions = None
27
+ if args.resize:
28
+ try:
29
+ width, height = map(int, args.resize.split('x'))
30
+ resize_dimensions = (width, height)
31
+ except ValueError:
32
+ print("Invalid format for resize dimensions. Please use 'AxB'.")
33
+ return
34
+
35
+ process_images(
36
+ input_dir=args.input_dir,
37
+ output_dir=args.output_dir,
38
+ crop=args.crop,
39
+ remove_bg=args.background_removal,
40
+ resize=resize_dimensions,
41
+ padding=args.padding,
42
+ background=args.background
43
+ )
44
+
45
+
46
+ if __name__ == "__main__":
47
+ main()
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ rembg
2
+ pillow