| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- """
- 将 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='输出精灵图路径 (默认 <input>_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()
|