#!/usr/bin/env python3 """Expand canonical A/B/C poses into the A/B/A/C/A/B/A/C walk cycle.""" from __future__ import annotations import argparse from pathlib import Path from PIL import Image POSE_SEQUENCE = (0, 1, 0, 2, 0, 1, 0, 2) def split_equal_columns(image: Image.Image, columns: int) -> list[Image.Image]: width, height = image.size edges = [round(index * width / columns) for index in range(columns + 1)] return [image.crop((edges[index], 0, edges[index + 1], height)) for index in range(columns)] def expand_pose_triplet(image: Image.Image) -> Image.Image: poses = split_equal_columns(image.convert("RGBA"), 3) cell_width = max(pose.width for pose in poses) cell_height = image.height output = Image.new("RGBA", (cell_width * len(POSE_SEQUENCE), cell_height), (0, 0, 0, 0)) for output_index, pose_index in enumerate(POSE_SEQUENCE): pose = poses[pose_index] x = output_index * cell_width + (cell_width - pose.width) // 2 output.alpha_composite(pose, (x, 0)) return output def combine_pose_images(paths: list[Path]) -> Image.Image: poses = [Image.open(path).convert("RGBA") for path in paths] cell_width = max(pose.width for pose in poses) cell_height = max(pose.height for pose in poses) output = Image.new("RGBA", (cell_width * len(poses), cell_height), (0, 0, 0, 0)) for index, pose in enumerate(poses): x = index * cell_width + (cell_width - pose.width) // 2 y = (cell_height - pose.height) // 2 output.alpha_composite(pose, (x, y)) return output def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--input", type=Path) parser.add_argument("--pose", type=Path, action="append", default=[]) parser.add_argument("--output", type=Path, required=True) args = parser.parse_args() if bool(args.input) == bool(args.pose): raise ValueError("Use either --input triplet or exactly three --pose images") if args.pose: if len(args.pose) != 3: raise ValueError(f"Expected exactly three --pose images, got {len(args.pose)}") for path in args.pose: if not path.exists(): raise FileNotFoundError(f"Canonical pose not found: {path}") image = combine_pose_images(args.pose) else: if args.input is None or not args.input.exists(): raise FileNotFoundError(f"Pose triplet not found: {args.input}") image = Image.open(args.input).convert("RGBA") if image.width < 3 or image.height < 1: raise ValueError(f"Invalid pose triplet size: {image.size}") output = expand_pose_triplet(image) args.output.parent.mkdir(parents=True, exist_ok=True) output.save(args.output) print(args.output) if __name__ == "__main__": main()