Python RGB Matrix games and animations https://www.xythobuz.de/ledmatrix_v2.html
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

bdf.py 4.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. #!/usr/bin/env python3
  2. # Uses the Python BDF format bitmap font parser:
  3. # https://github.com/tomchen/bdfparser
  4. #
  5. # And the pillow Python Imaging Library:
  6. # https://github.com/python-pillow/Pillow
  7. #
  8. # ----------------------------------------------------------------------------
  9. # "THE BEER-WARE LICENSE" (Revision 42):
  10. # <xythobuz@xythobuz.de> wrote this file. As long as you retain this notice
  11. # you can do whatever you want with this stuff. If we meet some day, and you
  12. # think this stuff is worth it, you can buy me a beer in return. Thomas Buck
  13. # ----------------------------------------------------------------------------
  14. from bdfparser import Font
  15. from PIL import Image
  16. import os
  17. class DrawText:
  18. def __init__(self, g, fg = (255, 255, 255), bg = (0, 0, 0), c = (0, 255, 0)):
  19. self.gui = g
  20. self.fg = fg
  21. self.bg = bg
  22. self.color = c
  23. scriptDir = os.path.dirname(os.path.realpath(__file__))
  24. fontDir = os.path.join(scriptDir, "fonts")
  25. self.fonts = {}
  26. for f in os.listdir(os.fsencode(fontDir)):
  27. filename = os.fsdecode(f)
  28. if not filename.lower().endswith(".bdf"):
  29. continue
  30. # TODO hard-coded per-font offsets and spacing adjustment
  31. offset = 0
  32. spacing = 0
  33. if filename == "iv18x16u.bdf":
  34. offset = 6
  35. elif filename == "ib8x8u.bdf":
  36. offset = 10
  37. elif filename == "lemon.bdf":
  38. offset = 8
  39. spacing = -3
  40. elif filename == "antidote.bdf":
  41. offset = 9
  42. spacing = -1
  43. elif filename == "uushi.bdf":
  44. offset = 8
  45. spacing = -2
  46. elif filename == "tom-thumb.bdf":
  47. offset = 12
  48. spacing = 2
  49. # center vertically, in case of
  50. # multiple stacked panels
  51. if self.gui.height > self.gui.panelH:
  52. offset += int(self.gui.panelH / 2)
  53. font = Font(os.path.join(fontDir, filename))
  54. data = (font, offset, {}, spacing)
  55. self.fonts[filename[:-4]] = data
  56. # internal
  57. def getGlyph(self, c, font):
  58. if not isinstance(font, str):
  59. # fall-back to first available font
  60. f, offset, cache, spacing = next(iter(self.fonts))
  61. else:
  62. # users font choice
  63. f, offset, cache, spacing = self.fonts[font]
  64. # only render glyphs once, cache resulting image data
  65. if not c in cache:
  66. g = f.glyph(c).draw()
  67. # invert color
  68. g = g.replace(1, 2).replace(0, 1).replace(2, 0)
  69. # render to pixel data
  70. bytesdict = {
  71. 0: int(self.fg[2] << 16 | self.fg[1] << 8 | self.fg[0]).to_bytes(4, byteorder = "little"),
  72. 1: int(self.bg[2] << 16 | self.bg[1] << 8 | self.bg[0]).to_bytes(4, byteorder = "little"),
  73. 2: int(self.color[2] << 16 | self.color[1] << 8 | self.color[0]).to_bytes(4, byteorder = "little"),
  74. }
  75. img = Image.frombytes('RGBA',
  76. (g.width(), g.height()),
  77. g.tobytes('RGBA', bytesdict))
  78. cache[c] = img
  79. return (cache[c], offset, spacing)
  80. # internal
  81. def drawGlyph(self, g, xOff, yOff, spacing):
  82. for x in range(0, g.width + spacing):
  83. for y in range(0, g.height):
  84. pos = (xOff + x, yOff + y)
  85. if x >= g.width:
  86. self.image.putpixel(pos, self.bg)
  87. else:
  88. p = g.getpixel((x, y))
  89. self.image.putpixel(pos, p)
  90. # text drawing API
  91. def setText(self, s, f):
  92. # calculate length of whole text
  93. w = 0
  94. h = 0
  95. for c in s:
  96. g, y, spacing = self.getGlyph(c, f)
  97. w += g.width + spacing
  98. if h < g.height:
  99. h = g.height
  100. # create new blank image buffer
  101. self.image = Image.new('RGBA', (w, h))
  102. # render glyphs into image
  103. w = 0
  104. for c in s:
  105. g, y, spacing = self.getGlyph(c, f)
  106. self.drawGlyph(g, w, 0, spacing)
  107. w += g.width + spacing
  108. self.yOffset = y
  109. # text drawing API
  110. def getDimensions(self):
  111. return (self.image.width, self.image.height)
  112. # text drawing API
  113. def draw(self, xOff = 0, yOff = 0):
  114. for x in range(0, self.image.width):
  115. xTarget = -xOff + x
  116. if xTarget < 0:
  117. continue
  118. if xTarget >= self.gui.width:
  119. break
  120. for y in range(0, self.image.height):
  121. yTarget = yOff + y + self.yOffset
  122. if yTarget < 0:
  123. continue
  124. if yTarget >= self.gui.height:
  125. break
  126. p = self.image.getpixel((x, y))
  127. self.gui.set_pixel(xTarget, yTarget, p)