From 84e2837f4bf05567fcfbc27b0345f6371064f7e0 Mon Sep 17 00:00:00 2001 From: Kariiv Date: Sat, 9 Jan 2021 15:00:38 +0200 Subject: [PATCH] Add TKinter Visualizer Emulator(Slow but works) --- .gitignore | 5 ++ pyleds/lib/Color.py | 52 +++++++++++++++++++ pyleds/lib/Litsimaja.py | 8 +-- pyleds/lib/{ => strip}/FakeStrip.py | 42 ++++++++++++--- pyleds/lib/strip/TkinterStrip.py | 73 ++++++++++++++++++++++++++ pyleds/lib/strip/WindowStrip.py | 79 +++++++++++++++++++++++++++++ pyleds/program/siinus/Wipes.py | 9 +++- 7 files changed, 257 insertions(+), 11 deletions(-) create mode 100644 .gitignore create mode 100644 pyleds/lib/Color.py rename pyleds/lib/{ => strip}/FakeStrip.py (70%) create mode 100644 pyleds/lib/strip/TkinterStrip.py create mode 100644 pyleds/lib/strip/WindowStrip.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..02b0685 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.idea/ +.git/ +__pycache__/ +*.pyc +*.swp \ No newline at end of file diff --git a/pyleds/lib/Color.py b/pyleds/lib/Color.py new file mode 100644 index 0000000..25d2564 --- /dev/null +++ b/pyleds/lib/Color.py @@ -0,0 +1,52 @@ +Color = lambda r, g, b: (r << 16) | (g << 8) | b + + +def Color_to_list(color): + rgb = [(color >> 16) & 255, (color >> 8) & 255, color & 255] + w = (color >> 24) & 255 + if w: + rgb.append(w) + return rgb + + +def Color_to_hex(color): + s = [(color >> 16) & 255, (color >> 8) & 255, color & 255] + return "#"+''.join(map(lambda y: ("0" + hex(int(y))[2:])[-2:], s)) + + +def Color_to_rgb(color): + return (color >> 16) & 255, (color >> 8) & 255, color & 255 + + +def hex_to_rgb(value): + value = value.lstrip('#') if value[0] == "#" else value + lv = len(value) + return [int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3)] + + +def to_Color(value): + if type(value) == str: + r, g, b = hex_to_rgb(value) + return Color(r, g, b) + if type(value) == int: + if 0 <= value <= 16777215: + return value + else: + raise ValueError("RGB Int exceeded the lower and upper limits") + if type(value) == list or type(value) == tuple: + out = [] + for n, code in enumerate(value): + if not str(code).isnumeric(): + raise ValueError("RGB color must be numeric") + if n > 4: + raise ValueError("RGB contains more than 4 values") + _code = int(code) + if _code < 0: + print(f"RGB value under 0") + out.append(0) + elif _code > 255: + print(f"RGB value over 255") + out.append(255) + else: + out.append(_code) + return Color(out[0], out[1], out[2]) diff --git a/pyleds/lib/Litsimaja.py b/pyleds/lib/Litsimaja.py index 6544cef..448fd33 100644 --- a/pyleds/lib/Litsimaja.py +++ b/pyleds/lib/Litsimaja.py @@ -1,21 +1,21 @@ from rpi_ws281x import PixelStrip -from lib.FakeStrip import FakeStrip +# from lib.strip.WindowStrip import WindowStrip +# from lib.strip.TkinterStrip import TkinterStrip from lib.LoopSwitch import LoopSwitch class Litsimaja(object): - _strip: PixelStrip _loops: [] def __init__(self): - self._strip = FakeStrip(290, 18, 800000, 10, False, 255, 0, 4104) + self._strip = PixelStrip(290, 18, 800000, 10, False, 255, 0, 4104) self._loops = [] self._strip.begin() def count_pixels(self) -> int: return self._strip.numPixels() - def get_strip(self) -> PixelStrip: + def get_strip(self): return self._strip def set_pixel_color(self, n: int, color: int) -> None: diff --git a/pyleds/lib/FakeStrip.py b/pyleds/lib/strip/FakeStrip.py similarity index 70% rename from pyleds/lib/FakeStrip.py rename to pyleds/lib/strip/FakeStrip.py index ad20338..5a05140 100644 --- a/pyleds/lib/FakeStrip.py +++ b/pyleds/lib/strip/FakeStrip.py @@ -1,13 +1,15 @@ -from rpi_ws281x import PixelStrip import atexit +from lib.Color import Color class _LedData(object): - _led: [] def __init__(self, channel, size): self.size = size self.channel = channel + self._led = {} + for i in range(size): + self._led[i] = 0 def __getitem__(self, pos): """Return the 24-bit RGB color value at the provided position or slice @@ -17,7 +19,6 @@ class _LedData(object): # and returning them in a list. if isinstance(pos, slice): return [self._led[n] for n in range(*pos.indices(self.size))] - # Else assume the passed in value is a number to the position. else: return self._led[pos] @@ -33,11 +34,11 @@ class _LedData(object): self._led[n] = value[index] index += 1 # Else assume the passed in value is a number to the position. - # else: + else: self._led[pos] = value -class FakeStrip(PixelStrip): +class FakeStrip(object): _brightness: int def __init__(self, num, pin, freq_hz=800000, dma=10, invert=False, @@ -78,7 +79,7 @@ class FakeStrip(PixelStrip): def show(self): """Update the display with the data from the LED buffer.""" - return # render + return def getBrightness(self) -> int: return self._brightness @@ -92,3 +93,32 @@ class FakeStrip(PixelStrip): def numPixels(self): """Return the number of pixels in the display.""" return self._led_data.size + + def setPixelColor(self, n, color): + """Set LED at position n to the provided 24-bit color value (in RGB order). + """ + self._led_data[n] = color + + def setPixelColorRGB(self, n, red, green, blue): + """Set LED at position n to the provided red, green, and blue color. + Each color component should be a value from 0 to 255 (where 0 is the + lowest intensity and 255 is the highest intensity). + """ + self.setPixelColor(n, Color(red, green, blue)) + + def getPixels(self): + """Return an object which allows access to the LED display data as if + it were a sequence of 24-bit RGB values. + """ + return self._led_data + + def getPixelColor(self, n): + """Get the 24-bit RGB color value for the LED at position n.""" + return self._led_data[n] + + def getPixelColorRGB(self, n): + c = lambda: None + setattr(c, 'r', self._led_data[n] >> 16 & 0xff) + setattr(c, 'g', self._led_data[n] >> 8 & 0xff) + setattr(c, 'b', self._led_data[n] & 0xff) + return c diff --git a/pyleds/lib/strip/TkinterStrip.py b/pyleds/lib/strip/TkinterStrip.py new file mode 100644 index 0000000..9076caf --- /dev/null +++ b/pyleds/lib/strip/TkinterStrip.py @@ -0,0 +1,73 @@ +from threading import Thread +from tkinter import Tk, Label +from .FakeStrip import FakeStrip +from lib.Color import Color_to_list +from time import sleep +import numpy as np +from PIL import Image, ImageTk + + +class Visualizer(Thread): + + def __init__(self, x, y, multi): + Thread.__init__(self) + self.x, self.y = x, y + self.multi = multi + self.root = None + self.panel = None + self.first_loop = True + self.start() + sleep(0.04) + + def run(self): + self.root = Tk() + self.root.title("RGB Visualizer") + self.panel = Label(self.root) + self.root.geometry(f'{int(self.x * self.multi)}x{int(self.y * self.multi)}') + self.panel.pack() + self.root.mainloop() + + def print_image(self, frame): + self.panel.configure(image=frame) + self.panel.frame = frame + + +class TkinterStrip(FakeStrip): + + def __init__(self, num, pin, freq_hz=800000, dma=10, invert=False, + brightness=255, channel=0, strip_type=None, gamma=None): + super().__init__(num, pin, freq_hz, dma, invert, brightness, channel, strip_type, gamma) + self.viz = Visualizer(95, 50, 6) + # self.ms = time() * 1000.0 + self.show() + + def show(self): + """Update the display with the data from the LED buffer.""" + bp = [49, 99, 194, 244, 290] + x = self.viz.x + y = self.viz.y + + data = np.zeros((y, x, 3), dtype=np.uint8) + + for s in range(self.numPixels()): + x1 = y1 = 0 + if s < 49: + x1 = x / 2 + s - 1 + elif s < 99: + x1 = x - 1 + y1 = (s - 49) + elif s < 194: + x1 = x + (99 - s) - 1 + y1 = y - 1 + elif s < 244: + y1 = y + (194 - s) - 1 + elif s < 290: + x1 = s - 244 + x1 = int(x1) + y1 = int(y1) + data[y1, x1] = Color_to_list(self.getPixelColor(s)) + img = Image.fromarray(data, 'RGB').resize((int(self.viz.x * self.viz.multi), int(self.viz.y * self.viz.multi))) + self.viz.print_image(ImageTk.PhotoImage(img)) + # ms = time() * 1000.0 + # print(ms - self.ms) + # self.ms = ms diff --git a/pyleds/lib/strip/WindowStrip.py b/pyleds/lib/strip/WindowStrip.py new file mode 100644 index 0000000..22f3c7b --- /dev/null +++ b/pyleds/lib/strip/WindowStrip.py @@ -0,0 +1,79 @@ +from threading import Thread +from tkinter import Tk, Canvas +from lib.strip.FakeStrip import FakeStrip +import time + + +class Visualizer(Thread): + + def __init__(self): + Thread.__init__(self) + self.x, self.y = 1000, 500 + self.first_loop = True + self.start() + time.sleep(0.01) + + def run(self): + self.root = Tk() + self.canvas = Canvas(self.root, width=self.x, height=self.y) + self.root.geometry(f'{self.x}x{self.y}') + self.canvas.grid() + self.root.mainloop() + + +class WindowStrip(FakeStrip): + + def __init__(self, num, pin, freq_hz=800000, dma=10, invert=False, + brightness=255, channel=0, strip_type=None, gamma=None): + super().__init__(num, pin, freq_hz, dma, invert, brightness, channel, strip_type, gamma) + self.viz = Visualizer() + self.show() + + def show(self): + """Update the display with the data from the LED buffer.""" + size = 10.5 + bp = [47, 97, 191, 242, 289] + x = self.viz.x + y = self.viz.y + first_loop = self.viz.first_loop + + for s in range(self.numPixels()): + rgb_int = self.getPixelColor(s) + r = rgb_int & 255 + g = (rgb_int >> 8) & 255 + b = (rgb_int >> 16) & 255 + + if not first_loop: + self.viz.canvas.itemconfig(s + 1, fill="#%02x%02x%02x" % (r, g, b)) + else: + x1 = 0 + y1 = 0 + x2 = 0 + y2 = 0 + if s < bp[0]: + x1 = (x / 2) + s * size + y1 = 0 + x2 = x1 + size + y2 = size + elif s < bp[1]: + x1 = x - size + y1 = -(bp[0] - s) * size + x2 = x1 + size + y2 = y1 + size + elif s < bp[2]: + x1 = x + (bp[1] - s) * size + y1 = y + x2 = x1 - size + y2 = y1 - size + elif s < bp[3]: + x1 = 0 + y1 = y + (bp[2] - s) * size + x2 = x1 + size + y2 = y1 - size + elif s < bp[4]: + x1 = size * - (bp[3] - s) + y1 = 0 + x2 = x1 + size + y2 = y1 + size + self.viz.canvas.create_rectangle(x1, y1, x2, y2, fill="#%02x%02x%02x" % (r, g, b), outline="") + self.viz.first_loop = False diff --git a/pyleds/program/siinus/Wipes.py b/pyleds/program/siinus/Wipes.py index ac4cbb7..d108f6a 100644 --- a/pyleds/program/siinus/Wipes.py +++ b/pyleds/program/siinus/Wipes.py @@ -1,6 +1,13 @@ from lib.Program import Program from lib.Litsimaja import Litsimaja -from rpi_ws281x import Color + + +def Color(red, green, blue): + """Convert the provided red, green, blue color to a 24-bit color value. + Each color component should be a value 0-255 where 0 is the lowest intensity + and 255 is the highest intensity. + """ + return (red << 16) | (green << 8) | blue def name():