mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-03-29 05:19:50 +00:00
Add splash screen icon and build-time SVG-to-RGB565 generator
- pyxis-icon.svg: Pyxis constellation icon (3 stars with connecting lines) - generate_splash.py: PlatformIO pre-build script that renders the SVG to a 160x160 RGB565 PROGMEM header (SplashImage.h) using cairosvg + Pillow - .gitignore: Exclude generated SplashImage.h - platformio.ini: Add generate_splash.py to both build environments Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
||||
.vscode/
|
||||
*.pyc
|
||||
docs/flasher/firmware/
|
||||
lib/tdeck_ui/Hardware/TDeck/SplashImage.h
|
||||
|
||||
98
generate_splash.py
Normal file
98
generate_splash.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
PlatformIO pre-build script: Convert pyxis-icon.svg to RGB565 PROGMEM header.
|
||||
|
||||
Generates lib/tdeck_ui/Hardware/TDeck/SplashImage.h containing a 160x160
|
||||
pixel splash image as a PROGMEM byte array. Skips regeneration if the
|
||||
header is already newer than the source SVG.
|
||||
|
||||
Requires: cairosvg, Pillow (gracefully skips if not installed)
|
||||
"""
|
||||
|
||||
Import("env")
|
||||
|
||||
import os
|
||||
import struct
|
||||
|
||||
ICON_SIZE = 160
|
||||
SVG_FILE = "pyxis-icon.svg"
|
||||
HEADER_FILE = os.path.join("lib", "tdeck_ui", "Hardware", "TDeck", "SplashImage.h")
|
||||
BG_COLOR = (0x1D, 0x1A, 0x1E) # #1D1A1E — matches fill_screen in show_splash()
|
||||
|
||||
|
||||
def rgb888_to_rgb565(r, g, b):
|
||||
"""Convert 8-bit RGB to 16-bit RGB565 (big-endian bytes)."""
|
||||
rgb565 = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
|
||||
return struct.pack(">H", rgb565)
|
||||
|
||||
|
||||
def should_regenerate(svg_path, header_path):
|
||||
"""Return True if the header needs regeneration."""
|
||||
if not os.path.exists(header_path):
|
||||
return True
|
||||
return os.path.getmtime(svg_path) > os.path.getmtime(header_path)
|
||||
|
||||
|
||||
project_dir = env.get("PROJECT_DIR", os.getcwd())
|
||||
svg_path = os.path.join(project_dir, SVG_FILE)
|
||||
header_path = os.path.join(project_dir, HEADER_FILE)
|
||||
|
||||
if not os.path.exists(svg_path):
|
||||
print(f"[generate_splash] {SVG_FILE} not found, skipping splash generation")
|
||||
elif not should_regenerate(svg_path, header_path):
|
||||
print(f"[generate_splash] {HEADER_FILE} is up-to-date, skipping")
|
||||
else:
|
||||
try:
|
||||
import cairosvg
|
||||
from PIL import Image
|
||||
import io
|
||||
except ImportError as e:
|
||||
print(f"[generate_splash] Missing dependency ({e}), skipping splash generation")
|
||||
print(f"[generate_splash] Install with: pip install cairosvg Pillow")
|
||||
else:
|
||||
print(f"[generate_splash] Rendering {SVG_FILE} -> {HEADER_FILE} ({ICON_SIZE}x{ICON_SIZE} RGB565)")
|
||||
|
||||
# Render SVG to PNG at target size
|
||||
png_data = cairosvg.svg2png(
|
||||
url=svg_path,
|
||||
output_width=ICON_SIZE,
|
||||
output_height=ICON_SIZE,
|
||||
)
|
||||
|
||||
img = Image.open(io.BytesIO(png_data)).convert("RGBA")
|
||||
|
||||
# Composite onto background color (handles transparency)
|
||||
bg = Image.new("RGBA", (ICON_SIZE, ICON_SIZE), BG_COLOR + (255,))
|
||||
bg.paste(img, (0, 0), img)
|
||||
img = bg.convert("RGB")
|
||||
|
||||
# Convert to RGB565 big-endian byte array
|
||||
pixels = list(img.getdata())
|
||||
rgb565_bytes = bytearray()
|
||||
for r, g, b in pixels:
|
||||
rgb565_bytes.extend(rgb888_to_rgb565(r, g, b))
|
||||
|
||||
# Write C header
|
||||
os.makedirs(os.path.dirname(header_path), exist_ok=True)
|
||||
|
||||
with open(header_path, "w") as f:
|
||||
f.write("// AUTO-GENERATED by generate_splash.py — do not edit\n")
|
||||
f.write("#ifndef SPLASH_IMAGE_H\n")
|
||||
f.write("#define SPLASH_IMAGE_H\n\n")
|
||||
f.write("#include <Arduino.h>\n\n")
|
||||
f.write(f"#define SPLASH_WIDTH {ICON_SIZE}\n")
|
||||
f.write(f"#define SPLASH_HEIGHT {ICON_SIZE}\n")
|
||||
f.write(f"#define HAS_SPLASH_IMAGE 1\n\n")
|
||||
f.write(f"// {ICON_SIZE}x{ICON_SIZE} RGB565 big-endian ({len(rgb565_bytes)} bytes)\n")
|
||||
f.write("static const uint8_t PROGMEM splash_image[] = {\n")
|
||||
|
||||
# Write bytes, 16 per line
|
||||
for i in range(0, len(rgb565_bytes), 16):
|
||||
chunk = rgb565_bytes[i:i+16]
|
||||
hex_str = ", ".join(f"0x{b:02X}" for b in chunk)
|
||||
trailing = "," if i + 16 < len(rgb565_bytes) else ""
|
||||
f.write(f" {hex_str}{trailing}\n")
|
||||
|
||||
f.write("};\n\n")
|
||||
f.write("#endif // SPLASH_IMAGE_H\n")
|
||||
|
||||
print(f"[generate_splash] Generated {len(rgb565_bytes)} bytes ({ICON_SIZE}x{ICON_SIZE} RGB565)")
|
||||
@@ -1,6 +1,8 @@
|
||||
; T-Deck environment using Bluedroid BLE stack (fallback, uses more RAM)
|
||||
[env:tdeck-bluedroid]
|
||||
extra_scripts = pre:version.py
|
||||
extra_scripts =
|
||||
pre:version.py
|
||||
pre:generate_splash.py
|
||||
platform = espressif32
|
||||
board = esp32-s3-devkitc-1
|
||||
framework = arduino
|
||||
@@ -79,6 +81,7 @@ build_flags =
|
||||
[env:tdeck]
|
||||
extra_scripts =
|
||||
pre:version.py
|
||||
pre:generate_splash.py
|
||||
pre:patch_nimble.py
|
||||
platform = espressif32
|
||||
board = esp32-s3-devkitc-1
|
||||
|
||||
21
pyxis-icon.svg
Normal file
21
pyxis-icon.svg
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<radialGradient id="starGlow">
|
||||
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.8"/>
|
||||
<stop offset="0.5" style="stop-color:#FFFFFF;stop-opacity:0.3"/>
|
||||
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<circle cx="256" cy="256" r="256" fill="#1D1A1E"/>
|
||||
<g transform="matrix(1, 0, 0, 1, 17.99218, -6.964715)">
|
||||
<path stroke="#FFFFFF" stroke-width="4.5" fill="none" stroke-linecap="round" opacity="0.6" d="M 245.377 322.844 L 181.759 90.3"/>
|
||||
<path stroke="#FFFFFF" stroke-width="4.5" fill="none" stroke-linecap="round" opacity="0.6" d="M 273.819 413.359 L 245.377 322.844"/>
|
||||
<circle cx="181.759" cy="90.3" r="20" fill="url(#starGlow)"/>
|
||||
<circle cx="181.759" cy="90.3" r="8" fill="#FFFFFF"/>
|
||||
<circle cx="245.377" cy="322.844" r="26" fill="url(#starGlow)"/>
|
||||
<circle cx="245.377" cy="322.844" r="10" fill="#FFFFFF"/>
|
||||
<circle cx="273.819" cy="413.359" r="22" fill="url(#starGlow)"/>
|
||||
<circle cx="273.819" cy="413.359" r="9" fill="#FFFFFF"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
Reference in New Issue
Block a user