RGBMatrixEmulator 0.11.4__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- RGBMatrixEmulator/__init__.py +5 -0
- RGBMatrixEmulator/adapters/__init__.py +68 -0
- RGBMatrixEmulator/adapters/base.py +111 -0
- RGBMatrixEmulator/adapters/browser_adapter/README.md +89 -0
- RGBMatrixEmulator/adapters/browser_adapter/__init__.py +0 -0
- RGBMatrixEmulator/adapters/browser_adapter/adapter.py +53 -0
- RGBMatrixEmulator/adapters/browser_adapter/request_handlers/__init__.py +3 -0
- RGBMatrixEmulator/adapters/browser_adapter/request_handlers/image.py +10 -0
- RGBMatrixEmulator/adapters/browser_adapter/request_handlers/image_web_socket.py +30 -0
- RGBMatrixEmulator/adapters/browser_adapter/request_handlers/main.py +9 -0
- RGBMatrixEmulator/adapters/browser_adapter/server.py +73 -0
- RGBMatrixEmulator/adapters/browser_adapter/static/assets/client.js +91 -0
- RGBMatrixEmulator/adapters/browser_adapter/static/assets/icon.ico +0 -0
- RGBMatrixEmulator/adapters/browser_adapter/static/assets/styles.css +25 -0
- RGBMatrixEmulator/adapters/browser_adapter/static/index.html +144 -0
- RGBMatrixEmulator/adapters/pygame_adapter.py +71 -0
- RGBMatrixEmulator/adapters/sixel_adapter.py +72 -0
- RGBMatrixEmulator/adapters/terminal_adapter.py +36 -0
- RGBMatrixEmulator/adapters/tkinter_adapter.py +83 -0
- RGBMatrixEmulator/adapters/turtle_adapter.py +95 -0
- RGBMatrixEmulator/emulators/__init__.py +0 -0
- RGBMatrixEmulator/emulators/canvas.py +39 -0
- RGBMatrixEmulator/emulators/matrix.py +50 -0
- RGBMatrixEmulator/emulators/options.py +188 -0
- RGBMatrixEmulator/graphics/__init__.py +155 -0
- RGBMatrixEmulator/graphics/color.py +36 -0
- RGBMatrixEmulator/graphics/font.py +34 -0
- RGBMatrixEmulator/icon.ico +0 -0
- RGBMatrixEmulator/icon.png +0 -0
- RGBMatrixEmulator/logger.py +29 -0
- RGBMatrixEmulator/version.py +5 -0
- rgbmatrixemulator-0.11.4.data/data/RGBMatrixEmulator/client.js +91 -0
- rgbmatrixemulator-0.11.4.data/data/RGBMatrixEmulator/icon.ico +0 -0
- rgbmatrixemulator-0.11.4.data/data/RGBMatrixEmulator/icon.png +0 -0
- rgbmatrixemulator-0.11.4.data/data/RGBMatrixEmulator/index.html +144 -0
- rgbmatrixemulator-0.11.4.data/data/RGBMatrixEmulator/styles.css +25 -0
- rgbmatrixemulator-0.11.4.data/data/docs/LICENSE +9 -0
- rgbmatrixemulator-0.11.4.data/data/docs/README.md +160 -0
- rgbmatrixemulator-0.11.4.dist-info/METADATA +186 -0
- rgbmatrixemulator-0.11.4.dist-info/RECORD +43 -0
- rgbmatrixemulator-0.11.4.dist-info/WHEEL +5 -0
- rgbmatrixemulator-0.11.4.dist-info/licenses/LICENSE +9 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<head>
|
|
3
|
+
<title>{{ adapter.emulator_details_text() }}</title>
|
|
4
|
+
<link rel="shortcut icon" href="assets/icon.ico">
|
|
5
|
+
<link rel="stylesheet" href="assets/styles.css">
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<img id="liveImg" class={{ "" if adapter.options.browser.image_border else "no-border" }} />
|
|
9
|
+
|
|
10
|
+
{% if adapter.options.browser.fps_display %}
|
|
11
|
+
<div id="fpsDisplay">
|
|
12
|
+
Frames per second: <span id="fps">0</span>
|
|
13
|
+
</div>
|
|
14
|
+
{% end %}
|
|
15
|
+
|
|
16
|
+
{% if adapter.options.browser.debug_text %}
|
|
17
|
+
<div id="emulatorDetails">
|
|
18
|
+
<h2>{{ adapter.emulator_details_text() }}</h2>
|
|
19
|
+
|
|
20
|
+
<div class="emulatorDetailsTableRow">
|
|
21
|
+
<div id="matrixDetails" class="tableContainer">
|
|
22
|
+
<h3>Matrix Details:</h3>
|
|
23
|
+
|
|
24
|
+
<table>
|
|
25
|
+
<tbody>
|
|
26
|
+
<tr>
|
|
27
|
+
<td>
|
|
28
|
+
Matrix Width:
|
|
29
|
+
</td>
|
|
30
|
+
<td>
|
|
31
|
+
{{ adapter.options.cols }}
|
|
32
|
+
</td>
|
|
33
|
+
</tr>
|
|
34
|
+
<tr>
|
|
35
|
+
<td>
|
|
36
|
+
Matrix Height:
|
|
37
|
+
</td>
|
|
38
|
+
<td>
|
|
39
|
+
{{ adapter.options.rows }}
|
|
40
|
+
</td>
|
|
41
|
+
</td>
|
|
42
|
+
</tr>
|
|
43
|
+
<tr>
|
|
44
|
+
<td>
|
|
45
|
+
Image Size:
|
|
46
|
+
</td>
|
|
47
|
+
<td>
|
|
48
|
+
{{ adapter.options.window_size_str(pixel_text="px") }}
|
|
49
|
+
</td>
|
|
50
|
+
</tr>
|
|
51
|
+
<tr>
|
|
52
|
+
<td>
|
|
53
|
+
Chain Length:
|
|
54
|
+
</td>
|
|
55
|
+
<td>
|
|
56
|
+
{{ adapter.options.chain_length }}
|
|
57
|
+
</td>
|
|
58
|
+
</tr>
|
|
59
|
+
<tr>
|
|
60
|
+
<td>
|
|
61
|
+
Parallel Chains:
|
|
62
|
+
</td>
|
|
63
|
+
<td>
|
|
64
|
+
{{ adapter.options.parallel }}
|
|
65
|
+
</td>
|
|
66
|
+
</tr>
|
|
67
|
+
<tr>
|
|
68
|
+
<td>
|
|
69
|
+
Pixel Size:
|
|
70
|
+
</td>
|
|
71
|
+
<td>
|
|
72
|
+
{{ adapter.options.pixel_size }}
|
|
73
|
+
</td>
|
|
74
|
+
</tr>
|
|
75
|
+
<tr>
|
|
76
|
+
<td>
|
|
77
|
+
Pixel Style:
|
|
78
|
+
</td>
|
|
79
|
+
<td>
|
|
80
|
+
{{ adapter.options.pixel_style }}
|
|
81
|
+
</td>
|
|
82
|
+
</tr>
|
|
83
|
+
<tr>
|
|
84
|
+
<td>
|
|
85
|
+
Adapter:
|
|
86
|
+
</td>
|
|
87
|
+
<td>
|
|
88
|
+
{{ adapter.__class__.__name__ }}
|
|
89
|
+
</td>
|
|
90
|
+
</tr>
|
|
91
|
+
</tbody>
|
|
92
|
+
</table>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<div id="browserDetails" class="tableContainer">
|
|
96
|
+
<h3>Browser Details:</h3>
|
|
97
|
+
|
|
98
|
+
<table>
|
|
99
|
+
<tbody>
|
|
100
|
+
<tr>
|
|
101
|
+
<td>
|
|
102
|
+
Port:
|
|
103
|
+
</td>
|
|
104
|
+
<td>
|
|
105
|
+
{{ adapter.options.browser.port }}
|
|
106
|
+
</td>
|
|
107
|
+
</tr>
|
|
108
|
+
<tr>
|
|
109
|
+
<td>
|
|
110
|
+
Target FPS:
|
|
111
|
+
</td>
|
|
112
|
+
<td>
|
|
113
|
+
{{ adapter.options.browser.target_fps }}
|
|
114
|
+
</td>
|
|
115
|
+
</td>
|
|
116
|
+
</tr>
|
|
117
|
+
<tr>
|
|
118
|
+
<td>
|
|
119
|
+
FPS Display Enabled:
|
|
120
|
+
</td>
|
|
121
|
+
<td>
|
|
122
|
+
{{ adapter.options.browser.fps_display }}
|
|
123
|
+
</td>
|
|
124
|
+
</tr>
|
|
125
|
+
<tr>
|
|
126
|
+
<td>
|
|
127
|
+
Image Quality:
|
|
128
|
+
</td>
|
|
129
|
+
<td>
|
|
130
|
+
{{ adapter.options.browser.quality }}
|
|
131
|
+
</td>
|
|
132
|
+
</tr>
|
|
133
|
+
</tbody>
|
|
134
|
+
</table>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
{% end %}
|
|
139
|
+
|
|
140
|
+
<input id="targetFps" type="hidden" value={{ adapter.options.browser.target_fps }} />
|
|
141
|
+
|
|
142
|
+
<script type="text/javascript" src="assets/client.js"></script>
|
|
143
|
+
</body>
|
|
144
|
+
</html>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
# Try to suppress the pygame load warning if able.
|
|
6
|
+
try:
|
|
7
|
+
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"
|
|
8
|
+
except Exception:
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
import pygame
|
|
12
|
+
|
|
13
|
+
from PIL import Image
|
|
14
|
+
from pygame.locals import QUIT
|
|
15
|
+
from RGBMatrixEmulator.adapters.base import BaseAdapter
|
|
16
|
+
from RGBMatrixEmulator.graphics import Color
|
|
17
|
+
from RGBMatrixEmulator.logger import Logger
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PygameAdapter(BaseAdapter):
|
|
21
|
+
|
|
22
|
+
SUPPORTS_ALTERNATE_PIXEL_STYLE = True
|
|
23
|
+
|
|
24
|
+
def __init__(self, width, height, options):
|
|
25
|
+
super().__init__(width, height, options)
|
|
26
|
+
self.__surface = None
|
|
27
|
+
|
|
28
|
+
def load_emulator_window(self):
|
|
29
|
+
if self.loaded:
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
Logger.info('Loading {}'.format(self.emulator_details_text()))
|
|
33
|
+
self.__surface = pygame.display.set_mode(self.options.window_size())
|
|
34
|
+
pygame.init()
|
|
35
|
+
|
|
36
|
+
self.__set_emulator_icon()
|
|
37
|
+
pygame.display.set_caption(self.emulator_details_text())
|
|
38
|
+
|
|
39
|
+
self.loaded = True
|
|
40
|
+
|
|
41
|
+
def draw_to_screen(self, pixels):
|
|
42
|
+
image = self._get_masked_image(pixels)
|
|
43
|
+
pygame_surface = pygame.image.fromstring(image.tobytes(),
|
|
44
|
+
self.options.window_size(), "RGB")
|
|
45
|
+
self.__surface.blit(pygame_surface, (0, 0))
|
|
46
|
+
|
|
47
|
+
pygame.display.flip()
|
|
48
|
+
|
|
49
|
+
def check_for_quit_event(self):
|
|
50
|
+
# We don't have events, but this will keep the emulator from appearing as if it's not responding.
|
|
51
|
+
# This also enables closing the window to kill the emulator
|
|
52
|
+
for event in pygame.event.get():
|
|
53
|
+
if event.type == QUIT:
|
|
54
|
+
pygame.quit()
|
|
55
|
+
sys.exit()
|
|
56
|
+
|
|
57
|
+
def __set_emulator_icon(self):
|
|
58
|
+
emulator_path = os.path.abspath(os.path.dirname(__file__))
|
|
59
|
+
icon_path = os.path.join(emulator_path, '..', 'icon.png')
|
|
60
|
+
icon = pygame.image.load(os.path.normpath(icon_path))
|
|
61
|
+
|
|
62
|
+
pygame.display.set_icon(icon)
|
|
63
|
+
|
|
64
|
+
def __pygame_pixel(self, col, row):
|
|
65
|
+
return pygame.Rect(
|
|
66
|
+
col * self.options.pixel_size,
|
|
67
|
+
row * self.options.pixel_size,
|
|
68
|
+
self.options.pixel_size,
|
|
69
|
+
self.options.pixel_size
|
|
70
|
+
)
|
|
71
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import sys
|
|
4
|
+
import io
|
|
5
|
+
import libsixel as sixel
|
|
6
|
+
|
|
7
|
+
from PIL import Image, ImageDraw, ImageEnhance
|
|
8
|
+
from typing import List
|
|
9
|
+
|
|
10
|
+
from RGBMatrixEmulator.adapters.base import BaseAdapter
|
|
11
|
+
from RGBMatrixEmulator.graphics.color import Color
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SixelAdapter(BaseAdapter):
|
|
15
|
+
|
|
16
|
+
SUPPORTS_ALTERNATE_PIXEL_STYLE = True
|
|
17
|
+
|
|
18
|
+
def draw_to_screen(self, pixels):
|
|
19
|
+
sixel = self.__encode_sixels(pixels,
|
|
20
|
+
scale=self.options.pixel_size,
|
|
21
|
+
outline=self.options.pixel_outline)
|
|
22
|
+
output = f"\033[H\n{sixel}\n"
|
|
23
|
+
sys.stdout.write(output)
|
|
24
|
+
|
|
25
|
+
def load_emulator_window(self):
|
|
26
|
+
os.system('cls||clear')
|
|
27
|
+
if platform.system() == "Windows":
|
|
28
|
+
os.system('mode con: cols={} lines={}'.format(self.width * 2 + 5, self.height + 3))
|
|
29
|
+
else:
|
|
30
|
+
os.system('stty columns {} rows {}'.format(self.width * 2 + 5, self.height + 3))
|
|
31
|
+
|
|
32
|
+
def __enlarge_pixels(self, pixels: List[List[Color]], scale: int, outline: int) -> Image.Image:
|
|
33
|
+
outline_color = '#000000'
|
|
34
|
+
w, h = len(pixels[0]), len(pixels)
|
|
35
|
+
iw, ih = w * scale, h * scale
|
|
36
|
+
i = Image.new('RGBA', (iw, ih))
|
|
37
|
+
d = ImageDraw.Draw(i)
|
|
38
|
+
for y, row in enumerate(pixels):
|
|
39
|
+
for x, pixel in enumerate(row):
|
|
40
|
+
rx, ry = x * scale, y * scale
|
|
41
|
+
ex, ey = rx + (scale - 1), ry + (scale - 1)
|
|
42
|
+
pixel = self.adjust_pixel_brightness(pixel)
|
|
43
|
+
if self.options.pixel_style == "circle":
|
|
44
|
+
d.ellipse([(rx, ry), (ex, ey)], fill=pixel, outline=outline_color, width=outline)
|
|
45
|
+
else:
|
|
46
|
+
d.rectangle([(rx, ry), (ex, ey)], fill=pixel, outline=outline_color, width=outline)
|
|
47
|
+
|
|
48
|
+
brightness = ImageEnhance.Brightness(i)
|
|
49
|
+
i = brightness.enhance(1.5)
|
|
50
|
+
contrast = ImageEnhance.Contrast(i)
|
|
51
|
+
i = contrast.enhance(0.8)
|
|
52
|
+
|
|
53
|
+
return i
|
|
54
|
+
|
|
55
|
+
def __encode_sixels(self, pixels: List[List[Color]], scale=4, outline=1) -> str:
|
|
56
|
+
'''Encodes given Image to a sixel string.'''
|
|
57
|
+
img = self.__enlarge_pixels(pixels, scale, outline)
|
|
58
|
+
img_data = img.convert('RGB').tobytes()
|
|
59
|
+
width = img.width
|
|
60
|
+
height = img.height
|
|
61
|
+
with io.BytesIO() as buf:
|
|
62
|
+
output = sixel.sixel_output_new(lambda data, buffer: buffer.write(data), buf)
|
|
63
|
+
dither = sixel.sixel_dither_new(256)
|
|
64
|
+
sixel.sixel_dither_initialize(dither, img_data, width, height, sixel.SIXEL_PIXELFORMAT_RGB888)
|
|
65
|
+
sixel.sixel_encode(img_data, width, height, 1, dither, output)
|
|
66
|
+
|
|
67
|
+
encoded = buf.getvalue().decode('ascii')
|
|
68
|
+
|
|
69
|
+
sixel.sixel_dither_unref(dither)
|
|
70
|
+
sixel.sixel_output_unref(output)
|
|
71
|
+
|
|
72
|
+
return encoded
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from RGBMatrixEmulator.adapters.base import BaseAdapter
|
|
5
|
+
from RGBMatrixEmulator.graphics import Color
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TerminalAdapter(BaseAdapter):
|
|
9
|
+
|
|
10
|
+
SUPPORTS_ALTERNATE_PIXEL_STYLE = True
|
|
11
|
+
SYMBOLS = {
|
|
12
|
+
"circle": " ●",
|
|
13
|
+
"square": "██"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
def __init__(self, width, height, options):
|
|
17
|
+
super().__init__(width, height, options)
|
|
18
|
+
self.__symbol = self.SYMBOLS.get(self.options.pixel_style)
|
|
19
|
+
|
|
20
|
+
def draw_to_screen(self, pixels):
|
|
21
|
+
output = "\033[H\n" # Move the cursor to the home position, add a little border
|
|
22
|
+
for pixel_row in pixels:
|
|
23
|
+
output += " " # Add a bit of border in case cursor causes line to wrap
|
|
24
|
+
for pixel in pixel_row:
|
|
25
|
+
pixel = self.adjust_pixel_brightness(pixel)
|
|
26
|
+
output += "\033[38;2;{};{};{}m".format(*pixel) # Set the cell to the pixel color
|
|
27
|
+
output += self.__symbol # Draw the pixel
|
|
28
|
+
output += "\033[37m" # Reset the color
|
|
29
|
+
|
|
30
|
+
output += " \n"
|
|
31
|
+
|
|
32
|
+
sys.stdout.write(output)
|
|
33
|
+
|
|
34
|
+
def load_emulator_window(self):
|
|
35
|
+
os.system('cls||clear')
|
|
36
|
+
os.system('mode con: cols={} lines={}'.format(self.width * 2 + 5, self.height + 3))
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import tkinter
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from RGBMatrixEmulator.adapters.base import BaseAdapter
|
|
5
|
+
from RGBMatrixEmulator.graphics import Color
|
|
6
|
+
from RGBMatrixEmulator.logger import Logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TkinterAdapter(BaseAdapter):
|
|
10
|
+
|
|
11
|
+
SUPPORTS_ALTERNATE_PIXEL_STYLE = True
|
|
12
|
+
|
|
13
|
+
def __init__(self, width, height, options):
|
|
14
|
+
super().__init__(width, height, options)
|
|
15
|
+
self.__root = None
|
|
16
|
+
self.__canvas = None
|
|
17
|
+
self.__pixels = None
|
|
18
|
+
|
|
19
|
+
def load_emulator_window(self):
|
|
20
|
+
if self.loaded:
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
Logger.info('Loading {}'.format(self.emulator_details_text()))
|
|
24
|
+
self.__root = tkinter.Tk()
|
|
25
|
+
self.__set_emulator_icon()
|
|
26
|
+
self.__root.title(self.emulator_details_text())
|
|
27
|
+
|
|
28
|
+
window_size = self.options.window_size()
|
|
29
|
+
self.__root.geometry('{}x{}'.format(*window_size))
|
|
30
|
+
self.__canvas = tkinter.Canvas(self.__root,
|
|
31
|
+
width=window_size[0],
|
|
32
|
+
height=window_size[1],
|
|
33
|
+
bd=0,
|
|
34
|
+
highlightthickness=0,
|
|
35
|
+
bg="black")
|
|
36
|
+
|
|
37
|
+
self.__initialize_bitmap()
|
|
38
|
+
self.__root.update()
|
|
39
|
+
|
|
40
|
+
self.loaded = True
|
|
41
|
+
|
|
42
|
+
def draw_to_screen(self, pixels):
|
|
43
|
+
for row, pixel_row in enumerate(pixels):
|
|
44
|
+
for col, pixel in enumerate(pixel_row):
|
|
45
|
+
pixel = self.adjust_pixel_brightness(pixel)
|
|
46
|
+
shape_id = self.__pixels[row][col]
|
|
47
|
+
|
|
48
|
+
self.__canvas.itemconfig(shape_id, fill=Color.to_hex(pixel))
|
|
49
|
+
|
|
50
|
+
self.__canvas.pack()
|
|
51
|
+
self.__root.update()
|
|
52
|
+
|
|
53
|
+
def __initialize_bitmap(self):
|
|
54
|
+
self.__pixels = []
|
|
55
|
+
|
|
56
|
+
for row in range(0, self.height):
|
|
57
|
+
new_row = []
|
|
58
|
+
|
|
59
|
+
for col in range(0, self.width):
|
|
60
|
+
coords = self.__pixel_dimensions(col, row)
|
|
61
|
+
|
|
62
|
+
if self.options.pixel_style == 'circle':
|
|
63
|
+
id = self.__canvas.create_oval(coords, width=0)
|
|
64
|
+
else:
|
|
65
|
+
id = self.__canvas.create_rectangle(coords, width=0)
|
|
66
|
+
|
|
67
|
+
new_row.append(id)
|
|
68
|
+
|
|
69
|
+
self.__pixels.append(new_row)
|
|
70
|
+
|
|
71
|
+
def __pixel_dimensions(self, col, row):
|
|
72
|
+
size = self.options.pixel_size
|
|
73
|
+
start, stop = (col * size, row * size)
|
|
74
|
+
|
|
75
|
+
return (start, stop, start + size, stop + size)
|
|
76
|
+
|
|
77
|
+
def __set_emulator_icon(self):
|
|
78
|
+
emulator_path = os.path.abspath(os.path.dirname(__file__))
|
|
79
|
+
raw_icon_path = os.path.join(emulator_path, '..', 'icon.png')
|
|
80
|
+
icon_path = os.path.normpath(raw_icon_path)
|
|
81
|
+
|
|
82
|
+
icon = tkinter.PhotoImage(file=icon_path)
|
|
83
|
+
self.__root.tk.call('wm', 'iconphoto', self.__root._w, icon)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tkinter
|
|
3
|
+
import turtle
|
|
4
|
+
|
|
5
|
+
from RGBMatrixEmulator.adapters.base import BaseAdapter
|
|
6
|
+
from RGBMatrixEmulator.graphics.color import Color
|
|
7
|
+
from RGBMatrixEmulator.logger import Logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TurtleAdapter(BaseAdapter):
|
|
11
|
+
def __init__(self, width, height, options):
|
|
12
|
+
super().__init__(width, height, options)
|
|
13
|
+
self.__pen = None
|
|
14
|
+
self.__screen = None
|
|
15
|
+
|
|
16
|
+
def draw_to_screen(self, pixels):
|
|
17
|
+
self.__pen.clear()
|
|
18
|
+
|
|
19
|
+
for row, pixel_row in enumerate(pixels):
|
|
20
|
+
self.__move_pen_to_row_start(row)
|
|
21
|
+
|
|
22
|
+
for _col, pixel in enumerate(pixel_row):
|
|
23
|
+
pixel = self.adjust_pixel_brightness(pixel)
|
|
24
|
+
self.__draw_pixel(pixel)
|
|
25
|
+
self.__move_pen_next_pixel()
|
|
26
|
+
|
|
27
|
+
self.__screen.update()
|
|
28
|
+
|
|
29
|
+
def load_emulator_window(self):
|
|
30
|
+
if self.loaded:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
Logger.info('Loading {}'.format(self.emulator_details_text()))
|
|
34
|
+
turtle.setup(*self.options.window_size())
|
|
35
|
+
turtle.title(self.emulator_details_text())
|
|
36
|
+
self.__pen = turtle.Turtle(visible = False)
|
|
37
|
+
self.__screen = self.__pen.getscreen()
|
|
38
|
+
self.__set_emulator_icon()
|
|
39
|
+
self.__screen.bgcolor(Color.BLACK())
|
|
40
|
+
turtle.tracer(0, 0)
|
|
41
|
+
turtle.colormode(255)
|
|
42
|
+
|
|
43
|
+
self.loaded = True
|
|
44
|
+
|
|
45
|
+
def __draw_pixel(self, pixel):
|
|
46
|
+
self.__pen.color(*pixel)
|
|
47
|
+
self.__pen.begin_fill()
|
|
48
|
+
|
|
49
|
+
if self.options.pixel_style == 'circle':
|
|
50
|
+
self.__draw_circle_pixel()
|
|
51
|
+
else:
|
|
52
|
+
self.__draw_square_pixel()
|
|
53
|
+
|
|
54
|
+
self.__pen.end_fill()
|
|
55
|
+
|
|
56
|
+
self.__pen.setheading(0)
|
|
57
|
+
|
|
58
|
+
def __draw_square_pixel(self):
|
|
59
|
+
for _ in range(0, 4):
|
|
60
|
+
self.__pen.forward(self.options.pixel_size)
|
|
61
|
+
self.__pen.left(90)
|
|
62
|
+
|
|
63
|
+
def __draw_circle_pixel(self):
|
|
64
|
+
self.__pen.pendown()
|
|
65
|
+
self.__pen.dot(self.options.pixel_size)
|
|
66
|
+
self.__pen.penup()
|
|
67
|
+
|
|
68
|
+
# Apparently dots cannot overlap, so set movement to the smallest increment possible
|
|
69
|
+
self.__pen.forward(1)
|
|
70
|
+
|
|
71
|
+
def __move_pen_next_pixel(self):
|
|
72
|
+
self.__pen.penup()
|
|
73
|
+
self.__pen.forward(self.options.pixel_size)
|
|
74
|
+
self.__pen.pendown()
|
|
75
|
+
|
|
76
|
+
def __move_pen_to_row_start(self, row_number):
|
|
77
|
+
self.__reset_pen_position()
|
|
78
|
+
self.__pen.penup()
|
|
79
|
+
self.__pen.setheading(270)
|
|
80
|
+
self.__pen.forward(self.options.pixel_size * row_number)
|
|
81
|
+
self.__pen.setheading(0)
|
|
82
|
+
self.__pen.pendown()
|
|
83
|
+
|
|
84
|
+
def __reset_pen_position(self):
|
|
85
|
+
self.__pen.penup()
|
|
86
|
+
self.__pen.goto(self.options.pixel_size / 2 - self.__screen.window_width() / 2, self.__screen.window_height() / 2 - self.options.pixel_size / 2)
|
|
87
|
+
self.__pen.pendown()
|
|
88
|
+
|
|
89
|
+
def __set_emulator_icon(self):
|
|
90
|
+
emulator_path = os.path.abspath(os.path.dirname(__file__))
|
|
91
|
+
raw_icon_path = os.path.join(emulator_path, '..', 'icon.png')
|
|
92
|
+
icon_path = os.path.normpath(raw_icon_path)
|
|
93
|
+
|
|
94
|
+
icon_image = tkinter.Image("photo", file=icon_path)
|
|
95
|
+
self.__screen._root.iconphoto(True, icon_image)
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from PIL import Image
|
|
3
|
+
from RGBMatrixEmulator.graphics.color import Color
|
|
4
|
+
|
|
5
|
+
class Canvas:
|
|
6
|
+
def __init__(self, options):
|
|
7
|
+
self.options = options
|
|
8
|
+
|
|
9
|
+
self.width = options.cols * options.chain_length
|
|
10
|
+
self.height = options.rows * options.parallel
|
|
11
|
+
self.display_adapter = options.display_adapter.get_instance(self.width, self.height, options)
|
|
12
|
+
|
|
13
|
+
self.__pixels = [[Color.BLACK() for x in range(0, self.width)] for y in range(0, self.height)]
|
|
14
|
+
|
|
15
|
+
self.display_adapter.load_emulator_window()
|
|
16
|
+
|
|
17
|
+
def Clear(self):
|
|
18
|
+
self.__pixels = [[Color.BLACK() for x in range(0, self.width)] for y in range(0, self.height)]
|
|
19
|
+
|
|
20
|
+
def Fill(self, r, g, b):
|
|
21
|
+
self.__pixels = [[(r, g, b) for x in range(0, self.width)] for y in range(0, self.height)]
|
|
22
|
+
|
|
23
|
+
def SetPixel(self, x, y, r, g, b):
|
|
24
|
+
if self.display_adapter.pixel_out_of_bounds(x, y):
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
pixel = self.__pixels[int(y)][int(x)] = (r, g, b)
|
|
28
|
+
|
|
29
|
+
def SetImage(self, image, offset_x=0, offset_y=0, *other):
|
|
30
|
+
original = Image.fromarray(np.array(self.__pixels, dtype=np.uint8), "RGB")
|
|
31
|
+
original.paste(image, (offset_x, offset_y))
|
|
32
|
+
self.__pixels = np.asarray(original)
|
|
33
|
+
|
|
34
|
+
# These are delegated to the display adapter to handle specific implementation.
|
|
35
|
+
def draw_to_screen(self):
|
|
36
|
+
self.display_adapter.draw_to_screen(self.__pixels)
|
|
37
|
+
|
|
38
|
+
def check_for_quit_event(self):
|
|
39
|
+
self.display_adapter.check_for_quit_event()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from RGBMatrixEmulator.emulators.canvas import Canvas
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RGBMatrix:
|
|
5
|
+
def __init__(self, options = {}):
|
|
6
|
+
self.options = options
|
|
7
|
+
|
|
8
|
+
self.width = options.cols * options.chain_length
|
|
9
|
+
self.height = options.rows * options.parallel
|
|
10
|
+
self.brightness = options.brightness
|
|
11
|
+
|
|
12
|
+
self.canvas = None
|
|
13
|
+
|
|
14
|
+
def CreateFrameCanvas(self):
|
|
15
|
+
self.canvas = Canvas(options = self.options)
|
|
16
|
+
|
|
17
|
+
return self.canvas
|
|
18
|
+
|
|
19
|
+
def SwapOnVSync(self, canvas):
|
|
20
|
+
canvas.check_for_quit_event()
|
|
21
|
+
canvas.draw_to_screen()
|
|
22
|
+
self.canvas = canvas
|
|
23
|
+
|
|
24
|
+
return self.canvas
|
|
25
|
+
|
|
26
|
+
def Clear(self):
|
|
27
|
+
self.__sync_canvas()
|
|
28
|
+
self.canvas.Clear()
|
|
29
|
+
self.SwapOnVSync(self.canvas)
|
|
30
|
+
|
|
31
|
+
def Fill(self, r, g, b):
|
|
32
|
+
self.__sync_canvas()
|
|
33
|
+
self.canvas.Fill(r, g, b)
|
|
34
|
+
self.SwapOnVSync(self.canvas)
|
|
35
|
+
|
|
36
|
+
def SetPixel(self, x, y, r, g, b):
|
|
37
|
+
self.__sync_canvas()
|
|
38
|
+
self.canvas.SetPixel(x, y, r, g, b)
|
|
39
|
+
self.SwapOnVSync(self.canvas)
|
|
40
|
+
|
|
41
|
+
def SetImage(self, image, offset_x=0, offset_y=0, *other):
|
|
42
|
+
self.__sync_canvas()
|
|
43
|
+
self.canvas.SetImage(image, offset_x, offset_y, *other)
|
|
44
|
+
self.SwapOnVSync(self.canvas)
|
|
45
|
+
|
|
46
|
+
def __sync_canvas(self):
|
|
47
|
+
if not self.canvas:
|
|
48
|
+
self.canvas = Canvas(options = self.options)
|
|
49
|
+
|
|
50
|
+
self.canvas.display_adapter.options.brightness = self.brightness
|