Skip to content

Instantly share code, notes, and snippets.

@kelvan
Last active February 11, 2026 17:56
Show Gist options
  • Select an option

  • Save kelvan/ed439852e3d8ff3939d2bc93ffa1ffe7 to your computer and use it in GitHub Desktop.

Select an option

Save kelvan/ed439852e3d8ff3939d2bc93ffa1ffe7 to your computer and use it in GitHub Desktop.
Display any emoji on your WLED Matrix
#!/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