sprite_builder.py 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. #!/usr/bin/env python3
  2. import argparse
  3. import io
  4. import logging
  5. import os
  6. import struct
  7. from PIL import Image, ImageOps
  8. # XBM flipper sprite (.fxbm) is 1-bit depth, width-padded to 8 bits
  9. # file format:
  10. # uint32 size of the rest of the file in bytes
  11. # uint32 width in px
  12. # uint32 height in px
  13. # uint8[] pixel data, every row is padded to 8 bits (like XBM)
  14. def image2xbm(input_file_path):
  15. with Image.open(input_file_path) as im:
  16. with io.BytesIO() as output:
  17. bw = im.convert("1")
  18. bw = ImageOps.invert(bw)
  19. bw.save(output, format="XBM")
  20. return output.getvalue()
  21. def xbm2fxbm(data):
  22. # hell as it is, but it works
  23. f = io.StringIO(data.decode().strip())
  24. width = int(f.readline().strip().split(" ")[2])
  25. height = int(f.readline().strip().split(" ")[2])
  26. data = f.read().strip().replace("\n", "").replace(" ", "").split("=")[1][:-1]
  27. data_str = data[1:-1].replace(",", " ").replace("0x", "")
  28. image_bin = bytearray.fromhex(data_str)
  29. output = struct.pack("<I", len(image_bin) + 8)
  30. output += struct.pack("<II", width, height)
  31. output += image_bin
  32. return output
  33. def process_sprites(input_dir, output_dir):
  34. for root, dirs, files in os.walk(input_dir):
  35. for file in files:
  36. input_file_path = os.path.join(root, file)
  37. rel_path = os.path.relpath(input_file_path, input_dir)
  38. new_rel_path = os.path.splitext(rel_path)[0] + ".fxbm"
  39. output_file_path = os.path.join(output_dir, new_rel_path)
  40. os.makedirs(os.path.dirname(output_file_path), exist_ok=True)
  41. print(f"Converting '{rel_path}' to '{new_rel_path}'")
  42. with open(output_file_path, "wb") as f:
  43. xbm = image2xbm(input_file_path)
  44. f.write(xbm2fxbm(xbm))
  45. def clear_directory(directory):
  46. for root, dirs, files in os.walk(directory, topdown=False):
  47. for name in files:
  48. os.remove(os.path.join(root, name))
  49. for name in dirs:
  50. os.rmdir(os.path.join(root, name))
  51. def main():
  52. parser = argparse.ArgumentParser()
  53. parser.add_argument("source", help="Source directory")
  54. parser.add_argument("target", help="Target directory")
  55. args = parser.parse_args()
  56. logging.basicConfig(level=logging.ERROR)
  57. logger = logging.getLogger(__name__)
  58. logger.debug(f"Building sprites from {args.source} to {args.target}")
  59. clear_directory(args.target)
  60. process_sprites(args.source, args.target)
  61. return 0
  62. if __name__ == "__main__":
  63. main()