gif_to_spritesheet.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. """
  2. 将 GIF 转换为精灵图(Spritesheet),并输出 AnimateSprite 的 frames 定义。
  3. 用法:
  4. python gif_to_spritesheet.py input.gif [--cols 8] [--scale 50] [--output spritesheet.png]
  5. 参数:
  6. --scale 缩放百分比,如 50 表示缩小到 50%,200 表示放大到 200%(默认 100)
  7. --cols 每行帧数(默认 8)
  8. --output 输出文件路径
  9. 输出:
  10. 1. 精灵图 PNG 文件
  11. 2. 控制台打印 AnimateSprite 的 frames 和 animations 配置(基于缩放后的尺寸)
  12. """
  13. import argparse
  14. import json
  15. from PIL import Image
  16. def extract_frames(gif_path: str) -> list[Image.Image]:
  17. gif = Image.open(gif_path)
  18. frames = []
  19. try:
  20. while True:
  21. frames.append(gif.copy().convert('RGBA'))
  22. gif.seek(gif.tell() + 1)
  23. except EOFError:
  24. pass
  25. return frames
  26. def scale_frames(frames: list[Image.Image], scale_percent: float) -> list[Image.Image]:
  27. if scale_percent == 100:
  28. return frames
  29. factor = scale_percent / 100.0
  30. ow, oh = frames[0].size
  31. nw, nh = max(1, round(ow * factor)), max(1, round(oh * factor))
  32. resample = Image.LANCZOS if factor < 1 else Image.LANCZOS
  33. return [f.resize((nw, nh), resample) for f in frames]
  34. def build_spritesheet(frames: list[Image.Image], cols: int) -> Image.Image:
  35. w, h = frames[0].size
  36. rows = (len(frames) + cols - 1) // cols
  37. sheet = Image.new('RGBA', (w * cols, h * rows), (0, 0, 0, 0))
  38. for i, frame in enumerate(frames):
  39. sheet.paste(frame, ((i % cols) * w, (i // cols) * h))
  40. return sheet
  41. def generate_animate_sprite_config(frame_count: int, frame_w: int, frame_h: int, cols: int) -> dict:
  42. frames = []
  43. for i in range(frame_count):
  44. x = (i % cols) * frame_w
  45. y = (i // cols) * frame_h
  46. frames.append([x, y, frame_w, frame_h])
  47. return {
  48. "frames": frames,
  49. "animations": {
  50. "default": {"frames": list(range(frame_count))},
  51. },
  52. }
  53. def main():
  54. parser = argparse.ArgumentParser(description='GIF 转精灵图 + AnimateSprite 配置生成')
  55. parser.add_argument('input', help='输入 GIF 文件路径')
  56. parser.add_argument('--cols', type=int, default=8, help='每行帧数 (默认 8)')
  57. parser.add_argument('--scale', type=float, default=100, help='缩放百分比,如 50=缩小一半,200=放大两倍 (默认 100)')
  58. parser.add_argument('--output', '-o', default=None, help='输出精灵图路径 (默认 <input>_spritesheet.png)')
  59. args = parser.parse_args()
  60. output_path = args.output or args.input.rsplit('.', 1)[0] + '_spritesheet.png'
  61. frames = extract_frames(args.input)
  62. orig_w, orig_h = frames[0].size
  63. print(f'提取到 {len(frames)} 帧, 原始单帧尺寸: {orig_w}x{orig_h}')
  64. if args.scale != 100:
  65. frames = scale_frames(frames, args.scale)
  66. sw, sh = frames[0].size
  67. print(f'缩放 {args.scale}%: {orig_w}x{orig_h} -> {sw}x{sh}')
  68. sheet = build_spritesheet(frames, args.cols)
  69. sheet.save(output_path)
  70. fw, fh = frames[0].size
  71. print(f'精灵图已保存: {output_path} ({sheet.size[0]}x{sheet.size[1]})')
  72. config = generate_animate_sprite_config(len(frames), fw, fh, args.cols)
  73. print('\n// AnimateSprite 配置:')
  74. print(f'// 精灵图尺寸: {sheet.size[0]}x{sheet.size[1]}, 单帧: {fw}x{fh}, 缩放: {args.scale}%')
  75. print(f'const sprite = new MiniRender.AnimateSprite({{')
  76. print(f' framerate: 7,')
  77. print(f' images: [\'<精灵图URL>\'],')
  78. print(f' frames: {json.dumps(config["frames"])},')
  79. print(f' animations: {{')
  80. print(f' default: {{ frames: {json.dumps(config["animations"]["default"]["frames"])} }},')
  81. print(f' }},')
  82. print(f' playOnce: false,')
  83. print(f' currentAnimation: \'default\',')
  84. print(f'}});')
  85. print(f'\n// sprite.width = {fw};')
  86. print(f'// sprite.height = {fh};')
  87. if __name__ == '__main__':
  88. main()