Spaces:
Sleeping
Sleeping
| 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 | |
| ) |