Footprint-locked AI pixel-art retexturing. Upload a sprite, pick a vibe (or write your own), hit Run. PixelLock restyles the colors and shading while keeping the exact silhouette: pixel-perfect, guaranteed by construction.
Built for the Hugging Face Build Small hackathon, June 2026.
- 🎨 Live Space: https://huggingface.co/spaces/solarkyle/PixelLock (GPU; cold-starts in ~30-60s on first visit, then auto-sleeps)
- 🧠 Model weights: https://huggingface.co/solarkyle/pixellock-gemma-12b-pixelart-gguf
- 🛠️ Stack: fine-tuned Gemma-4-12B (GGUF) · llama.cpp GBNF grammar · hand-built FastAPI UI · Docker GPU Space
| Input | Output | Vibe |
|---|---|---|
| autumn harvest | ||
| cosmic void |
PixelLock uses the wrong kind of model on purpose: a text LLM, not an image generator, does pixel art, and that is exactly why it can't break your sprite.
Image models hallucinate pixels: ask one to "make this sword icy" and the blade drifts, the outline wobbles, the transparency smears. For a resource-pack artist that's useless; the silhouette has to stay exactly where it was.
So PixelLock doesn't generate an image at all. It serializes the sprite to a tiny text wire format, a PALETTE block plus a GRID of space-separated cells, and a fine-tuned text LLM rewrites that text in a new style:
PALETTE
. transparent
A 156,226,254
B 255,255,255
GRID 16x16
. . . . A A . . . . . . . . . .
. . . A B A A . . . . . . . . .
...
The magic is a per-sprite GBNF decoding grammar (llama.cpp). Before the model writes a single token, we compile a grammar from the input's footprint: every transparent cell is pinned to a fixed literal at its exact position, and only the colored cells are free to choose a palette key. The model is therefore physically incapable of moving a pixel or changing the shape. Footprint and transparency are preserved by construction, not hoped for, not post-checked away.
It's language modeling, not diffusion. The grammar is a hard constraint on the decoder, so the pixel-perfect guarantee is structural, not statistical.
This took unconstrained grid validity from ~0% (no model we tested could reliably emit a correct 32x32 grid) to 100% at every size from 16x16 to 128x128, including 2x upscale.
Two distinct jobs, cleanly separated:
- The grammar owns structure: shape, size, transparency. Deterministic, guaranteed.
- The fine-tuned LLM owns taste: which colors, where the highlights and shadows fall, how to read "molten lava" or "frozen crystal" as a coherent palette and shading ramp across the sprite.
Take the model away and you get the input back unchanged. The art is the model's contribution; the grammar just guarantees it lands on the right canvas.
A QLoRA fine-tune of google/gemma-4-12b-it on a curated corpus of palette-indexed pixel-art sprites (<=64px), trained to rewrite the wire format as a retheme/restyle and as a 2x upscale-with-shading. Completion-only loss, step-checkpointing with best-eval selection, held-out eval set; final eval loss ~0.38. Exported to GGUF (q4_k_m) for llama.cpp.
| Path | What |
|---|---|
app/ |
The deployed Space source: FastAPI app, renderer, footprint validator, Dockerfile |
spritebench/ |
SpriteBench: the benchmark that picked the base model. Every candidate (open-weight, <=32B) ran palette-indexed editing tasks through a validated, vision-judged pipeline; footprint compliance is machine-checked before any judging. See spritebench/README.md |
docs/TECH_WRITEUP.md |
Full technical writeup: wire format, grammar compiler, training, evaluation |
| Input | Prompt |
|---|---|
| a sword | Molten lava blade: glowing magma core, white-hot edge, charred guard |
| a pickaxe | Solid gold royal: shimmering gold ramp, warm highlights, amber shadows |
| an apple | Frozen crystal: icy pale blue, frosted surface, glowing white highlight |
| anything <=64px | ...your own vibe. The shape stays locked; only the style changes. |
The UI shows the raw text the model wrote and a silhouette-locked badge so you can see the guarantee, not just take our word for it.
MIT