|
import gradio as gr |
|
import os |
|
import zipfile |
|
import tempfile |
|
import shutil |
|
from PIL import Image |
|
import io |
|
from typing import List, Tuple, Optional |
|
import numpy as np |
|
|
|
class ImageResizer: |
|
def __init__(self): |
|
self.supported_formats = ('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp') |
|
|
|
def get_background_color(self, image: Image.Image) -> tuple: |
|
""" |
|
Smart background color detection from image corners. |
|
Samples 10x10 pixel areas from all four corners and calculates average. |
|
""" |
|
width, height = image.size |
|
|
|
|
|
corner_size = min(10, width // 4, height // 4) |
|
|
|
corners = [ |
|
(0, 0, corner_size, corner_size), |
|
(width - corner_size, 0, width, corner_size), |
|
(0, height - corner_size, corner_size, height), |
|
(width - corner_size, height - corner_size, width, height) |
|
] |
|
|
|
total_r, total_g, total_b = 0, 0, 0 |
|
total_pixels = 0 |
|
|
|
for corner in corners: |
|
corner_region = image.crop(corner) |
|
|
|
if corner_region.mode != 'RGB': |
|
corner_region = corner_region.convert('RGB') |
|
|
|
|
|
pixels = list(corner_region.getdata()) |
|
for r, g, b in pixels: |
|
total_r += r |
|
total_g += g |
|
total_b += b |
|
total_pixels += 1 |
|
|
|
if total_pixels > 0: |
|
avg_r = total_r // total_pixels |
|
avg_g = total_g // total_pixels |
|
avg_b = total_b // total_pixels |
|
return (avg_r, avg_g, avg_b) |
|
else: |
|
return (255, 255, 255) |
|
|
|
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: |
|
""" |
|
Resize image using smart canvas padding instead of traditional resizing. |
|
""" |
|
original_width, original_height = image.size |
|
|
|
|
|
if maintain_aspect: |
|
|
|
original_aspect = original_width / original_height |
|
target_aspect = width / height |
|
|
|
if original_aspect > target_aspect: |
|
|
|
new_width = width |
|
new_height = int(width / original_aspect) |
|
else: |
|
|
|
new_height = height |
|
new_width = int(height * original_aspect) |
|
else: |
|
new_width = width |
|
new_height = height |
|
|
|
|
|
if new_width < original_width or new_height < original_height: |
|
|
|
resized_image = image.resize((new_width, new_height), Image.Resampling.LANCZOS) |
|
else: |
|
|
|
resized_image = image.copy() |
|
new_width, new_height = resized_image.size |
|
|
|
|
|
canvas = Image.new('RGB', (width, height), (255, 255, 255)) |
|
|
|
|
|
if is_png or (hasattr(image, 'format') and image.format == 'PNG'): |
|
if png_bg_option == "auto": |
|
bg_color = self.get_background_color(image) |
|
elif png_bg_option == "black": |
|
bg_color = (0, 0, 0) |
|
elif png_bg_option == "white": |
|
bg_color = (255, 255, 255) |
|
elif png_bg_option == "custom" and custom_color: |
|
bg_color = custom_color |
|
else: |
|
bg_color = (255, 255, 255) |
|
|
|
canvas = Image.new('RGB', (width, height), bg_color) |
|
|
|
|
|
x = (width - new_width) // 2 |
|
y = (height - new_height) // 2 |
|
|
|
|
|
if resized_image.mode == 'RGBA': |
|
canvas.paste(resized_image, (x, y), resized_image) |
|
else: |
|
canvas.paste(resized_image, (x, y)) |
|
|
|
return canvas |
|
|
|
def hex_to_rgb(self, hex_color: str) -> tuple: |
|
"""Convert hex color to RGB tuple.""" |
|
hex_color = hex_color.lstrip('#') |
|
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) |
|
|
|
def process_single_image(image, width, height, maintain_aspect, png_bg_option, custom_color_hex): |
|
"""Process a single image with Gradio interface.""" |
|
if image is None: |
|
return None, "Please upload an image first." |
|
|
|
try: |
|
|
|
if isinstance(image, np.ndarray): |
|
pil_image = Image.fromarray(image) |
|
else: |
|
pil_image = image |
|
|
|
|
|
if width <= 0 or height <= 0: |
|
return None, "Width and height must be positive numbers." |
|
|
|
|
|
resizer = ImageResizer() |
|
|
|
|
|
custom_color = None |
|
if png_bg_option == "custom": |
|
try: |
|
custom_color = resizer.hex_to_rgb(custom_color_hex) |
|
except: |
|
custom_color = (255, 255, 255) |
|
|
|
|
|
is_png = hasattr(pil_image, 'format') and pil_image.format == 'PNG' |
|
|
|
|
|
resized_image = resizer.resize_image( |
|
pil_image, width, height, maintain_aspect, |
|
png_bg_option, custom_color, is_png |
|
) |
|
|
|
return resized_image, f"β
Image resized successfully to {width}x{height}!" |
|
|
|
except Exception as e: |
|
return None, f"β Error processing image: {str(e)}" |
|
|
|
def process_folder_images(files, width, height, maintain_aspect, png_bg_option, custom_color_hex): |
|
"""Process multiple images from folder upload.""" |
|
if not files: |
|
return [], "Please upload image files first." |
|
|
|
try: |
|
|
|
if width <= 0 or height <= 0: |
|
return [], "Width and height must be positive numbers." |
|
|
|
|
|
resizer = ImageResizer() |
|
|
|
|
|
custom_color = None |
|
if png_bg_option == "custom": |
|
try: |
|
custom_color = resizer.hex_to_rgb(custom_color_hex) |
|
except: |
|
custom_color = (255, 255, 255) |
|
|
|
processed_images = [] |
|
processed_count = 0 |
|
|
|
for file in files: |
|
try: |
|
|
|
image = Image.open(file.name) |
|
|
|
|
|
is_png = file.name.lower().endswith('.png') |
|
|
|
|
|
resized_image = resizer.resize_image( |
|
image, width, height, maintain_aspect, |
|
png_bg_option, custom_color, is_png |
|
) |
|
|
|
processed_images.append(resized_image) |
|
processed_count += 1 |
|
|
|
except Exception as e: |
|
print(f"Error processing {file.name}: {str(e)}") |
|
continue |
|
|
|
status_msg = f"β
Successfully processed {processed_count} out of {len(files)} images to {width}x{height}!" |
|
return processed_images, status_msg |
|
|
|
except Exception as e: |
|
return [], f"β Error processing folder: {str(e)}" |
|
|
|
def process_zip_file(zip_file, width, height, maintain_aspect, png_bg_option, custom_color_hex): |
|
"""Process images from uploaded ZIP file.""" |
|
if zip_file is None: |
|
return [], "Please upload a ZIP file first." |
|
|
|
try: |
|
|
|
if width <= 0 or height <= 0: |
|
return [], "Width and height must be positive numbers." |
|
|
|
|
|
resizer = ImageResizer() |
|
|
|
|
|
custom_color = None |
|
if png_bg_option == "custom": |
|
try: |
|
custom_color = resizer.hex_to_rgb(custom_color_hex) |
|
except: |
|
custom_color = (255, 255, 255) |
|
|
|
processed_images = [] |
|
processed_count = 0 |
|
|
|
|
|
with tempfile.TemporaryDirectory() as temp_dir: |
|
|
|
with zipfile.ZipFile(zip_file.name, 'r') as zip_ref: |
|
zip_ref.extractall(temp_dir) |
|
|
|
|
|
for root, dirs, files in os.walk(temp_dir): |
|
for file in files: |
|
if file.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp')): |
|
try: |
|
file_path = os.path.join(root, file) |
|
image = Image.open(file_path) |
|
|
|
|
|
is_png = file.lower().endswith('.png') |
|
|
|
|
|
resized_image = resizer.resize_image( |
|
image, width, height, maintain_aspect, |
|
png_bg_option, custom_color, is_png |
|
) |
|
|
|
processed_images.append(resized_image) |
|
processed_count += 1 |
|
|
|
except Exception as e: |
|
print(f"Error processing {file}: {str(e)}") |
|
continue |
|
|
|
status_msg = f"β
Successfully processed {processed_count} images from ZIP file to {width}x{height}!" |
|
return processed_images, status_msg |
|
|
|
except Exception as e: |
|
return [], f"β Error processing ZIP file: {str(e)}" |
|
|
|
|
|
def create_gradio_app(): |
|
with gr.Blocks(title="πΌοΈ Image Resizer Pro - Gradio Edition", theme=gr.themes.Soft()) as app: |
|
gr.Markdown("# πΌοΈ Image Resizer Pro - Gradio Edition") |
|
gr.Markdown("**Smart Canvas Padding & PNG Background Selector** - Resize images with intelligent padding instead of stretching") |
|
|
|
with gr.Tabs(): |
|
|
|
with gr.TabItem("π· Single Image"): |
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
single_image_input = gr.Image(type="pil", label="Upload Image") |
|
|
|
with gr.Row(): |
|
single_width = gr.Number(value=800, label="Width", minimum=1, maximum=10000) |
|
single_height = gr.Number(value=600, label="Height", minimum=1, maximum=10000) |
|
|
|
single_maintain_aspect = gr.Checkbox(value=True, label="Maintain aspect ratio") |
|
|
|
with gr.Group(): |
|
gr.Markdown("**PNG Background Color**") |
|
single_png_bg = gr.Radio( |
|
choices=["auto", "black", "white", "custom"], |
|
value="auto", |
|
label="Background Option", |
|
info="Choose background color for PNG images" |
|
) |
|
single_custom_color = gr.Textbox( |
|
value="#FFFFFF", |
|
label="Custom Color (Hex)", |
|
placeholder="#FFFFFF", |
|
visible=False |
|
) |
|
|
|
single_process_btn = gr.Button("π Resize Image", variant="primary") |
|
|
|
with gr.Column(scale=1): |
|
single_output = gr.Image(label="Resized Image") |
|
single_status = gr.Textbox(label="Status", interactive=False) |
|
|
|
|
|
single_png_bg.change( |
|
lambda x: gr.update(visible=(x == "custom")), |
|
inputs=[single_png_bg], |
|
outputs=[single_custom_color] |
|
) |
|
|
|
|
|
single_process_btn.click( |
|
process_single_image, |
|
inputs=[single_image_input, single_width, single_height, single_maintain_aspect, single_png_bg, single_custom_color], |
|
outputs=[single_output, single_status] |
|
) |
|
|
|
|
|
with gr.TabItem("π Folder Processing"): |
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
folder_files_input = gr.File( |
|
file_count="multiple", |
|
file_types=["image"], |
|
label="Upload Multiple Images" |
|
) |
|
|
|
with gr.Row(): |
|
folder_width = gr.Number(value=800, label="Width", minimum=1, maximum=10000) |
|
folder_height = gr.Number(value=600, label="Height", minimum=1, maximum=10000) |
|
|
|
folder_maintain_aspect = gr.Checkbox(value=True, label="Maintain aspect ratio") |
|
|
|
with gr.Group(): |
|
gr.Markdown("**PNG Background Color**") |
|
folder_png_bg = gr.Radio( |
|
choices=["auto", "black", "white", "custom"], |
|
value="auto", |
|
label="Background Option", |
|
info="Choose background color for PNG images" |
|
) |
|
folder_custom_color = gr.Textbox( |
|
value="#FFFFFF", |
|
label="Custom Color (Hex)", |
|
placeholder="#FFFFFF", |
|
visible=False |
|
) |
|
|
|
folder_process_btn = gr.Button("π Process Folder", variant="primary") |
|
|
|
with gr.Column(scale=1): |
|
folder_output = gr.Gallery(label="Processed Images", columns=3, rows=2) |
|
folder_status = gr.Textbox(label="Status", interactive=False) |
|
|
|
|
|
folder_png_bg.change( |
|
lambda x: gr.update(visible=(x == "custom")), |
|
inputs=[folder_png_bg], |
|
outputs=[folder_custom_color] |
|
) |
|
|
|
|
|
folder_process_btn.click( |
|
process_folder_images, |
|
inputs=[folder_files_input, folder_width, folder_height, folder_maintain_aspect, folder_png_bg, folder_custom_color], |
|
outputs=[folder_output, folder_status] |
|
) |
|
|
|
|
|
with gr.TabItem("π¦ ZIP Processing"): |
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
zip_file_input = gr.File( |
|
file_types=[".zip"], |
|
label="Upload ZIP File" |
|
) |
|
|
|
with gr.Row(): |
|
zip_width = gr.Number(value=800, label="Width", minimum=1, maximum=10000) |
|
zip_height = gr.Number(value=600, label="Height", minimum=1, maximum=10000) |
|
|
|
zip_maintain_aspect = gr.Checkbox(value=True, label="Maintain aspect ratio") |
|
|
|
with gr.Group(): |
|
gr.Markdown("**PNG Background Color**") |
|
zip_png_bg = gr.Radio( |
|
choices=["auto", "black", "white", "custom"], |
|
value="auto", |
|
label="Background Option", |
|
info="Choose background color for PNG images" |
|
) |
|
zip_custom_color = gr.Textbox( |
|
value="#FFFFFF", |
|
label="Custom Color (Hex)", |
|
placeholder="#FFFFFF", |
|
visible=False |
|
) |
|
|
|
zip_process_btn = gr.Button("π Process ZIP", variant="primary") |
|
|
|
with gr.Column(scale=1): |
|
zip_output = gr.Gallery(label="Processed Images", columns=3, rows=2) |
|
zip_status = gr.Textbox(label="Status", interactive=False) |
|
|
|
|
|
zip_png_bg.change( |
|
lambda x: gr.update(visible=(x == "custom")), |
|
inputs=[zip_png_bg], |
|
outputs=[zip_custom_color] |
|
) |
|
|
|
|
|
zip_process_btn.click( |
|
process_zip_file, |
|
inputs=[zip_file_input, zip_width, zip_height, zip_maintain_aspect, zip_png_bg, zip_custom_color], |
|
outputs=[zip_output, zip_status] |
|
) |
|
|
|
|
|
gr.Markdown("---") |
|
gr.Markdown("**πΌοΈ Image Resizer Pro v3.2** - Smart Canvas Padding & PNG Background Selector Edition") |
|
gr.Markdown("Features: Smart background detection, Canvas padding, Multi-format support (JPG, PNG, BMP, TIFF, WEBP)") |
|
|
|
return app |
|
|
|
if __name__ == "__main__": |
|
app = create_gradio_app() |
|
app.launch( |
|
server_name="0.0.0.0", |
|
server_port=7860, |
|
share=False, |
|
debug=True |
|
) |