Spaces:
Sleeping
Sleeping
import gradio as gr | |
import PIL | |
from PIL import Image | |
import os | |
import tempfile | |
import zipfile | |
import shutil | |
from pathlib import Path | |
import numpy as np | |
# Define target sizes for different viewports | |
SIZES = [ | |
(480, 423), | |
(696, 613), | |
(960, 846), | |
(1095, 965), | |
(1280, 1128), | |
(1440, 1269), | |
(1670, 1472), | |
(1870, 1648), | |
(2048, 1805) | |
] | |
def resize_image(img, target_size): | |
"""Resize image maintaining aspect ratio""" | |
img_copy = img.copy() | |
return img_copy.resize(target_size, PIL.Image.Resampling.LANCZOS) | |
def generate_html_snippet(image_path_prefix): | |
"""Generate HTML code snippet with srcset""" | |
srcset_items = [f"{image_path_prefix}-{w}x{h}.jpg {w}w" for w, h in SIZES] | |
srcset_str = ",\n ".join(srcset_items) | |
sizes_items = [ | |
f"(max-width: {w}px) {w}px" for w, _ in SIZES[:-1] | |
] + [f"{SIZES[-1][0]}px"] | |
sizes_str = ",\n ".join(sizes_items) | |
# Using min-width for better responsive behavior | |
# sizes_str = "(min-width: 1000px) 45vw, (min-width: 780px) 696px, (min-width: 580px) 480px, calc(100vw - 60px)" | |
# Use the middle size as default src | |
default_size = SIZES[4] # 1280x1128 | |
html = f'''<!-- Responsive Image --> | |
<img | |
src="{image_path_prefix}-{default_size[0]}x{default_size[1]}.jpg" | |
srcset=" | |
{srcset_str} | |
" | |
sizes=" | |
{sizes_str} | |
" | |
alt="Your image description" | |
style="max-width: 100%; height: auto;" | |
>''' | |
return html | |
def get_path_components(filepath): | |
"""Split filepath into directory and filename""" | |
dirpath = os.path.dirname(filepath) | |
filename = os.path.basename(filepath) | |
base_filename = os.path.splitext(filename)[0] | |
return dirpath, base_filename | |
def process_image(input_img, input_path): | |
"""Main processing function""" | |
if input_img is None: | |
return None, None, "Please upload an image" | |
# Create temporary directory for processed images | |
temp_dir = tempfile.mkdtemp() | |
zip_path = os.path.join(temp_dir, "responsive_images.zip") | |
processed_paths = [] # Initialize the list outside try block | |
try: | |
# Get path components | |
if input_path and input_path.strip(): | |
dirpath, base_filename = get_path_components(input_path.strip()) | |
else: | |
# If no path provided, use default | |
dirpath, base_filename = "", "image" | |
# Convert to PIL Image if needed | |
if isinstance(input_img, np.ndarray): | |
img = Image.fromarray(input_img) | |
else: | |
img = input_img | |
# Convert to RGB if necessary | |
if img.mode != 'RGB': | |
img = img.convert('RGB') | |
# Process each size | |
for width, height in SIZES: | |
# For zip file, only use filename without path | |
output_path = os.path.join(temp_dir, f"{base_filename}-{width}x{height}.jpg") | |
resized = resize_image(img, (width, height)) | |
resized.save(output_path, "JPEG", quality=90) | |
processed_paths.append(output_path) | |
# Create zip file | |
with zipfile.ZipFile(zip_path, 'w') as zf: | |
for path in processed_paths: | |
zf.write(path, os.path.basename(path)) | |
# Generate HTML snippet using full path | |
full_path = dirpath + ("/" if dirpath else "") + base_filename | |
html_snippet = generate_html_snippet(full_path) | |
return zip_path, html_snippet, "Processing completed successfully!" | |
except Exception as e: | |
if os.path.exists(zip_path): | |
try: | |
os.remove(zip_path) | |
except: | |
pass | |
return None, None, f"Error processing image: {str(e)}" | |
finally: | |
# Clean up temporary files except zip | |
for path in processed_paths: | |
try: | |
os.remove(path) | |
except: | |
pass | |
# Try to remove the temp directory if it's empty | |
try: | |
os.rmdir(temp_dir) | |
except: | |
pass | |
# Create Gradio interface | |
with gr.Blocks(title="Responsive Image Generator") as app: | |
gr.Markdown(""" | |
# Responsive Image Generator | |
Upload an image to generate optimized versions for different viewport sizes. | |
You'll receive: | |
1. A ZIP file containing all sized versions | |
2. HTML code snippet with proper srcset attributes | |
Optional: Specify the full path where images will be stored (e.g., 'assets/images/about/mock-up.png') | |
""") | |
with gr.Row(): | |
with gr.Column(): | |
with gr.Row(): | |
input_image = gr.Image( | |
label="Upload Original Image", | |
type="pil", | |
show_label=True | |
) | |
with gr.Row(): | |
input_path = gr.Textbox( | |
label="Image Path (optional)", | |
placeholder="e.g., assets/images/about/mock-up.png", | |
value="" | |
) | |
process_btn = gr.Button("Process Image") | |
with gr.Column(): | |
output_zip = gr.File(label="Download Processed Images") | |
output_html = gr.Code( | |
label="HTML Code Snippet", | |
language="html" | |
) | |
output_message = gr.Textbox(label="Status") | |
def handle_upload(img): | |
"""Handle image upload to get filename""" | |
if isinstance(img, dict) and 'name' in img: | |
return img['name'] | |
return "" | |
# Update filename when image is uploaded | |
input_image.upload( | |
fn=handle_upload, | |
inputs=[input_image], | |
outputs=[input_path] | |
) | |
# Process button click | |
process_btn.click( | |
fn=process_image, | |
inputs=[input_image, input_path], | |
outputs=[output_zip, output_html, output_message] | |
) | |
# Launch the app with share=True for public access | |
app.launch(share=True) |