Last active
February 11, 2026 17:56
-
-
Save kelvan/ed439852e3d8ff3939d2bc93ffa1ffe7 to your computer and use it in GitHub Desktop.
Display any emoji on your WLED Matrix
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """Render an emoji on a WLED matrix via JSON API.""" | |
| import argparse | |
| import sys | |
| from pathlib import Path | |
| import requests | |
| from PIL import Image, ImageDraw, ImageFont | |
| EMOJI_FONT_PATHS = [ | |
| "/usr/share/fonts/noto/NotoColorEmoji.ttf", | |
| "/usr/share/fonts/google-noto/NotoColorEmoji.ttf", | |
| "/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf", | |
| "/usr/share/fonts/noto-emoji/NotoColorEmoji.ttf", | |
| "/usr/share/fonts/TTF/NotoColorEmoji.ttf", | |
| "/System/Library/Fonts/Apple Color Emoji.ttc", | |
| ] | |
| def find_font() -> str | None: | |
| for path in EMOJI_FONT_PATHS: | |
| if Path(path).exists(): | |
| return path | |
| return None | |
| def render_emoji(emoji: str, size: int, debug: bool = False) -> Image.Image: | |
| """Render an emoji to a sized RGBA image.""" | |
| font_path = find_font() | |
| if font_path is None: | |
| print("No emoji font found.", file=sys.stderr) | |
| sys.exit(1) | |
| font = ImageFont.truetype(font_path, 109) | |
| # Large canvas to guarantee the emoji fits | |
| canvas_size = 512 | |
| img = Image.new("RGBA", (canvas_size, canvas_size), (0, 0, 0, 0)) | |
| draw = ImageDraw.Draw(img) | |
| draw.text((100, 100), emoji, font=font, embedded_color=True) | |
| if debug: | |
| debug_dir = Path("debug_emoji") | |
| debug_dir.mkdir(exist_ok=True) | |
| img.save(debug_dir / "raw.png") | |
| print(f"Raw canvas bbox: {img.getbbox()}", file=sys.stderr) | |
| bbox = img.getbbox() | |
| if not bbox: | |
| print(f"Rendering produced empty image for '{emoji}'.", file=sys.stderr) | |
| sys.exit(1) | |
| img = img.crop(bbox) | |
| # Pad to square to preserve aspect ratio | |
| w, h = img.size | |
| side = max(w, h) | |
| square = Image.new("RGBA", (side, side), (0, 0, 0, 0)) | |
| square.paste(img, ((side - w) // 2, (side - h) // 2)) | |
| img = square.resize((size, size), Image.LANCZOS) | |
| if debug: | |
| img.save(debug_dir / "final.png") | |
| print(f"Final image saved to {debug_dir / 'final.png'}", file=sys.stderr) | |
| return img | |
| def image_to_wled_payload(img: Image.Image, brightness: int = 128) -> dict: | |
| """Convert an RGBA image to a WLED JSON API payload.""" | |
| img = img.convert("RGBA") | |
| pixels = list(img.get_flattened_data()) | |
| segment_data = [] | |
| for r, g, b, a in pixels: | |
| if a < 32: | |
| segment_data.append("000000") | |
| else: | |
| segment_data.append(f"{r:02x}{g:02x}{b:02x}") | |
| return { | |
| "on": True, | |
| "bri": brightness, | |
| "seg": {"i": segment_data}, | |
| } | |
| def send_to_wled(host: str, payload: dict) -> None: | |
| """Send pixel data to WLED via JSON API.""" | |
| url = f"http://{host}/json/state" | |
| resp = requests.post(url, json=payload, timeout=5) | |
| resp.raise_for_status() | |
| print(f"Sent to {host} — status: {resp.status_code}") | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description="Display an emoji on a WLED matrix") | |
| parser.add_argument("host", help="WLED device IP or hostname") | |
| parser.add_argument("emoji", help="Emoji character to display") | |
| parser.add_argument( | |
| "-b", "--brightness", type=int, default=128, help="Brightness 0-255" | |
| ) | |
| parser.add_argument( | |
| "-s", "--size", type=int, default=16, help="Matrix size NxN (default: 16)" | |
| ) | |
| parser.add_argument( | |
| "--debug", action="store_true", help="Save debug images to debug_emoji/" | |
| ) | |
| args = parser.parse_args() | |
| img = render_emoji(args.emoji, args.size, args.debug) | |
| payload = image_to_wled_payload(img, args.brightness) | |
| send_to_wled(args.host, payload) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment