108 lines
3.6 KiB
Python
108 lines
3.6 KiB
Python
"""
|
|
Image processing utilities for preview generation
|
|
"""
|
|
|
|
import base64
|
|
import io
|
|
import logging
|
|
from typing import Optional
|
|
from PIL import Image
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def create_preview_image_b64(image_data: bytes, target_size: int = 512, quality: int = 85) -> Optional[str]:
|
|
"""
|
|
Convert PNG image data to JPEG preview with specified size and return as base64
|
|
|
|
Args:
|
|
image_data: Original PNG image data in bytes
|
|
target_size: Target size for the preview (default: 512x512)
|
|
quality: JPEG quality (1-100, default: 85)
|
|
|
|
Returns:
|
|
Base64 encoded JPEG image string, or None if conversion fails
|
|
"""
|
|
try:
|
|
# Open image from bytes
|
|
with Image.open(io.BytesIO(image_data)) as img:
|
|
# Convert to RGB if necessary (PNG might have alpha channel)
|
|
if img.mode in ('RGBA', 'LA', 'P'):
|
|
# Create white background for transparent images
|
|
background = Image.new('RGB', img.size, (255, 255, 255))
|
|
if img.mode == 'P':
|
|
img = img.convert('RGBA')
|
|
background.paste(img, mask=img.split()[-1] if img.mode in ('RGBA', 'LA') else None)
|
|
img = background
|
|
elif img.mode != 'RGB':
|
|
img = img.convert('RGB')
|
|
|
|
# Resize to target size maintaining aspect ratio
|
|
img.thumbnail((target_size, target_size), Image.Resampling.LANCZOS)
|
|
|
|
# If image is smaller than target size, pad it to exact size
|
|
if img.size != (target_size, target_size):
|
|
# Create new image with target size and white background
|
|
new_img = Image.new('RGB', (target_size, target_size), (255, 255, 255))
|
|
# Center the resized image
|
|
x = (target_size - img.size[0]) // 2
|
|
y = (target_size - img.size[1]) // 2
|
|
new_img.paste(img, (x, y))
|
|
img = new_img
|
|
|
|
# Convert to JPEG and encode as base64
|
|
output_buffer = io.BytesIO()
|
|
img.save(output_buffer, format='JPEG', quality=quality, optimize=True)
|
|
jpeg_data = output_buffer.getvalue()
|
|
|
|
# Encode to base64
|
|
base64_data = base64.b64encode(jpeg_data).decode('utf-8')
|
|
|
|
logger.info(f"Preview image created: {target_size}x{target_size} JPEG, {len(jpeg_data)} bytes, base64 length: {len(base64_data)}")
|
|
return base64_data
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to create preview image: {str(e)}")
|
|
return None
|
|
|
|
|
|
def validate_image_data(image_data: bytes) -> bool:
|
|
"""
|
|
Validate if image data is a valid image
|
|
|
|
Args:
|
|
image_data: Image data in bytes
|
|
|
|
Returns:
|
|
True if valid image, False otherwise
|
|
"""
|
|
try:
|
|
with Image.open(io.BytesIO(image_data)) as img:
|
|
img.verify() # Verify image integrity
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def get_image_info(image_data: bytes) -> Optional[dict]:
|
|
"""
|
|
Get image information
|
|
|
|
Args:
|
|
image_data: Image data in bytes
|
|
|
|
Returns:
|
|
Dictionary with image info (format, size, mode) or None if invalid
|
|
"""
|
|
try:
|
|
with Image.open(io.BytesIO(image_data)) as img:
|
|
return {
|
|
'format': img.format,
|
|
'size': img.size,
|
|
'mode': img.mode,
|
|
'bytes': len(image_data)
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Failed to get image info: {str(e)}")
|
|
return None
|