sprite_builder.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  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. if file.startswith('.'):
  37. continue
  38. try:
  39. input_file_path = os.path.join(root, file)
  40. rel_path = os.path.relpath(input_file_path, input_dir)
  41. new_rel_path = os.path.splitext(rel_path)[0] + ".fxbm"
  42. output_file_path = os.path.join(output_dir, new_rel_path)
  43. os.makedirs(os.path.dirname(output_file_path), exist_ok=True)
  44. print(f"Converting '{rel_path}' to '{new_rel_path}'")
  45. xbm = image2xbm(input_file_path)
  46. img_data = xbm2fxbm(xbm)
  47. with open(output_file_path, "wb") as f:
  48. f.write(img_data)
  49. except Exception as e:
  50. print(f"Cannot convert '{rel_path}': {e}")
  51. def clear_directory(directory):
  52. for root, dirs, files in os.walk(directory, topdown=False):
  53. for name in files:
  54. os.remove(os.path.join(root, name))
  55. for name in dirs:
  56. os.rmdir(os.path.join(root, name))
  57. def main():
  58. parser = argparse.ArgumentParser()
  59. parser.add_argument("source", help="Source directory")
  60. parser.add_argument("target", help="Target directory")
  61. args = parser.parse_args()
  62. logging.basicConfig(level=logging.ERROR)
  63. logger = logging.getLogger(__name__)
  64. logger.debug(f"Building sprites from {args.source} to {args.target}")
  65. clear_directory(args.target)
  66. process_sprites(args.source, args.target)
  67. return 0
  68. if __name__ == "__main__":
  69. main()