any2any-img / app.py
namelessai's picture
Update app.py
2861e5c verified
import gradio as gr
from PIL import Image
import io
import os
from pathlib import Path
from typing import Optional, Tuple
import json
# Supported formats
INPUT_FORMATS = ["PNG", "JPG", "JPEG", "WEBP", "BMP", "GIF", "TIFF", "ICO"]
OUTPUT_FORMATS = ["PNG", "JPG", "JPEG", "WEBP", "BMP", "GIF", "TIFF", "ICO", "PDF"]
def convert_image(
input_image: Image.Image,
output_format: str,
quality: int = 95,
resize_width: Optional[int] = None,
resize_height: Optional[int] = None,
maintain_aspect: bool = True
) -> Tuple[str, dict]:
"""
Convert image to specified format with optional resizing.
Args:
input_image: PIL Image object
output_format: Target format (PNG, JPG, etc.)
quality: Quality for lossy formats (1-100)
resize_width: Optional width for resizing
resize_height: Optional height for resizing
maintain_aspect: Whether to maintain aspect ratio
Returns:
Tuple of (output_path, metadata_dict)
"""
if input_image is None:
return None, {"error": "No image provided"}
try:
# Get original dimensions
orig_width, orig_height = input_image.size
# Handle resizing if requested
if resize_width or resize_height:
if maintain_aspect:
# Calculate aspect ratio
aspect = orig_width / orig_height
if resize_width and not resize_height:
new_width = resize_width
new_height = int(resize_width / aspect)
elif resize_height and not resize_width:
new_height = resize_height
new_width = int(resize_height * aspect)
else:
# Both specified, fit within bounds
new_width = resize_width
new_height = resize_height
width_ratio = resize_width / orig_width
height_ratio = resize_height / orig_height
ratio = min(width_ratio, height_ratio)
new_width = int(orig_width * ratio)
new_height = int(orig_height * ratio)
else:
new_width = resize_width or orig_width
new_height = resize_height or orig_height
input_image = input_image.resize((new_width, new_height), Image.Resampling.LANCZOS)
# Convert RGBA to RGB for formats that don't support transparency
if output_format.upper() in ["JPG", "JPEG"] and input_image.mode in ["RGBA", "LA", "P"]:
# Create white background
background = Image.new("RGB", input_image.size, (255, 255, 255))
if input_image.mode == "P":
input_image = input_image.convert("RGBA")
background.paste(input_image, mask=input_image.split()[-1] if input_image.mode in ["RGBA", "LA"] else None)
input_image = background
# Prepare output
output_buffer = io.BytesIO()
output_format_upper = output_format.upper()
# Handle format-specific options
save_kwargs = {}
if output_format_upper in ["JPG", "JPEG"]:
save_kwargs["quality"] = quality
save_kwargs["optimize"] = True
output_format_upper = "JPEG"
elif output_format_upper == "PNG":
save_kwargs["optimize"] = True
elif output_format_upper == "WEBP":
save_kwargs["quality"] = quality
elif output_format_upper == "GIF":
if input_image.mode not in ["P", "L"]:
input_image = input_image.convert("P", palette=Image.ADAPTIVE)
# Save to buffer
input_image.save(output_buffer, format=output_format_upper, **save_kwargs)
output_buffer.seek(0)
# Save to temporary file
output_filename = f"converted.{output_format.lower()}"
output_path = os.path.join("/tmp", output_filename)
with open(output_path, "wb") as f:
f.write(output_buffer.getvalue())
# Prepare metadata
metadata = {
"original_size": f"{orig_width}x{orig_height}",
"output_size": f"{input_image.size[0]}x{input_image.size[1]}",
"original_mode": input_image.mode,
"output_format": output_format_upper,
"file_size_bytes": len(output_buffer.getvalue()),
"file_size_kb": round(len(output_buffer.getvalue()) / 1024, 2),
"quality": quality if output_format_upper in ["JPEG", "WEBP"] else "N/A"
}
return output_path, metadata
except Exception as e:
return None, {"error": str(e)}
def process_conversion(
input_image,
output_format,
quality,
resize_width,
resize_height,
maintain_aspect
):
"""Gradio interface wrapper for convert_image."""
if input_image is None:
return None, "❌ Please upload an image first.", {}
# Convert resize inputs (handle empty strings)
width = int(resize_width) if resize_width else None
height = int(resize_height) if resize_height else None
output_path, metadata = convert_image(
input_image,
output_format,
quality,
width,
height,
maintain_aspect
)
if output_path:
metadata_str = f"""
βœ… **Conversion Successful!**
πŸ“Š **Metadata:**
- Original Size: {metadata['original_size']}
- Output Size: {metadata['output_size']}
- Original Mode: {metadata['original_mode']}
- Output Format: {metadata['output_format']}
- File Size: {metadata['file_size_kb']} KB
- Quality: {metadata['quality']}
"""
return output_path, metadata_str, metadata
else:
error_msg = f"❌ **Conversion Failed:**\n{metadata.get('error', 'Unknown error')}"
return None, error_msg, metadata
# MCP Server Configuration
MCP_CONFIG = {
"name": "image-converter",
"version": "1.0.0",
"description": "Convert images between various formats with quality and resize options",
"tools": [
{
"name": "convert_image",
"description": "Convert an image from one format to another with optional resizing",
"parameters": {
"input_image": "PIL Image or path to image file",
"output_format": f"Target format. Supported: {', '.join(OUTPUT_FORMATS)}",
"quality": "Quality for lossy formats (1-100, default: 95)",
"resize_width": "Optional width for resizing (pixels)",
"resize_height": "Optional height for resizing (pixels)",
"maintain_aspect": "Maintain aspect ratio when resizing (default: True)"
}
}
],
"supported_formats": {
"input": INPUT_FORMATS,
"output": OUTPUT_FORMATS
}
}
def create_interface():
"""Create and configure the Gradio interface."""
with gr.Blocks(title="Image Format Converter", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# πŸ–ΌοΈ Image Format Converter
Convert images between any format with quality control and resizing options.
**Supported Formats:** PNG, JPG/JPEG, WEBP, BMP, GIF, TIFF, ICO, PDF
""")
with gr.Row():
with gr.Column(scale=1):
input_image = gr.Image(
label="Upload Image",
type="pil",
sources=["upload", "clipboard"]
)
output_format = gr.Dropdown(
choices=OUTPUT_FORMATS,
value="PNG",
label="Output Format"
)
quality = gr.Slider(
minimum=1,
maximum=100,
value=95,
step=1,
label="Quality (for JPG/WEBP)",
info="Higher quality = larger file size"
)
with gr.Accordion("Resize Options", open=False):
maintain_aspect = gr.Checkbox(
label="Maintain Aspect Ratio",
value=True
)
with gr.Row():
resize_width = gr.Number(
label="Width (px)",
precision=0,
value=None
)
resize_height = gr.Number(
label="Height (px)",
precision=0,
value=None
)
convert_btn = gr.Button("πŸ”„ Convert", variant="primary", size="lg")
with gr.Column(scale=1):
output_file = gr.File(label="Download Converted Image")
metadata_display = gr.Markdown(label="Conversion Info")
# Hidden component for metadata JSON (useful for MCP)
metadata_json = gr.JSON(visible=False)
# Connect components
convert_btn.click(
fn=process_conversion,
inputs=[
input_image,
output_format,
quality,
resize_width,
resize_height,
maintain_aspect
],
outputs=[output_file, metadata_display, metadata_json]
)
# MCP Info
with gr.Accordion("πŸ”§ MCP Configuration", open=False):
gr.JSON(value=MCP_CONFIG, label="MCP Server Config")
gr.Markdown("""
### Using with MCP
This app is designed to work with Model Context Protocol (MCP) servers.
**Key Features:**
- Programmatic access via `convert_image()` function
- Structured metadata output
- Standard PIL Image input/output
- Configuration exposed via `MCP_CONFIG`
**Example MCP Tool Definition:**
```json
{
"name": "convert_image",
"description": "Convert image format",
"parameters": {
"input_image": "string (path or PIL Image)",
"output_format": "string (PNG, JPG, WEBP, etc.)",
"quality": "integer (1-100)"
}
}
```
""")
return demo
if __name__ == "__main__":
# Create and launch the interface
demo = create_interface()
demo.launch(
server_name="0.0.0.0",
server_port=7860,
mcp_server=True
)