hermes-agent/skills/creative/pixel-art/SKILL.md
Teknium d40a828a8b
feat(pixel-art): add hardware palettes and video animation (#12725)
Expand the pixel-art skill from 2 presets (arcade, snes) to 14 presets
with hardware-accurate palettes (NES, Game Boy, PICO-8, C64, Apple II,
MS Paint, CRT mono), plus a procedural video overlay pipeline.

Ported from Synero/pixel-art-studio (MIT). Full attribution in
ATTRIBUTION.md.

What's in:
- scripts/palettes.py — 28 named RGB palettes (hardware + artistic)
- scripts/pixel_art.py — 14 presets, named palette support, CLI
- scripts/pixel_art_video.py — 12 animation scenes (stars, rain,
  fireflies, snow, embers, lightning, etc.) → MP4/GIF via ffmpeg
- references/palettes.md — palette catalog
- SKILL.md — clarify-tool workflow (offer style, then optional scene)

What's out (intentional):
- Wu's quantizer (PIL's built-in quantize suffices)
- Sobel edge-aware downsample (scipy dep not worth it)
- Atkinson/Bayer dither (would need numpy reimpl)
- Pollinations text-to-image (Hermes uses image_generate instead)

Video pipeline uses subprocess.run with check=True (replaces os.system)
and tempfile.TemporaryDirectory (replaces manual cleanup).
2026-04-19 16:59:20 -07:00

7.6 KiB

name description version author license metadata
pixel-art Convert images into retro pixel art with hardware-accurate palettes (NES, Game Boy, PICO-8, C64, etc.), and animate them into short videos. Presets cover arcade, SNES, and 10+ era-correct looks. Use `clarify` to let the user pick a style before generating. 2.0.0 dodo-reach MIT
hermes
tags category credits
creative
pixel-art
arcade
snes
nes
gameboy
retro
image
video
creative
Hardware palettes and animation loops ported from Synero/pixel-art-studio (MIT) — https://github.com/Synero/pixel-art-studio

Pixel Art

Convert any image into retro pixel art, then optionally animate it into a short MP4 or GIF with era-appropriate effects (rain, fireflies, snow, embers).

Two scripts ship with this skill:

  • scripts/pixel_art.py — photo → pixel-art PNG (Floyd-Steinberg dithering)
  • scripts/pixel_art_video.py — pixel-art PNG → animated MP4 (+ optional GIF)

Each is importable or runnable directly. Presets snap to hardware palettes when you want era-accurate colors (NES, Game Boy, PICO-8, etc.), or use adaptive N-color quantization for arcade/SNES-style looks.

When to Use

  • User wants retro pixel art from a source image
  • User asks for NES / Game Boy / PICO-8 / C64 / arcade / SNES styling
  • User wants a short looping animation (rain scene, night sky, snow, etc.)
  • Posters, album covers, social posts, sprites, characters, avatars

Workflow

Before generating, confirm the style with the user. Different presets produce very different outputs and regenerating is costly.

Step 1 — Offer a style

Call clarify with 4 representative presets. Pick the set based on what the user asked for — don't just dump all 14.

Default menu when the user's intent is unclear:

clarify(
    question="Which pixel-art style do you want?",
    choices=[
        "arcade — bold, chunky 80s cabinet feel (16 colors, 8px)",
        "nes — Nintendo 8-bit hardware palette (54 colors, 8px)",
        "gameboy — 4-shade green Game Boy DMG",
        "snes — cleaner 16-bit look (32 colors, 4px)",
    ],
)

When the user already named an era (e.g. "80s arcade", "Gameboy"), skip clarify and use the matching preset directly.

Step 2 — Offer animation (optional)

If the user asked for a video/GIF, or the output might benefit from motion, ask which scene:

clarify(
    question="Want to animate it? Pick a scene or skip.",
    choices=[
        "night — stars + fireflies + leaves",
        "urban — rain + neon pulse",
        "snow — falling snowflakes",
        "skip — just the image",
    ],
)

Do NOT call clarify more than twice in a row. One for style, one for scene if animation is on the table. If the user explicitly asked for a specific style and scene in their message, skip clarify entirely.

Step 3 — Generate

Run pixel_art() first; if animation was requested, chain into pixel_art_video() on the result.

Preset Catalog

Preset Era Palette Block Best for
arcade 80s arcade adaptive 16 8px Bold posters, hero art
snes 16-bit adaptive 32 4px Characters, detailed scenes
nes 8-bit NES (54) 8px True NES look
gameboy DMG handheld 4 green shades 8px Monochrome Game Boy
gameboy_pocket Pocket handheld 4 grey shades 8px Mono GB Pocket
pico8 PICO-8 16 fixed 6px Fantasy-console look
c64 Commodore 64 16 fixed 8px 8-bit home computer
apple2 Apple II hi-res 6 fixed 10px Extreme retro, 6 colors
teletext BBC Teletext 8 pure 10px Chunky primary colors
mspaint Windows MS Paint 24 fixed 8px Nostalgic desktop
mono_green CRT phosphor 2 green 6px Terminal/CRT aesthetic
mono_amber CRT amber 2 amber 6px Amber monitor look
neon Cyberpunk 10 neons 6px Vaporwave/cyber
pastel Soft pastel 10 pastels 6px Kawaii / gentle

Named palettes live in scripts/palettes.py (see references/palettes.md for the complete list — 28 named palettes total). Any preset can be overridden:

pixel_art("in.png", "out.png", preset="snes", palette="PICO_8", block=6)

Scene Catalog (for video)

Scene Effects
night Twinkling stars + fireflies + drifting leaves
dusk Fireflies + sparkles
tavern Dust motes + warm sparkles
indoor Dust motes
urban Rain + neon pulse
nature Leaves + fireflies
magic Sparkles + fireflies
storm Rain + lightning
underwater Bubbles + light sparkles
fire Embers + sparkles
snow Snowflakes + sparkles
desert Heat shimmer + dust

Invocation Patterns

Python (import)

import sys
sys.path.insert(0, "/home/teknium/.hermes/skills/creative/pixel-art/scripts")
from pixel_art import pixel_art
from pixel_art_video import pixel_art_video

# 1. Convert to pixel art
pixel_art("/path/to/photo.jpg", "/tmp/pixel.png", preset="nes")

# 2. Animate (optional)
pixel_art_video(
    "/tmp/pixel.png",
    "/tmp/pixel.mp4",
    scene="night",
    duration=6,
    fps=15,
    seed=42,
    export_gif=True,
)

CLI

cd /home/teknium/.hermes/skills/creative/pixel-art/scripts

python pixel_art.py in.jpg out.png --preset gameboy
python pixel_art.py in.jpg out.png --preset snes --palette PICO_8 --block 6

python pixel_art_video.py out.png out.mp4 --scene night --duration 6 --gif

Pipeline Rationale

Pixel conversion:

  1. Boost contrast/color/sharpness (stronger for smaller palettes)
  2. Posterize to simplify tonal regions before quantization
  3. Downscale by block with Image.NEAREST (hard pixels, no interpolation)
  4. Quantize with Floyd-Steinberg dithering — against either an adaptive N-color palette OR a named hardware palette
  5. Upscale back with Image.NEAREST

Quantizing AFTER downscale keeps dithering aligned with the final pixel grid. Quantizing before would waste error-diffusion on detail that disappears.

Video overlay:

  • Copies the base frame each tick (static background)
  • Overlays stateless-per-frame particle draws (one function per effect)
  • Encodes via ffmpeg libx264 -pix_fmt yuv420p -crf 18
  • Optional GIF via palettegen + paletteuse

Dependencies

  • Python 3.9+
  • Pillow (pip install Pillow)
  • ffmpeg on PATH (only needed for video — Hermes installs package this)

Pitfalls

  • Pallet keys are case-sensitive ("NES", "PICO_8", "GAMEBOY_ORIGINAL").
  • Very small sources (<100px wide) collapse under 8-10px blocks. Upscale the source first if it's tiny.
  • Fractional block or palette will break quantization — keep them positive ints.
  • Animation particle counts are tuned for ~640x480 canvases. On very large images you may want a second pass with a different seed for density.
  • mono_green / mono_amber force color=0.0 (desaturate). If you override and keep chroma, the 2-color palette can produce stripes on smooth regions.
  • clarify loop: call it at most twice per turn (style, then scene). Don't pepper the user with more picks.

Verification

  • PNG is created at the output path
  • Clear square pixel blocks visible at the preset's block size
  • Color count matches preset (eyeball the image or run Image.open(p).getcolors())
  • Video is a valid MP4 (ffprobe can open it) with non-zero size

Attribution

Named hardware palettes and the procedural animation loops in pixel_art_video.py are ported from pixel-art-studio (MIT). See ATTRIBUTION.md in this skill directory for details.