File size: 2,630 Bytes
df4a21a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | """
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))
# Convert to RGB if necessary (handles RGBA, grayscale, etc.)
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()
|