| | """ |
| | Image processing utilities. |
| | """ |
| |
|
| | from io import BytesIO |
| | from typing import Optional, Tuple |
| |
|
| | from PIL import Image |
| |
|
| | from app.core.errors import ImageProcessingError |
| | from app.core.logging import get_logger |
| |
|
| | logger = get_logger(__name__) |
| |
|
| |
|
| | def load_image_from_bytes(image_bytes: bytes) -> Image.Image: |
| | """ |
| | Load a PIL Image from raw bytes. |
| | |
| | Args: |
| | image_bytes: Raw image bytes |
| | |
| | Returns: |
| | PIL Image object |
| | |
| | Raises: |
| | ImageProcessingError: If image cannot be decoded |
| | """ |
| | try: |
| | image = Image.open(BytesIO(image_bytes)) |
| | |
| | if image.mode != "RGB": |
| | image = image.convert("RGB") |
| | return image |
| | except Exception as e: |
| | logger.error(f"Failed to decode image: {e}") |
| | raise ImageProcessingError( |
| | message="Failed to decode image", |
| | details={"error": str(e)} |
| | ) |
| |
|
| |
|
| | def validate_image_bytes(image_bytes: bytes) -> bool: |
| | """ |
| | Validate that bytes represent a valid image. |
| | |
| | Args: |
| | image_bytes: Raw image bytes |
| | |
| | Returns: |
| | True if valid image, False otherwise |
| | """ |
| | try: |
| | image = Image.open(BytesIO(image_bytes)) |
| | image.verify() |
| | return True |
| | except Exception: |
| | return False |
| |
|
| |
|
| | def get_image_info(image: Image.Image) -> dict: |
| | """ |
| | Get basic information about an image. |
| | |
| | Args: |
| | image: PIL Image object |
| | |
| | Returns: |
| | Dictionary with image info |
| | """ |
| | return { |
| | "width": image.width, |
| | "height": image.height, |
| | "mode": image.mode, |
| | "format": image.format |
| | } |
| |
|
| |
|
| | def resize_image( |
| | image: Image.Image, |
| | size: Tuple[int, int], |
| | resample: int = Image.Resampling.LANCZOS |
| | ) -> Image.Image: |
| | """ |
| | Resize image to specified size. |
| | |
| | Args: |
| | image: PIL Image object |
| | size: Target (width, height) |
| | resample: Resampling filter |
| | |
| | Returns: |
| | Resized PIL Image |
| | """ |
| | return image.resize(size, resample=resample) |
| |
|
| |
|
| | def image_to_bytes( |
| | image: Image.Image, |
| | format: str = "PNG", |
| | quality: int = 95 |
| | ) -> bytes: |
| | """ |
| | Convert PIL Image to bytes. |
| | |
| | Args: |
| | image: PIL Image object |
| | format: Output format (PNG, JPEG, etc.) |
| | quality: JPEG quality (1-95) |
| | |
| | Returns: |
| | Image as bytes |
| | """ |
| | buffer = BytesIO() |
| | if format.upper() == "JPEG": |
| | image.save(buffer, format=format, quality=quality) |
| | else: |
| | image.save(buffer, format=format) |
| | return buffer.getvalue() |
| |
|