gif_to_spritesheet.py 4.3 KB

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