From a714bd9d76a34f35df7e1a11efe377597c4a486d Mon Sep 17 00:00:00 2001 From: lounge Date: Tue, 27 Jan 2026 22:42:37 +0100 Subject: [PATCH] changes needed to adapt to new environment --- config.py | 25 ++--- configs/pixelthudconf.py | 8 ++ deckensteuerung.service | 3 + html/draw.html | 38 ++++++-- html/gallery.html | 191 +++++++++++++++++++++++++++++++++++++++ main.py | 69 +++++++++++++- setup-apps.sh | 16 ++-- 7 files changed, 318 insertions(+), 32 deletions(-) create mode 100644 configs/pixelthudconf.py create mode 100644 html/gallery.html diff --git a/config.py b/config.py index 5f1e686..d8d9c91 100644 --- a/config.py +++ b/config.py @@ -24,7 +24,7 @@ ScreenY = 40 DefaultBrightness = 0.6 -Serial = "/dev/ttyACM0" +Serial = "/dev/ttyTeensy" # kills app after some seconds if it sends no data NoDataTimeout = 40 @@ -47,6 +47,7 @@ Apps = [ AppConfig(guiname="Digi Clock", name="digiclock", cmd="./digi_clock.py"), AppConfig(guiname="Text Scroller MQTT", name="textscroll", cmd="./textscroll.py"), AppConfig(guiname="Spot", name="spot", cmd="./spot.py", white=True), + AppConfig(guiname="Fireworks", name="fireworks", cmd="./fireworks.py", white=False), AppConfig(guiname="Flicker", name="flicker", cmd="./flicker"), AppConfig(guiname="Pixelflut", name="pixelflut", cmd="./pixelflut", persistent=True), @@ -60,17 +61,17 @@ Apps = [ AppConfig(guiname="Wget Video/Gif/Images", name="wget", cmd="./wget.sh"), # juergen/pixelfoo - AppConfig(guiname="Congress noise", name="cnoise", cmd="./cnoise", path="pixelfoo-apps/target/release/"), - AppConfig(guiname="Color code", name="colorcode", cmd="./colorcode", path="pixelfoo-apps/target/release/"), - AppConfig(guiname="Game of Life", name="life", cmd="./life", path="pixelfoo-apps/target/release/"), - AppConfig(guiname="Matrix Code", name="matrix-code", cmd="./matrix-code", path="pixelfoo-apps/target/release/"), - AppConfig(guiname="Lorenz Attractor", name="lorenz", cmd="./lorenz", path="pixelfoo-apps/target/release/"), - AppConfig(guiname="Primes", name="primes", cmd="./primes", path="pixelfoo-apps/target/release/"), - AppConfig(guiname="Alien Message", name="alien-message", cmd="./alien-message", path="pixelfoo-apps/target/release/"), - AppConfig(guiname="Dual Moodlight", name="bimood", cmd="./bimood", path="pixelfoo-apps/target/release/"), - AppConfig(guiname="Maze", name="maze", cmd="./maze", path="pixelfoo-apps/target/release/"), - AppConfig(guiname="Dual Maze", name="dualmaze", cmd="./dualmaze", path="pixelfoo-apps/target/release/"), - AppConfig(guiname="Predator & Prey", name="predprey", cmd="./predprey", path="pixelfoo-apps/target/release/"), + AppConfig(guiname="Congress noise", name="cnoise", cmd="./cnoise"), + AppConfig(guiname="Color code", name="colorcode", cmd="./colorcode"), + AppConfig(guiname="Game of Life", name="life", cmd="./life"), + AppConfig(guiname="Matrix Code", name="matrix-code", cmd="./matrix-code"), + AppConfig(guiname="Lorenz Attractor", name="lorenz", cmd="./lorenz"), + AppConfig(guiname="Primes", name="primes", cmd="./primes"), + AppConfig(guiname="Alien Message", name="alien-message", cmd="./alien-message"), + AppConfig(guiname="Dual Moodlight", name="bimood", cmd="./bimood"), + AppConfig(guiname="Maze", name="maze", cmd="./maze"), + AppConfig(guiname="Dual Maze", name="dualmaze", cmd="./dualmaze"), + AppConfig(guiname="Predator & Prey", name="predprey", cmd="./predprey"), # App(guiname="Beat Saber Ceiling", name="beatsaberceiling", cmd="./beatsaberceiling.py", path="beatsaberceiling"), diff --git a/configs/pixelthudconf.py b/configs/pixelthudconf.py new file mode 100644 index 0000000..6be5ddb --- /dev/null +++ b/configs/pixelthudconf.py @@ -0,0 +1,8 @@ +Apps = [ + # pixelthud + {"guiname": "Fading Pixels", "name": "fadingpxls", "cmd": "apps/fading_pixels.py", "persistent": False}, + {"guiname": "Plane Wave", "name": "planewave", "cmd": "apps/plane_wave.py", "persistent": False}, + {"guiname": "Rock-paper-scissors-spock-lizard", "name": "rps", "cmd": "apps/rps.py", "persistent": False}, + {"guiname": "Doom Fire", "name": "doomfire", "cmd": "apps/doom_fire_psx2.py", "persistent": False}, + {"guiname": "Maxwell FDTD", "name": "fdtd", "cmd": "apps/fdtd.py", "persistent": False}, +] diff --git a/deckensteuerung.service b/deckensteuerung.service index 5e6dbce..cd99346 100644 --- a/deckensteuerung.service +++ b/deckensteuerung.service @@ -2,6 +2,9 @@ Description=Deckensteuerung Server [Service] +User=lounge +Group=uucp + Type=exec ExecStart=/usr/bin/python3 -u /home/ds/pixelserver2/main.py WorkingDirectory=/home/ds/pixelserver2 diff --git a/html/draw.html b/html/draw.html index e56568e..87e8512 100644 --- a/html/draw.html +++ b/html/draw.html @@ -13,8 +13,10 @@ --header-color: #1a202c; --subheader-color: #718096; --toolbar-bg: #ffffff; - --button-bg: #e53e3e; - --button-hover-bg: #c53030; + --button-red-bg: #e53e3e; + --button-red-hover-bg: #c53030; + --button-blue-bg: #3b82f6; + --button-blue-hover-bg: #3167bf; --swatch-border: #e2e8f0; --active-swatch-border: #3b82f6; --input-bg: #f7fafc; @@ -154,9 +156,8 @@ color: var(--text-color); } - #clearCanvas { + .control-group button { padding: 0.6rem 1rem; - background-color: var(--button-bg); color: white; border: none; border-radius: 0.5rem; @@ -165,8 +166,20 @@ transition: background-color 0.2s ease; } + #clearCanvas { + background-color: var(--button-red-bg); + } + #clearCanvas:hover { - background-color: var(--button-hover-bg); + background-color: var(--button-red-hover-bg); + } + + #saveCanvas { + background-color: var(--button-blue-bg); + } + + #saveCanvas:hover { + background-color: var(--button-blue-hover-bg); } #grid-container { @@ -191,7 +204,7 @@

Pixel Art Canvas

-

Click and drag to paint. Your changes are live.

+

Click and drag to paint. Your changes are live. Gallery

@@ -210,6 +223,7 @@
+
@@ -224,14 +238,16 @@ const paletteContainer = document.getElementById('color-palette'); const brushSizeSelect = document.getElementById('brushSize'); const clearCanvasBtn = document.getElementById('clearCanvas'); + const saveCanvasBtn = document.getElementById('saveCanvas'); // --- Grid Configuration --- const GRID_ROWS = 40; const GRID_COLS = 80; const DEFAULT_COLOR = '#ffffff'; const PRESET_COLORS = [ - '#ffffff', '#c0c0c0', '#ff0000', '#ffa500', '#ffff00', - '#008000', '#0000ff', '#4b0082', '#ee82ee', '#000000' + '#ffffff', '#c0c0c0', '#ff0000', '#ffa500', + '#ffff00', '#008000', '#0000ff', '#4b0082', + '#ee82ee', '#b5651d', '#000000', ]; // --- State Management --- @@ -414,6 +430,12 @@ })) }); + saveCanvasBtn.addEventListener('click', async () => { + await fetch("/save_frame").then((res) => { + // if (res.ok) ... + }); + }); + // --- Initial Load --- initializePalette(); initializeGrid(); diff --git a/html/gallery.html b/html/gallery.html new file mode 100644 index 0000000..4a2bb12 --- /dev/null +++ b/html/gallery.html @@ -0,0 +1,191 @@ + + + + + + Pixel Art Gallery + + + + +
+
+

Pixel Art Gallery

+

Back to Drawing.

+
+ + +
+ + + + + + + + diff --git a/main.py b/main.py index 6a90cb6..9d8e085 100755 --- a/main.py +++ b/main.py @@ -1,5 +1,8 @@ #!/usr/bin/env python3 +import base64 +import datetime +import io import json import logging import math @@ -8,15 +11,18 @@ import subprocess import threading import time from collections import OrderedDict +from pathlib import Path +import PIL.Image import bottle -import numpy as np -import serial # noinspection PyUnresolvedReferences import bottle.ext.websocket as bottle_ws +import geventwebsocket.websocket +import numpy as np +import urllib.request +import serial # noinspection PyUnresolvedReferences from bottle.ext.websocket import GeventWebSocketServer -import geventwebsocket.websocket import config import filters @@ -507,7 +513,7 @@ def pixel(x, y, r, g, b, w): data.buffer[y][x][2] = b if data.channels == 4: data.buffer[y][x][3] = w - runner.datasource.pushData(data) + runner.app.datasource.pushData(data) return "ok" @@ -558,6 +564,61 @@ def frame_ws(ws: geventwebsocket.websocket.WebSocket): runner.app.datasource.pushData(data) +@bottle.route("/save_frame") +def save_frame(): + screenshot_dir = Path("./screenshots") + screenshot_dir.mkdir(parents=True, exist_ok=True) + data = runner.datasource.getData() + PIL.Image.fromarray(data.buffer.copy().astype("u1")).save(screenshot_dir.joinpath(f"frame-{datetime.datetime.now().astimezone().replace(tzinfo=None).isoformat()}.png")) + return "ok" + + +@bottle.post("/load_frame") +def load_frame(): + src = bottle.request.json["src"] + buf = np.array(PIL.Image.open(io.BytesIO(urllib.request.urlopen(src).read())), dtype=np.uint8) + + if buf.shape == (80, 40, 3) or buf.shape == (80, 40, 4): + buf = buf.swapaxes(0, 1) + elif buf.shape == (40, 80, 3) or buf.shape == (40, 80, 4): + pass + else: + return 415 + + if runner.app.name != "pixelcanvas": + startApp("pixelcanvas") + for _ in range(200): + if runner.requestedApp is None: + break + time.sleep(0.01) + else: + return 500 + + runner.app.datasource.pushData(Frame(buf, buf.shape[2])) + return "ok" + + +@bottle.route("/gallery_frames") +def gallery_frames(): + screenshot_dir = Path("./screenshots") + if not screenshot_dir.exists(): + return [] + frames = [] + for p in screenshot_dir.glob("frame-*.png"): + try: + dt = datetime.datetime.fromisoformat(p.stem.removeprefix("frame-")).astimezone().astimezone(datetime.timezone.utc) + i = "data:image/png;base64," + base64.b64encode(p.read_bytes()).decode() + frames.append({ + "dt": dt.isoformat(), + "caption": dt.strftime("%d.%m.%Y %H:%M"), + "src": i, + }) + except: + continue + frames.sort(key=lambda f: f["dt"], reverse=True) + return json.dumps(frames) + + @bottle.route("/") def index(): return bottle.static_file("index.html", root='html') diff --git a/setup-apps.sh b/setup-apps.sh index 9f2e725..ad5ca64 100755 --- a/setup-apps.sh +++ b/setup-apps.sh @@ -23,20 +23,20 @@ if hash cargo 2>/dev/null; then # get juergens apps mkdir -p external pushd external - if [ ! -d "pixelfoo" ]; then - git clone https://git.chaospott.de/juergen/pixelfoo.git + if [ ! -d "pixelfoo-apps" ]; then + git clone https://git.chaospott.de/starblue/pixelfoo-apps fi - pushd pixelfoo + pushd pixelfoo-apps git pull cargo build --release popd popd - cp -f external/pixelfoo/target/release/cnoise apps/ - cp -f external/pixelfoo/target/release/bimood apps/ - cp -f external/pixelfoo/target/release/predprey apps/ - cp -f external/pixelfoo/target/release/maze apps/ - cp -f external/pixelfoo/target/release/dualmaze apps/ + cp -f external/pixelfoo-apps/target/release/cnoise apps/ + cp -f external/pixelfoo-apps/target/release/bimood apps/ + cp -f external/pixelfoo-apps/target/release/predprey apps/ + cp -f external/pixelfoo-apps/target/release/maze apps/ + cp -f external/pixelfoo-apps/target/release/dualmaze apps/ fi #echo "Getting mathpixel"