Skip to main content
Glama
image_utils.py7.91 kB
from typing import Tuple, Optional import base64 from PIL import Image from io import BytesIO import logging from ..config.constants import SUPPORTED_IMAGE_TYPES from ..core.exceptions import ImageProcessingError, ValidationError def validate_image_format(mime_type: str) -> bool: """Validate that the MIME type is supported.""" return mime_type.lower() in SUPPORTED_IMAGE_TYPES def get_image_dimensions(image_b64: str) -> Tuple[int, int]: """Get image dimensions from base64 data.""" try: image_data = base64.b64decode(image_b64) image = Image.open(BytesIO(image_data)) return image.size except Exception as e: logging.error(f"Failed to get image dimensions: {e}") raise ValidationError(f"Invalid image data: {e}") def get_image_info(image_b64: str) -> dict: """Get comprehensive image information from base64 data.""" try: image_data = base64.b64decode(image_b64) image = Image.open(BytesIO(image_data)) return { "format": image.format, "mode": image.mode, "size": image.size, "width": image.width, "height": image.height, "size_bytes": len(image_data), "aspect_ratio": round(image.width / image.height, 2), "color_mode": image.mode, "has_transparency": image.mode in ("RGBA", "LA") or "transparency" in image.info, } except Exception as e: logging.error(f"Failed to get image info: {e}") raise ValidationError(f"Invalid image data: {e}") def optimize_image_size(image_b64: str, max_size: int = 20 * 1024 * 1024) -> str: """Optimize image size if it exceeds the maximum limit.""" try: image_data = base64.b64decode(image_b64) if len(image_data) <= max_size: return image_b64 # Implement basic compression by reducing quality/size image = Image.open(BytesIO(image_data)) # Calculate scale factor to reduce size scale_factor = (max_size * 0.8 / len(image_data)) ** 0.5 new_width = int(image.width * scale_factor) new_height = int(image.height * scale_factor) # Resize image resized_image = image.resize((new_width, new_height), Image.Resampling.LANCZOS) # Save with compression output = BytesIO() if image.format == "JPEG": resized_image.save(output, format="JPEG", quality=85, optimize=True) else: resized_image.save(output, format="PNG", optimize=True) output.seek(0) optimized_data = output.read() # If still too large, raise error if len(optimized_data) > max_size: raise ValidationError( f"Image size {len(optimized_data)} still exceeds maximum {max_size} after optimization" ) return base64.b64encode(optimized_data).decode() except Exception as e: logging.error(f"Failed to optimize image size: {e}") raise ImageProcessingError(f"Image optimization failed: {e}") def convert_image_format(image_b64: str, target_format: str = "PNG") -> str: """Convert image to specified format.""" try: image_data = base64.b64decode(image_b64) image = Image.open(BytesIO(image_data)) # Handle transparency for JPEG conversion if target_format.upper() == "JPEG" and image.mode in ("RGBA", "LA"): # Create white background for JPEG background = Image.new("RGB", image.size, (255, 255, 255)) if image.mode == "RGBA": background.paste(image, mask=image.split()[-1]) # Use alpha channel as mask else: background.paste(image) image = background output = BytesIO() image.save(output, format=target_format.upper()) output.seek(0) return base64.b64encode(output.read()).decode() except Exception as e: logging.error(f"Failed to convert image format: {e}") raise ImageProcessingError(f"Image format conversion failed: {e}") def create_thumbnail(source_path: str, thumb_path: str, size: int = 256) -> None: """ Create a thumbnail from an image file, saving to disk. Args: source_path: Path to source image file thumb_path: Path where thumbnail should be saved size: Maximum thumbnail size (maintains aspect ratio) """ try: with Image.open(source_path) as image: # Create thumbnail maintaining aspect ratio image.thumbnail((size, size), Image.Resampling.LANCZOS) # Convert to RGB for JPEG if necessary if image.mode in ("RGBA", "LA", "P"): rgb_image = Image.new("RGB", image.size, (255, 255, 255)) if image.mode == "P": image = image.convert("RGBA") if image.mode in ("RGBA", "LA"): rgb_image.paste(image, mask=image.split()[-1]) else: rgb_image.paste(image) image = rgb_image # Save as JPEG for smaller file size image.save(thumb_path, format="JPEG", quality=85, optimize=True) except Exception as e: logging.error(f"Failed to create thumbnail {source_path} -> {thumb_path}: {e}") raise ImageProcessingError(f"Thumbnail creation failed: {e}") def create_thumbnail_base64(image_b64: str, size: Tuple[int, int] = (256, 256)) -> str: """Create a thumbnail from base64 image data.""" try: image_data = base64.b64decode(image_b64) image = Image.open(BytesIO(image_data)) # Create thumbnail maintaining aspect ratio image.thumbnail(size, Image.Resampling.LANCZOS) output = BytesIO() # Use PNG for thumbnails to preserve quality image.save(output, format="PNG", optimize=True) output.seek(0) return base64.b64encode(output.read()).decode() except Exception as e: logging.error(f"Failed to create thumbnail: {e}") raise ImageProcessingError(f"Thumbnail creation failed: {e}") def estimate_compression_ratio(original_b64: str, compressed_b64: str) -> float: """Estimate compression ratio between original and compressed images.""" try: original_size = len(base64.b64decode(original_b64)) compressed_size = len(base64.b64decode(compressed_b64)) return compressed_size / original_size if original_size > 0 else 1.0 except Exception as e: logging.error(f"Failed to estimate compression ratio: {e}") return 1.0 def validate_image_content(image_b64: str, mime_type: str) -> bool: """Validate that image content matches the declared MIME type.""" try: image_data = base64.b64decode(image_b64) image = Image.open(BytesIO(image_data)) # Map PIL formats to MIME types format_mapping = { "JPEG": ["image/jpeg", "image/jpg"], "PNG": ["image/png"], "WEBP": ["image/webp"], "GIF": ["image/gif"], } expected_mimes = format_mapping.get(image.format, []) return mime_type.lower() in expected_mimes except Exception as e: logging.error(f"Failed to validate image content: {e}") return False def detect_image_type(image_b64: str) -> Optional[str]: """Detect the actual image type from base64 data.""" try: image_data = base64.b64decode(image_b64) image = Image.open(BytesIO(image_data)) # Map PIL formats to MIME types format_mapping = { "JPEG": "image/jpeg", "PNG": "image/png", "WEBP": "image/webp", "GIF": "image/gif", } return format_mapping.get(image.format) except Exception as e: logging.error(f"Failed to detect image type: {e}") return None

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zhongweili/nanobanana-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server