/
sarus
/
photo-processor
Обзор
Документация
Войти
/
sarus
/
photo-processor
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/photo_processor/image_loader.py
95 строк
3 KB
otcheskiy
feat: add image loading and fixed crop
01 авг 2026, 11:52
01 авг 2026, 11:52
ef42adf
Код
Авторство
О чём код?
"""Загрузка изображения, EXIF orientation и преобразование в RGB.""" from __future__ import annotations from dataclasses import dataclass from pathlib import Path from PIL import Image, ImageOps, UnidentifiedImageError class ImageLoadError(ValueError): """Ошибка чтения или нормализации изображения.""" @dataclass(frozen=True) class LoadedImage: """Изображение после EXIF transpose и преобразования в RGB.""" image: Image.Image original_width: int original_height: int source_path: Path def _composite_on_white(image: Image.Image) -> Image.Image: """Наложить изображение с альфа-каналом на белый RGB-фон.""" rgba = image.convert("RGBA") background = Image.new("RGB", rgba.size, (255, 255, 255)) background.paste(rgba, mask=rgba.getchannel("A")) return background def to_rgb(image: Image.Image) -> Image.Image: """Преобразовать изображение в RGB без потери прозрачности в чёрный фон.""" if image.mode == "RGB": return image if image.mode in {"RGBA", "LA"}: return _composite_on_white(image) if image.mode == "P": if "transparency" in image.info: return _composite_on_white(image.convert("RGBA")) return image.convert("RGB") if image.mode == "RGBa": return _composite_on_white(image.convert("RGBA")) return image.convert("RGB") def load_image(path: Path) -> LoadedImage: """Открыть файл, исправить EXIF orientation и привести к RGB.""" source = path.resolve() try: with Image.open(source) as opened: opened.load() transposed = ImageOps.exif_transpose(opened) if transposed is None: transposed = opened # Копия нужна: контекст Image.open закрывает исходный файл. working = transposed.copy() except FileNotFoundError as exc: raise ImageLoadError(f"Файл не найден: {source}") from exc except UnidentifiedImageError as exc: raise ImageLoadError(f"Файл не является изображением: {source.name}") from exc except OSError as exc: raise ImageLoadError( f"Не удалось прочитать изображение '{source.name}': {exc}" ) from exc width, height = working.size if width <= 0 or height <= 0: working.close() raise ImageLoadError( f"Изображение '{source.name}' имеет недопустимый размер: {width}x{height}." ) rgb = to_rgb(working) if rgb is not working: working.close() if rgb.mode != "RGB": mode = rgb.mode rgb.close() raise ImageLoadError( f"Не удалось привести '{source.name}' к RGB (получен режим {mode})." ) return LoadedImage( image=rgb, original_width=width, original_height=height, source_path=source, )