dramp77 commited on
Commit
4857b64
Β·
verified Β·
1 Parent(s): 4507af0

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +446 -0
app.py ADDED
@@ -0,0 +1,446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import zipfile
4
+ import tempfile
5
+ import shutil
6
+ from PIL import Image
7
+ import io
8
+ from typing import List, Tuple, Optional
9
+ import numpy as np
10
+
11
+ class ImageResizer:
12
+ def __init__(self):
13
+ self.supported_formats = ('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp')
14
+
15
+ def get_background_color(self, image: Image.Image) -> tuple:
16
+ """
17
+ Smart background color detection from image corners.
18
+ Samples 10x10 pixel areas from all four corners and calculates average.
19
+ """
20
+ width, height = image.size
21
+
22
+ # Define corner regions (10x10 pixels)
23
+ corner_size = min(10, width // 4, height // 4)
24
+
25
+ corners = [
26
+ (0, 0, corner_size, corner_size), # Top-left
27
+ (width - corner_size, 0, width, corner_size), # Top-right
28
+ (0, height - corner_size, corner_size, height), # Bottom-left
29
+ (width - corner_size, height - corner_size, width, height) # Bottom-right
30
+ ]
31
+
32
+ total_r, total_g, total_b = 0, 0, 0
33
+ total_pixels = 0
34
+
35
+ for corner in corners:
36
+ corner_region = image.crop(corner)
37
+ # Convert to RGB if not already
38
+ if corner_region.mode != 'RGB':
39
+ corner_region = corner_region.convert('RGB')
40
+
41
+ # Get average color of this corner
42
+ pixels = list(corner_region.getdata())
43
+ for r, g, b in pixels:
44
+ total_r += r
45
+ total_g += g
46
+ total_b += b
47
+ total_pixels += 1
48
+
49
+ if total_pixels > 0:
50
+ avg_r = total_r // total_pixels
51
+ avg_g = total_g // total_pixels
52
+ avg_b = total_b // total_pixels
53
+ return (avg_r, avg_g, avg_b)
54
+ else:
55
+ return (255, 255, 255) # Default to white
56
+
57
+ def resize_image(self, image: Image.Image, width: int, height: int, maintain_aspect: bool = True, png_bg_option: str = "auto", custom_color: tuple = None, is_png: bool = False) -> Image.Image:
58
+ """
59
+ Resize image using smart canvas padding instead of traditional resizing.
60
+ """
61
+ original_width, original_height = image.size
62
+
63
+ # Calculate target dimensions
64
+ if maintain_aspect:
65
+ # Calculate aspect ratios
66
+ original_aspect = original_width / original_height
67
+ target_aspect = width / height
68
+
69
+ if original_aspect > target_aspect:
70
+ # Image is wider, fit to width
71
+ new_width = width
72
+ new_height = int(width / original_aspect)
73
+ else:
74
+ # Image is taller, fit to height
75
+ new_height = height
76
+ new_width = int(height * original_aspect)
77
+ else:
78
+ new_width = width
79
+ new_height = height
80
+
81
+ # Only resize if the image is larger than target dimensions
82
+ if new_width < original_width or new_height < original_height:
83
+ # Use LANCZOS for high-quality downsampling
84
+ resized_image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
85
+ else:
86
+ # Keep original size if it's smaller than target
87
+ resized_image = image.copy()
88
+ new_width, new_height = resized_image.size
89
+
90
+ # Create canvas with padding
91
+ canvas = Image.new('RGB', (width, height), (255, 255, 255))
92
+
93
+ # Handle PNG background color
94
+ if is_png or (hasattr(image, 'format') and image.format == 'PNG'):
95
+ if png_bg_option == "auto":
96
+ bg_color = self.get_background_color(image)
97
+ elif png_bg_option == "black":
98
+ bg_color = (0, 0, 0)
99
+ elif png_bg_option == "white":
100
+ bg_color = (255, 255, 255)
101
+ elif png_bg_option == "custom" and custom_color:
102
+ bg_color = custom_color
103
+ else:
104
+ bg_color = (255, 255, 255)
105
+
106
+ canvas = Image.new('RGB', (width, height), bg_color)
107
+
108
+ # Calculate position to center the image
109
+ x = (width - new_width) // 2
110
+ y = (height - new_height) // 2
111
+
112
+ # Paste the resized image onto the canvas
113
+ if resized_image.mode == 'RGBA':
114
+ canvas.paste(resized_image, (x, y), resized_image)
115
+ else:
116
+ canvas.paste(resized_image, (x, y))
117
+
118
+ return canvas
119
+
120
+ def hex_to_rgb(self, hex_color: str) -> tuple:
121
+ """Convert hex color to RGB tuple."""
122
+ hex_color = hex_color.lstrip('#')
123
+ return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
124
+
125
+ def process_single_image(image, width, height, maintain_aspect, png_bg_option, custom_color_hex):
126
+ """Process a single image with Gradio interface."""
127
+ if image is None:
128
+ return None, "Please upload an image first."
129
+
130
+ try:
131
+ # Convert Gradio image to PIL Image
132
+ if isinstance(image, np.ndarray):
133
+ pil_image = Image.fromarray(image)
134
+ else:
135
+ pil_image = image
136
+
137
+ # Validate dimensions
138
+ if width <= 0 or height <= 0:
139
+ return None, "Width and height must be positive numbers."
140
+
141
+ # Initialize resizer
142
+ resizer = ImageResizer()
143
+
144
+ # Handle custom color
145
+ custom_color = None
146
+ if png_bg_option == "custom":
147
+ try:
148
+ custom_color = resizer.hex_to_rgb(custom_color_hex)
149
+ except:
150
+ custom_color = (255, 255, 255) # Default to white if invalid
151
+
152
+ # Check if image is PNG
153
+ is_png = hasattr(pil_image, 'format') and pil_image.format == 'PNG'
154
+
155
+ # Resize image
156
+ resized_image = resizer.resize_image(
157
+ pil_image, width, height, maintain_aspect,
158
+ png_bg_option, custom_color, is_png
159
+ )
160
+
161
+ return resized_image, f"βœ… Image resized successfully to {width}x{height}!"
162
+
163
+ except Exception as e:
164
+ return None, f"❌ Error processing image: {str(e)}"
165
+
166
+ def process_folder_images(files, width, height, maintain_aspect, png_bg_option, custom_color_hex):
167
+ """Process multiple images from folder upload."""
168
+ if not files:
169
+ return [], "Please upload image files first."
170
+
171
+ try:
172
+ # Validate dimensions
173
+ if width <= 0 or height <= 0:
174
+ return [], "Width and height must be positive numbers."
175
+
176
+ # Initialize resizer
177
+ resizer = ImageResizer()
178
+
179
+ # Handle custom color
180
+ custom_color = None
181
+ if png_bg_option == "custom":
182
+ try:
183
+ custom_color = resizer.hex_to_rgb(custom_color_hex)
184
+ except:
185
+ custom_color = (255, 255, 255) # Default to white if invalid
186
+
187
+ processed_images = []
188
+ processed_count = 0
189
+
190
+ for file in files:
191
+ try:
192
+ # Open image
193
+ image = Image.open(file.name)
194
+
195
+ # Check if image is PNG
196
+ is_png = file.name.lower().endswith('.png')
197
+
198
+ # Resize image
199
+ resized_image = resizer.resize_image(
200
+ image, width, height, maintain_aspect,
201
+ png_bg_option, custom_color, is_png
202
+ )
203
+
204
+ processed_images.append(resized_image)
205
+ processed_count += 1
206
+
207
+ except Exception as e:
208
+ print(f"Error processing {file.name}: {str(e)}")
209
+ continue
210
+
211
+ status_msg = f"βœ… Successfully processed {processed_count} out of {len(files)} images to {width}x{height}!"
212
+ return processed_images, status_msg
213
+
214
+ except Exception as e:
215
+ return [], f"❌ Error processing folder: {str(e)}"
216
+
217
+ def process_zip_file(zip_file, width, height, maintain_aspect, png_bg_option, custom_color_hex):
218
+ """Process images from uploaded ZIP file."""
219
+ if zip_file is None:
220
+ return [], "Please upload a ZIP file first."
221
+
222
+ try:
223
+ # Validate dimensions
224
+ if width <= 0 or height <= 0:
225
+ return [], "Width and height must be positive numbers."
226
+
227
+ # Initialize resizer
228
+ resizer = ImageResizer()
229
+
230
+ # Handle custom color
231
+ custom_color = None
232
+ if png_bg_option == "custom":
233
+ try:
234
+ custom_color = resizer.hex_to_rgb(custom_color_hex)
235
+ except:
236
+ custom_color = (255, 255, 255) # Default to white if invalid
237
+
238
+ processed_images = []
239
+ processed_count = 0
240
+
241
+ # Create temporary directory
242
+ with tempfile.TemporaryDirectory() as temp_dir:
243
+ # Extract ZIP file
244
+ with zipfile.ZipFile(zip_file.name, 'r') as zip_ref:
245
+ zip_ref.extractall(temp_dir)
246
+
247
+ # Process all images in the extracted folder
248
+ for root, dirs, files in os.walk(temp_dir):
249
+ for file in files:
250
+ if file.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp')):
251
+ try:
252
+ file_path = os.path.join(root, file)
253
+ image = Image.open(file_path)
254
+
255
+ # Check if image is PNG
256
+ is_png = file.lower().endswith('.png')
257
+
258
+ # Resize image
259
+ resized_image = resizer.resize_image(
260
+ image, width, height, maintain_aspect,
261
+ png_bg_option, custom_color, is_png
262
+ )
263
+
264
+ processed_images.append(resized_image)
265
+ processed_count += 1
266
+
267
+ except Exception as e:
268
+ print(f"Error processing {file}: {str(e)}")
269
+ continue
270
+
271
+ status_msg = f"βœ… Successfully processed {processed_count} images from ZIP file to {width}x{height}!"
272
+ return processed_images, status_msg
273
+
274
+ except Exception as e:
275
+ return [], f"❌ Error processing ZIP file: {str(e)}"
276
+
277
+ # Create Gradio interface
278
+ def create_gradio_app():
279
+ with gr.Blocks(title="πŸ–ΌοΈ Image Resizer Pro - Gradio Edition", theme=gr.themes.Soft()) as app:
280
+ gr.Markdown("# πŸ–ΌοΈ Image Resizer Pro - Gradio Edition")
281
+ gr.Markdown("**Smart Canvas Padding & PNG Background Selector** - Resize images with intelligent padding instead of stretching")
282
+
283
+ with gr.Tabs():
284
+ # Single Image Tab
285
+ with gr.TabItem("πŸ“· Single Image"):
286
+ with gr.Row():
287
+ with gr.Column(scale=1):
288
+ single_image_input = gr.Image(type="pil", label="Upload Image")
289
+
290
+ with gr.Row():
291
+ single_width = gr.Number(value=800, label="Width", minimum=1, maximum=10000)
292
+ single_height = gr.Number(value=600, label="Height", minimum=1, maximum=10000)
293
+
294
+ single_maintain_aspect = gr.Checkbox(value=True, label="Maintain aspect ratio")
295
+
296
+ with gr.Group():
297
+ gr.Markdown("**PNG Background Color**")
298
+ single_png_bg = gr.Radio(
299
+ choices=["auto", "black", "white", "custom"],
300
+ value="auto",
301
+ label="Background Option",
302
+ info="Choose background color for PNG images"
303
+ )
304
+ single_custom_color = gr.Textbox(
305
+ value="#FFFFFF",
306
+ label="Custom Color (Hex)",
307
+ placeholder="#FFFFFF",
308
+ visible=False
309
+ )
310
+
311
+ single_process_btn = gr.Button("πŸ”„ Resize Image", variant="primary")
312
+
313
+ with gr.Column(scale=1):
314
+ single_output = gr.Image(label="Resized Image")
315
+ single_status = gr.Textbox(label="Status", interactive=False)
316
+
317
+ # Show/hide custom color input
318
+ single_png_bg.change(
319
+ lambda x: gr.update(visible=(x == "custom")),
320
+ inputs=[single_png_bg],
321
+ outputs=[single_custom_color]
322
+ )
323
+
324
+ # Process single image
325
+ single_process_btn.click(
326
+ process_single_image,
327
+ inputs=[single_image_input, single_width, single_height, single_maintain_aspect, single_png_bg, single_custom_color],
328
+ outputs=[single_output, single_status]
329
+ )
330
+
331
+ # Folder Processing Tab
332
+ with gr.TabItem("πŸ“ Folder Processing"):
333
+ with gr.Row():
334
+ with gr.Column(scale=1):
335
+ folder_files_input = gr.File(
336
+ file_count="multiple",
337
+ file_types=["image"],
338
+ label="Upload Multiple Images"
339
+ )
340
+
341
+ with gr.Row():
342
+ folder_width = gr.Number(value=800, label="Width", minimum=1, maximum=10000)
343
+ folder_height = gr.Number(value=600, label="Height", minimum=1, maximum=10000)
344
+
345
+ folder_maintain_aspect = gr.Checkbox(value=True, label="Maintain aspect ratio")
346
+
347
+ with gr.Group():
348
+ gr.Markdown("**PNG Background Color**")
349
+ folder_png_bg = gr.Radio(
350
+ choices=["auto", "black", "white", "custom"],
351
+ value="auto",
352
+ label="Background Option",
353
+ info="Choose background color for PNG images"
354
+ )
355
+ folder_custom_color = gr.Textbox(
356
+ value="#FFFFFF",
357
+ label="Custom Color (Hex)",
358
+ placeholder="#FFFFFF",
359
+ visible=False
360
+ )
361
+
362
+ folder_process_btn = gr.Button("πŸ”„ Process Folder", variant="primary")
363
+
364
+ with gr.Column(scale=1):
365
+ folder_output = gr.Gallery(label="Processed Images", columns=3, rows=2)
366
+ folder_status = gr.Textbox(label="Status", interactive=False)
367
+
368
+ # Show/hide custom color input
369
+ folder_png_bg.change(
370
+ lambda x: gr.update(visible=(x == "custom")),
371
+ inputs=[folder_png_bg],
372
+ outputs=[folder_custom_color]
373
+ )
374
+
375
+ # Process folder
376
+ folder_process_btn.click(
377
+ process_folder_images,
378
+ inputs=[folder_files_input, folder_width, folder_height, folder_maintain_aspect, folder_png_bg, folder_custom_color],
379
+ outputs=[folder_output, folder_status]
380
+ )
381
+
382
+ # ZIP Processing Tab
383
+ with gr.TabItem("πŸ“¦ ZIP Processing"):
384
+ with gr.Row():
385
+ with gr.Column(scale=1):
386
+ zip_file_input = gr.File(
387
+ file_types=[".zip"],
388
+ label="Upload ZIP File"
389
+ )
390
+
391
+ with gr.Row():
392
+ zip_width = gr.Number(value=800, label="Width", minimum=1, maximum=10000)
393
+ zip_height = gr.Number(value=600, label="Height", minimum=1, maximum=10000)
394
+
395
+ zip_maintain_aspect = gr.Checkbox(value=True, label="Maintain aspect ratio")
396
+
397
+ with gr.Group():
398
+ gr.Markdown("**PNG Background Color**")
399
+ zip_png_bg = gr.Radio(
400
+ choices=["auto", "black", "white", "custom"],
401
+ value="auto",
402
+ label="Background Option",
403
+ info="Choose background color for PNG images"
404
+ )
405
+ zip_custom_color = gr.Textbox(
406
+ value="#FFFFFF",
407
+ label="Custom Color (Hex)",
408
+ placeholder="#FFFFFF",
409
+ visible=False
410
+ )
411
+
412
+ zip_process_btn = gr.Button("πŸ”„ Process ZIP", variant="primary")
413
+
414
+ with gr.Column(scale=1):
415
+ zip_output = gr.Gallery(label="Processed Images", columns=3, rows=2)
416
+ zip_status = gr.Textbox(label="Status", interactive=False)
417
+
418
+ # Show/hide custom color input
419
+ zip_png_bg.change(
420
+ lambda x: gr.update(visible=(x == "custom")),
421
+ inputs=[zip_png_bg],
422
+ outputs=[zip_custom_color]
423
+ )
424
+
425
+ # Process ZIP
426
+ zip_process_btn.click(
427
+ process_zip_file,
428
+ inputs=[zip_file_input, zip_width, zip_height, zip_maintain_aspect, zip_png_bg, zip_custom_color],
429
+ outputs=[zip_output, zip_status]
430
+ )
431
+
432
+ # Footer
433
+ gr.Markdown("---")
434
+ gr.Markdown("**πŸ–ΌοΈ Image Resizer Pro v3.2** - Smart Canvas Padding & PNG Background Selector Edition")
435
+ gr.Markdown("Features: Smart background detection, Canvas padding, Multi-format support (JPG, PNG, BMP, TIFF, WEBP)")
436
+
437
+ return app
438
+
439
+ if __name__ == "__main__":
440
+ app = create_gradio_app()
441
+ app.launch(
442
+ server_name="0.0.0.0",
443
+ server_port=7860,
444
+ share=False,
445
+ debug=True
446
+ )