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.
Files changed (42) hide show
  1. RGBMatrixEmulator/__init__.py +5 -0
  2. RGBMatrixEmulator/adapters/__init__.py +68 -0
  3. RGBMatrixEmulator/adapters/base.py +111 -0
  4. RGBMatrixEmulator/adapters/browser_adapter/README.md +89 -0
  5. RGBMatrixEmulator/adapters/browser_adapter/__init__.py +0 -0
  6. RGBMatrixEmulator/adapters/browser_adapter/adapter.py +53 -0
  7. RGBMatrixEmulator/adapters/browser_adapter/request_handlers/__init__.py +3 -0
  8. RGBMatrixEmulator/adapters/browser_adapter/request_handlers/image.py +10 -0
  9. RGBMatrixEmulator/adapters/browser_adapter/request_handlers/image_web_socket.py +30 -0
  10. RGBMatrixEmulator/adapters/browser_adapter/request_handlers/main.py +9 -0
  11. RGBMatrixEmulator/adapters/browser_adapter/server.py +73 -0
  12. RGBMatrixEmulator/adapters/browser_adapter/static/assets/client.js +91 -0
  13. RGBMatrixEmulator/adapters/browser_adapter/static/assets/icon.ico +0 -0
  14. RGBMatrixEmulator/adapters/browser_adapter/static/assets/styles.css +25 -0
  15. RGBMatrixEmulator/adapters/browser_adapter/static/index.html +144 -0
  16. RGBMatrixEmulator/adapters/pygame_adapter.py +71 -0
  17. RGBMatrixEmulator/adapters/sixel_adapter.py +72 -0
  18. RGBMatrixEmulator/adapters/terminal_adapter.py +36 -0
  19. RGBMatrixEmulator/adapters/tkinter_adapter.py +83 -0
  20. RGBMatrixEmulator/adapters/turtle_adapter.py +95 -0
  21. RGBMatrixEmulator/emulators/__init__.py +0 -0
  22. RGBMatrixEmulator/emulators/canvas.py +39 -0
  23. RGBMatrixEmulator/emulators/matrix.py +50 -0
  24. RGBMatrixEmulator/emulators/options.py +188 -0
  25. RGBMatrixEmulator/graphics/__init__.py +155 -0
  26. RGBMatrixEmulator/graphics/color.py +36 -0
  27. RGBMatrixEmulator/graphics/font.py +34 -0
  28. RGBMatrixEmulator/icon.ico +0 -0
  29. RGBMatrixEmulator/icon.png +0 -0
  30. RGBMatrixEmulator/logger.py +29 -0
  31. RGBMatrixEmulator/version.py +5 -0
  32. rgbmatrixemulator-0.11.4.data/data/RGBMatrixEmulator/client.js +91 -0
  33. rgbmatrixemulator-0.11.4.data/data/RGBMatrixEmulator/icon.ico +0 -0
  34. rgbmatrixemulator-0.11.4.data/data/RGBMatrixEmulator/icon.png +0 -0
  35. rgbmatrixemulator-0.11.4.data/data/RGBMatrixEmulator/index.html +144 -0
  36. rgbmatrixemulator-0.11.4.data/data/RGBMatrixEmulator/styles.css +25 -0
  37. rgbmatrixemulator-0.11.4.data/data/docs/LICENSE +9 -0
  38. rgbmatrixemulator-0.11.4.data/data/docs/README.md +160 -0
  39. rgbmatrixemulator-0.11.4.dist-info/METADATA +186 -0
  40. rgbmatrixemulator-0.11.4.dist-info/RECORD +43 -0
  41. rgbmatrixemulator-0.11.4.dist-info/WHEEL +5 -0
  42. 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