101 lines
3.3 KiB
Python
101 lines
3.3 KiB
Python
"""Сервис обработки изображений."""
|
||
import io
|
||
from pathlib import Path
|
||
from typing import Optional, Tuple
|
||
|
||
from PIL import Image
|
||
|
||
|
||
class ImageProcessor:
|
||
"""Сервис для обработки изображений заданий."""
|
||
|
||
MAX_SIZE = (800, 800)
|
||
SUPPORTED_FORMATS = {"JPEG", "PNG", "WEBP"}
|
||
QUALITY = 85
|
||
|
||
@staticmethod
|
||
def resize_image(
|
||
image_data: bytes, max_size: Tuple[int, int] = MAX_SIZE, quality: int = QUALITY
|
||
) -> bytes:
|
||
"""
|
||
Изменить размер изображения.
|
||
|
||
Args:
|
||
image_data: Байты изображения
|
||
max_size: Максимальный размер (width, height)
|
||
quality: Качество JPEG (1-100)
|
||
|
||
Returns:
|
||
Байты обработанного изображения
|
||
"""
|
||
image = Image.open(io.BytesIO(image_data))
|
||
image_format = image.format or "JPEG"
|
||
|
||
# Конвертируем в RGB если нужно
|
||
if image_format == "PNG" and image.mode in ("RGBA", "LA"):
|
||
background = Image.new("RGB", image.size, (255, 255, 255))
|
||
if image.mode == "RGBA":
|
||
background.paste(image, mask=image.split()[3])
|
||
else:
|
||
background.paste(image)
|
||
image = background
|
||
elif image.mode != "RGB":
|
||
image = image.convert("RGB")
|
||
|
||
# Изменяем размер с сохранением пропорций
|
||
image.thumbnail(max_size, Image.Resampling.LANCZOS)
|
||
|
||
# Сохраняем в байты
|
||
output = io.BytesIO()
|
||
image.save(output, format="JPEG", quality=quality, optimize=True)
|
||
return output.getvalue()
|
||
|
||
@staticmethod
|
||
def validate_image(image_data: bytes) -> Tuple[bool, Optional[str]]:
|
||
"""
|
||
Валидировать изображение.
|
||
|
||
Args:
|
||
image_data: Байты изображения
|
||
|
||
Returns:
|
||
(is_valid, error_message)
|
||
"""
|
||
try:
|
||
image = Image.open(io.BytesIO(image_data))
|
||
image_format = image.format
|
||
|
||
if image_format not in ImageProcessor.SUPPORTED_FORMATS:
|
||
return False, f"Неподдерживаемый формат: {image_format}"
|
||
|
||
# Проверяем размер
|
||
width, height = image.size
|
||
if width > 2000 or height > 2000:
|
||
return False, "Изображение слишком большое (максимум 2000x2000)"
|
||
|
||
# Проверяем файл на валидность
|
||
image.verify()
|
||
return True, None
|
||
|
||
except Exception as e:
|
||
return False, f"Ошибка валидации: {str(e)}"
|
||
|
||
@staticmethod
|
||
def get_image_info(image_data: bytes) -> dict:
|
||
"""
|
||
Получить информацию об изображении.
|
||
|
||
Args:
|
||
image_data: Байты изображения
|
||
|
||
Returns:
|
||
Словарь с информацией (format, size, mode)
|
||
"""
|
||
image = Image.open(io.BytesIO(image_data))
|
||
return {
|
||
"format": image.format,
|
||
"size": image.size,
|
||
"mode": image.mode,
|
||
}
|
||
|