2
0

ani2spritesheet.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. from PIL import Image
  2. import io,os,sys
  3. import logging
  4. logging.basicConfig(format='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s',level=logging.INFO)
  5. def analyzeANIFile(filePath):
  6. with open(filePath,'rb') as f:
  7. if f.read(4) != b'RIFF':
  8. return {"code":-1,"msg":"File is not a ANI File!"}
  9. logging.debug('文件头检查完成!')
  10. fileSize = int.from_bytes(f.read(4), byteorder='little', signed=False)
  11. # if os.path.getsize(filePath) != fileSize:
  12. # return {"code":-2,"msg":"File is damaged!"}
  13. logging.debug('文件长度检查完成!')
  14. if f.read(4) != b'ACON':
  15. return {"code":-1,"msg":"File is not a ANI File!"}
  16. logging.debug('魔数检查完成!')
  17. frameRate = (1/60)*1000
  18. readANIH = False
  19. while(True):
  20. chunkName = f.read(4)
  21. if chunkName == b'LIST' and readANIH:
  22. break
  23. if chunkName == b'anih':
  24. readANIH = True
  25. chunkSize = int.from_bytes(f.read(4), byteorder='little', signed=False)
  26. if chunkName.lower() == b'rate':
  27. logging.debug('发现自定义速率!')
  28. frameRate = frameRate * int.from_bytes(f.read(4), byteorder='little', signed=False)
  29. logging.warning('发现自定义速率!由于GIF限制,将取第一帧与第二帧的速率作为整体速率!')
  30. f.read(chunkSize - 4)
  31. else:
  32. logging.debug('发现自定义Chunk!')
  33. f.read(chunkSize)
  34. listChunkSize = int.from_bytes(f.read(4), byteorder='little', signed=False)
  35. if f.read(4) != b'fram':
  36. return {"code":-3,"msg":"File not a ANI File!(No Frames)"}
  37. logging.debug('frame头检查完成!')
  38. frameList = []
  39. nowSize = 4
  40. while(nowSize < listChunkSize):
  41. if f.read(4) != b'icon':
  42. return {"code":-4,"msg":"File not a ANI File!(Other Kind Frames)"}
  43. nowSize += 4
  44. subChunkSize = int.from_bytes(f.read(4), byteorder='little', signed=False)
  45. nowSize += 4
  46. frameList.append(f.read(subChunkSize))
  47. nowSize += subChunkSize
  48. return {"code":0,"msg":frameList,"frameRate":frameRate}
  49. def CURPaletteFix(image):
  50. # type: (Image.ImageFile.ImageFile) -> None
  51. if (image.mode == 'P'):
  52. palette = list(image.palette.getdata()[1])
  53. for i in range(4, len(palette), 4):
  54. if sum(palette[i:i + 3]) == 0:
  55. continue
  56. palette[i + 3] = 255
  57. image.putpalette(palette, 'BGRA')
  58. if __name__ == '__main__':
  59. OUTPUT_SIZE = (48,48)
  60. if len(sys.argv) < 2:
  61. logging.fatal("Usage:python ani2spritesheet.py <inputFile> <outputFile,Option>")
  62. else:
  63. res = analyzeANIFile(sys.argv[1])
  64. GIFframes = []
  65. if res["code"] == 0:
  66. logging.info('ANI文件分析完成,帧提取完成!')
  67. output = Image.new("RGBA", (OUTPUT_SIZE[0], OUTPUT_SIZE[1] * len(res["msg"])))
  68. for frameIndex in range(len(res["msg"])):
  69. frameImage = Image.open(io.BytesIO(res["msg"][frameIndex]),formats=['cur'])
  70. CURPaletteFix(frameImage)
  71. extracted_frame = frameImage.convert('RGBA').resize(OUTPUT_SIZE)
  72. position = (0, OUTPUT_SIZE[0] * frameIndex)
  73. output.paste(extracted_frame, position)
  74. if(len(sys.argv) >= 3):
  75. output.save(sys.argv[2],format="PNG")
  76. else:
  77. output.save(f"{sys.argv[1].strip('.ani')}.png",format="PNG")
  78. logging.info('SpriteSheet生成完成!')
  79. else:
  80. logging.fatal(res["msg"])