from pathlib import Path import numpy as np from PIL import Image from spriteforge.backends import BackendContext, create_backend, implemented_backends from spriteforge.build import build_assets from spriteforge.manifest import AssetSpec, load_library from spriteforge.reader import decode_sfa def _png(path: Path, rgba: np.ndarray) -> None: Image.fromarray(rgba, "RGBA").save(path) def test_import_splits_sheet_and_sets_directions(tmp_path: Path): sheet = np.zeros((4, 8, 4), dtype=np.uint8) sheet[:, :4] = (255, 0, 0, 255); sheet[:, 4:] = (0, 255, 0, 255) _png(tmp_path / "sheet.png", sheet) spec = AssetSpec(backend="import", source="sheet.png", dirs=2, fps=10, params={"frame_width": 4, "frame_height": 4}) backend = create_backend("import") assert backend.dependencies(spec, BackendContext(tmp_path, "hero")) == (tmp_path / "sheet.png",) asset = backend.generate(spec, BackendContext(tmp_path, "hero")) assert len(asset.frames) == 2 and asset.directions_centidegrees == (0, 18000) assert asset.frames[0].duration_ms == 100 def test_composite_alpha_blends_in_manifest_order(tmp_path: Path): bottom = np.zeros((2, 2, 4), dtype=np.uint8); bottom[:] = (200, 0, 0, 255) top = np.zeros((2, 2, 4), dtype=np.uint8); top[:] = (0, 0, 200, 128) _png(tmp_path / "bottom.png", bottom); _png(tmp_path / "top.png", top) spec = AssetSpec(backend="composite", layers=["bottom.png", "top.png"], dirs=1) asset = create_backend("composite").generate(spec, BackendContext(tmp_path, "layered")) pixel = asset.frames[0].rgba[0, 0] assert tuple(pixel) == (100, 0, 100, 255) assert asset.layer_count == 2 def test_all_local_backends_are_registered(): assert {"procedural", "blender", "import", "composite"} <= set(implemented_backends()) assert "image_api" not in implemented_backends() def test_import_and_composite_build_real_sfa_files(tmp_path: Path): red = np.zeros((4, 4, 4), dtype=np.uint8); red[:] = (200, 20, 20, 255) blue = np.zeros((4, 4, 4), dtype=np.uint8); blue[1:3, 1:3] = (20, 20, 200, 255) _png(tmp_path / "red.png", red); _png(tmp_path / "blue.png", blue) manifest = tmp_path / "assets.yaml" manifest.write_text("imported:\n backend: import\n source: red.png\nlayered:\n backend: composite\n layers: [red.png, blue.png]\n") result = build_assets(load_library([manifest]), tmp_path / "build", cache_dir=tmp_path / "cache") assert result.asset_count == 2 assert decode_sfa((tmp_path / "build" / "imported.sfa").read_bytes()).frames layered = decode_sfa((tmp_path / "build" / "layered.sfa").read_bytes()) assert layered.layer_orders == ((0, 1),)