"""Asset loading helpers for backgrounds and character sprites.""" from __future__ import annotations from pathlib import Path from typing import Iterable from PIL import Image class AssetNotFoundError(FileNotFoundError): """Raised when an asset cannot be resolved from the project folders.""" class AssetManager: def __init__(self, project_root: str | Path) -> None: self.project_root = Path(project_root).resolve() self.characters_dir = self.project_root / "characters" self.backgrounds_dir = self.project_root / "backgrounds" def load_background(self, asset: str | Path) -> Image.Image: path = self.resolve(asset, (self.backgrounds_dir, self.project_root)) return Image.open(path).convert("RGBA") def load_character(self, asset: str | Path) -> Image.Image: path = self.resolve(asset, (self.characters_dir, self.project_root)) image = Image.open(path).convert("RGBA") return self._crop_alpha(image) def resolve(self, asset: str | Path, roots: Iterable[Path]) -> Path: asset_path = Path(asset) if asset_path.is_absolute() and asset_path.exists(): return asset_path candidates: list[Path] = [] for root in roots: candidates.append(root / asset_path) if asset_path.suffix: continue candidates.extend(sorted(root.glob(f"{asset_path.name}.*"))) for candidate in candidates: if candidate.exists() and candidate.is_file(): return candidate.resolve() searched = ", ".join(str(root) for root in roots) raise AssetNotFoundError(f"Could not find asset {asset!r} in {searched}.") @staticmethod def _crop_alpha(image: Image.Image) -> Image.Image: bbox = image.getbbox() if bbox is None: return image return image.crop(bbox)