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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. #!/usr/bin/env python3
  2. # Uses the pillow Python Imaging Library:
  3. # https://github.com/python-pillow/Pillow
  4. #
  5. # ----------------------------------------------------------------------------
  6. # "THE BEER-WARE LICENSE" (Revision 42):
  7. # <xythobuz@xythobuz.de> wrote this file. As long as you retain this notice
  8. # you can do whatever you want with this stuff. If we meet some day, and you
  9. # think this stuff is worth it, you can buy me a beer in return. Thomas Buck
  10. # ----------------------------------------------------------------------------
  11. from PIL import Image
  12. import time
  13. import os
  14. # GIF images contain Palletes for each frame.
  15. # These can be different between frames.
  16. # So the library is converting them to RGB.
  17. # This means we can not replace the background easily
  18. # for all but the first frame in animations.
  19. # This setting changes that to keep the same palette, as long as possible.
  20. # This works with some GIFs, but not all of them.
  21. from PIL import GifImagePlugin
  22. GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
  23. class ImageScreen:
  24. def __init__(self, g, p, t = 0.2, i = 1, to = 5.0, bg = None, target_size = None, nearest = True):
  25. self.gui = g
  26. self.time = t
  27. self.iterations = i
  28. self.timeout = to
  29. self.background = bg
  30. scriptDir = os.path.dirname(os.path.realpath(__file__))
  31. self.path = os.path.join(scriptDir, "images", p)
  32. self.image = Image.open(self.path)
  33. # for some reason non-animated images don't even have this attribute
  34. if not hasattr(self.image, "is_animated"):
  35. self.image.is_animated = False
  36. self.image.n_frames = 1
  37. if target_size == None:
  38. target_size = (self.gui.width, self.gui.height)
  39. # automatically crop and scale large images
  40. if not self.image.is_animated and ((self.image.width > target_size[0]) or (self.image.height > target_size[1])):
  41. # crop to visible area
  42. self.image = self.image.crop(self.image.getbbox())
  43. # keep the aspect ratio and fit within visible area
  44. ratio = self.image.width / self.image.height
  45. max_width = int(ratio * target_size[1])
  46. max_height = int(target_size[0] / ratio)
  47. if (max_height >= target_size[1]) or (((target_size[0] - max_width) < (target_size[1] - max_height)) and ((target_size[0] - max_width) >= 0)):
  48. width = max_width
  49. height = target_size[1]
  50. else:
  51. width = target_size[0]
  52. height = max_height
  53. # resize
  54. if nearest:
  55. self.image = self.image.resize((width, height),
  56. Image.Resampling.NEAREST)
  57. else:
  58. self.image = self.image.resize((width, height))
  59. # new image object is also missing these
  60. self.image.is_animated = False
  61. self.image.n_frames = 1
  62. # enlarge small images
  63. if not self.image.is_animated and ((self.image.width * 2) <= target_size[0]) and ((self.image.height * 2) <= target_size[1]):
  64. self.image = self.image.crop(self.image.getbbox())
  65. self.image = self.image.resize((self.image.width * 2, self.image.height * 2),
  66. Image.Resampling.NEAREST)
  67. self.image.is_animated = False
  68. self.image.n_frames = 1
  69. # TODO cropping and scaling not working for GIF animations
  70. print(p, self.image.width, self.image.height, self.image.is_animated, self.image.n_frames, nearest)
  71. self.xOff = int((self.gui.width - self.image.width) / 2)
  72. self.yOff = int((self.gui.height - self.image.height) / 2)
  73. self.restart()
  74. def restart(self):
  75. self.start = time.time()
  76. self.frame = time.time()
  77. self.count = 0
  78. self.done = 0
  79. self.image.seek(0)
  80. def finished(self):
  81. if self.done >= self.iterations:
  82. return True
  83. # only apply timeout for static images
  84. if self.image.is_animated:
  85. return False
  86. else:
  87. return (time.time() - self.start) >= self.timeout
  88. def draw(self):
  89. if self.image.is_animated:
  90. now = time.time()
  91. if (now - self.frame) >= self.time:
  92. self.frame = now
  93. self.count = (self.count + 1) % self.image.n_frames
  94. if self.count == 0:
  95. self.done += 1
  96. self.image.seek(self.count)
  97. p = self.image.getpalette()
  98. for x in range(0, self.image.width):
  99. for y in range(0, self.image.height):
  100. v = self.image.getpixel((x, y))
  101. if isinstance(v, int):
  102. c = None
  103. if self.background != None:
  104. if "transparency" in self.image.info:
  105. if v == self.image.info["transparency"]:
  106. c = self.background
  107. else:
  108. if v == self.image.info["background"]:
  109. c = self.background
  110. if c == None:
  111. c = (p[v * 3 + 0], p[v * 3 + 1], p[v * 3 + 2])
  112. self.gui.set_pixel(x + self.xOff, y + self.yOff, c)
  113. else:
  114. self.gui.set_pixel(x + self.xOff, y + self.yOff, v)
  115. if __name__ == "__main__":
  116. import sys
  117. import util
  118. i = util.getInput()
  119. t = util.getTarget(i)
  120. from manager import Manager
  121. m = Manager(t, i, 1)
  122. scriptDir = os.path.dirname(os.path.realpath(__file__))
  123. imageDir = os.path.join(scriptDir, "images")
  124. for f in os.listdir(os.fsencode(imageDir)):
  125. filename = os.fsdecode(f)
  126. if len(sys.argv) > 1:
  127. if not sys.argv[1] in filename:
  128. continue
  129. for i in range(0, 2):
  130. nearest = (i == 1)
  131. d = ImageScreen(t, filename, 0.2, 1, 2.5, None, None, nearest)
  132. m.add(d)
  133. if filename != "camp23.png":
  134. continue
  135. if (t.width != 32) or (t.height != 32):
  136. continue
  137. try:
  138. # dump generated image to console, for embedding in Pico script
  139. print()
  140. print("Dumping image to img_tmp.py")
  141. with open("img_tmp.py", "w") as f:
  142. f.write("# Image \"" + filename + "\"" + os.linesep)
  143. f.write("# size:" + str(d.image.width) + "x" + str(d.image.height) + os.linesep)
  144. f.write("img_data = [" + os.linesep)
  145. for y in range(0, d.image.height):
  146. f.write(" [" + os.linesep)
  147. for x in range(0, d.image.width):
  148. s = str(d.image.getpixel((x, y)))
  149. f.write(" " + s + "," + os.linesep)
  150. f.write(" ]," + os.linesep)
  151. f.write("]" + os.linesep)
  152. print()
  153. except Exception as e:
  154. print()
  155. if hasattr(sys, "print_exception"):
  156. sys.print_exception(e)
  157. else:
  158. print(e)
  159. print()
  160. m.restart()
  161. util.loop(t, m.draw)