""" 将 GIF 转换为精灵图(Spritesheet),并输出 AnimateSprite 的 frames 定义。 用法: python gif_to_spritesheet.py input.gif [--cols 8] [--scale 50] [--output spritesheet.png] 参数: --scale 缩放百分比,如 50 表示缩小到 50%,200 表示放大到 200%(默认 100) --cols 每行帧数(默认 8) --output 输出文件路径 输出: 1. 精灵图 PNG 文件 2. 控制台打印 AnimateSprite 的 frames 和 animations 配置(基于缩放后的尺寸) """ import argparse import json from PIL import Image def extract_frames(gif_path: str) -> list[Image.Image]: gif = Image.open(gif_path) frames = [] try: while True: frames.append(gif.copy().convert('RGBA')) gif.seek(gif.tell() + 1) except EOFError: pass return frames def scale_frames(frames: list[Image.Image], scale_percent: float) -> list[Image.Image]: if scale_percent == 100: return frames factor = scale_percent / 100.0 ow, oh = frames[0].size nw, nh = max(1, round(ow * factor)), max(1, round(oh * factor)) resample = Image.LANCZOS if factor < 1 else Image.LANCZOS return [f.resize((nw, nh), resample) for f in frames] def build_spritesheet(frames: list[Image.Image], cols: int) -> Image.Image: w, h = frames[0].size rows = (len(frames) + cols - 1) // cols sheet = Image.new('RGBA', (w * cols, h * rows), (0, 0, 0, 0)) for i, frame in enumerate(frames): sheet.paste(frame, ((i % cols) * w, (i // cols) * h)) return sheet def generate_animate_sprite_config(frame_count: int, frame_w: int, frame_h: int, cols: int) -> dict: frames = [] for i in range(frame_count): x = (i % cols) * frame_w y = (i // cols) * frame_h frames.append([x, y, frame_w, frame_h]) return { "frames": frames, "animations": { "default": {"frames": list(range(frame_count))}, }, } def main(): parser = argparse.ArgumentParser(description='GIF 转精灵图 + AnimateSprite 配置生成') parser.add_argument('input', help='输入 GIF 文件路径') parser.add_argument('--cols', type=int, default=8, help='每行帧数 (默认 8)') parser.add_argument('--scale', type=float, default=100, help='缩放百分比,如 50=缩小一半,200=放大两倍 (默认 100)') parser.add_argument('--output', '-o', default=None, help='输出精灵图路径 (默认 _spritesheet.png)') args = parser.parse_args() output_path = args.output or args.input.rsplit('.', 1)[0] + '_spritesheet.png' frames = extract_frames(args.input) orig_w, orig_h = frames[0].size print(f'提取到 {len(frames)} 帧, 原始单帧尺寸: {orig_w}x{orig_h}') if args.scale != 100: frames = scale_frames(frames, args.scale) sw, sh = frames[0].size print(f'缩放 {args.scale}%: {orig_w}x{orig_h} -> {sw}x{sh}') sheet = build_spritesheet(frames, args.cols) sheet.save(output_path) fw, fh = frames[0].size print(f'精灵图已保存: {output_path} ({sheet.size[0]}x{sheet.size[1]})') config = generate_animate_sprite_config(len(frames), fw, fh, args.cols) print('\n// AnimateSprite 配置:') print(f'// 精灵图尺寸: {sheet.size[0]}x{sheet.size[1]}, 单帧: {fw}x{fh}, 缩放: {args.scale}%') print(f'const sprite = new MiniRender.AnimateSprite({{') print(f' framerate: 7,') print(f' images: [\'<精灵图URL>\'],') print(f' frames: {json.dumps(config["frames"])},') print(f' animations: {{') print(f' default: {{ frames: {json.dumps(config["animations"]["default"]["frames"])} }},') print(f' }},') print(f' playOnce: false,') print(f' currentAnimation: \'default\',') print(f'}});') print(f'\n// sprite.width = {fw};') print(f'// sprite.height = {fh};') if __name__ == '__main__': main()