# PerfectPixel
**An AI-powered animation sprite generation studio**
Turn a one-line character description into a base character, then auto-generate 100+ motion
animations (walk, run, attack, magic, β¦) and complete 8-direction sprite sets β exported in
formats your game engine can import directly.




**[π Homepage](https://pp.andrew-ai.app/)**
**English** Β· [νκ΅μ΄](README.ko.md)
A power-up animation generated end-to-end by the actual pipeline (`gemini-3-pro-image`).
---
## Why this is a hard problem
Plenty of tools draw a pretty picture. Game developers need something else: a 6-frame walk
must be *exactly* 6 frames, every frame must read as the *same* character, the background must
be fully alpha-transparent, and the character's anchor must not jitter between frames β all in a
format an engine can import as-is.
AI models are bad at these constraints. Frame counts come out wrong, poses bleed into each
other, hair color drifts mid-strip (*identity drift*), and the chroma-key background spills
onto the character's silhouette.
**PerfectPixel's core idea: let the AI render, but enforce quality, consistency, and precision
with a deterministic post-processing pipeline.** Even when the AI's output is non-deterministic,
the result converges to a consistent quality bar. That is the engineering moat β see the
before/after comparisons below, all produced by the *real* pipeline code on *real* AI output.
## Features
- **Text β character**: describe a character, pick a style (pixel art, chibi, cartoon β¦), and
generate a base character with the background auto-removed.
- **100+ motion presets**: a categorized keyword catalog β walk, run, jump, attack, magic,
emotes, and more.
- **8-direction sets**: 5 directions are generated by AI and the 3 mirror directions are
derived by horizontal mirroring, for a consistent set at **37.5% lower generation cost**.
- **Self-correcting quality loop**: generate β matte β extract β inspect β corrective
regenerate, up to 3 passes, converging frame count and motion quality.
- **Real pixelization**: shared-palette quantization + pixel-grid snapping for authentic dot art.
- **Engine-friendly export**: sprite sheet + `manifest.json` + Aseprite-compatible JSON +
per-state GIF/APNG + individual frame PNGs, all in one pass.
- **Multi-provider**: choose Gemini, OpenRouter, fal.ai, or BytePlus as the backend.
- **Sessions & gallery**: work state is persisted to disk and results are auto-archived.
## Sample Output
| Walk | Idle | Cheer | Power Up |
|------|------|-------|----------|
|

|

|

|

|
| Dance | Victory | Block | Low HP |
|

|

|

|

|
## How it works
Generating one state (animation) runs this pipeline in the Go backend:
```
description + style + motion preset
β
βΌ
build prompt βββΊ AI image generation (horizontal filmstrip)
β
βΌ
background detect & matte βββΊ frame extraction (cell segmentation)
β
βΌ
inspect (frame count / identity drift / motion presence)
β
ββ pass ββββββββββββββΊ pixel quantization βββΊ done
β
ββ fail ββΊ corrective retry hint βββΊ regenerate (up to 3Γ)
```
Two things make this more than a naive retry loop:
- **Best-candidate scoring** β every attempt is scored `score = Found*100 β errors*10`, and the
best candidate is kept. A perfect result returns immediately; if 3 passes still aren't perfect,
the best-so-far is returned (never an empty hand). API errors and cancellations bail early.
- **Measurement-driven retry hints** β defects detected during inspection are converted into
precise English correction instructions injected into the next prompt (e.g. *"the previous
result read as 7 poses but exactly 6 are required; split the canvas into 6 even columnsβ¦"*).
Combined with user feedback, each pass becomes a **closed-loop self-correction** that converges
on the defect rather than rolling the dice again.
---
## The engineering: deterministic correction
The design philosophy in one line: **signal processing, not heuristics.** Three signal-processing
axes β wrapped in a self-diagnostic, self-correcting closed loop.
### 1. Background removal β chrominance-based matting
Instead of thresholding RGB, colors are converted to **YCbCr** and the background is separated
using only the chrominance (Cb, Cr) components, discarding luma (Y). This treats shaded and
bright magenta as the same color, and is inherently robust to JPEG's 4:2:0 subsampling (which
preserves luma but crushes chrominance). The background key is estimated as the **mode of a CbCr
histogram** (not the mean, so gradients/noise don't shift it), sampled from the four corners
where the character rarely intrudes. Soft alpha matting uses a **Hermite smoothstep** for edge
feathering, **despill** projects out only the key-direction color spill (keeping the character's
own colors), and a **4-connectivity flood fill** clears residual background while preserving
isolated interior pixels (so the character never gets holes). A **self-diagnostic magenta
fallback** re-mattes with pure `#FF00FF` when opacity or magenta-residue metrics spike.

> **WITHOUT** (naive RGB threshold): 2,739px of magenta residue + 8,164px pink halo remain.
> **WITH** (YCbCr matting + despill + flood fill): 2px residue, 3,447px halo, character body
> (~456K opaque px) fully preserved.
### 2. Frame segmentation β projection profile + DP optimal cut
Asking for a "6-frame filmstrip" rarely yields 6 evenly spaced poses; gaps are uneven and arms
touch the neighbor. PerfectPixel borrows OCR's **projection-profile + optimal-cut** technique.
A **vertical alpha projection** `P[x] = Ξ£_y Ξ±(x,y)` makes inter-pose gutters appear as valleys;
after smoothing, content runs are counted as the *natural pose count*. When poses are fused and
the valley vanishes, **dynamic programming** finds the globally optimal `expectedβ1` cuts,
minimizing a cost of `Ξ£ P[cut] + λ·(width β ideal)Β²`. Unlike greedy or connected-component
methods (which would fuse two touching poses into one blob), the DP cut finds the minimum-alpha
seam and splits *exactly* into the expected count, slicing through as little of the limbs as
possible.

> Real fire-mage *kick* strip (9 frames). **WITHOUT** (equal split): all 8 cut lines slice
> straight through the characters. **WITH** (projection + DP): **0 cut lines cross a character**;
> all 9 poses separated intact.
### 3. Frame alignment β alpha-weighted centroid
When centering a pose in its cell, using the **bounding-box center** lets a pose with an
outstretched arm or weapon push the torso to the opposite side β so the character jitters
left/right during playback. Instead, the **alpha-weighted centroid** (center of mass,
`cx = Σ(x·α) / Σα`) is aligned to the cell center; the large torso dominates the centroid, so no
matter how the limbs extend, the torso stays put. A shared scale unifies character size
(downscale only, CatmullRom interpolation), and a baseline offset preserves jump arcs. This
deterministically guarantees the "rock-steady axis" feel that matters most for game sprites.

> Real fire-mage *dash* strip (5 frames). Onion-skin overlay; red line is the cell center.
> **WITHOUT** (bbox center): character drifts left/right (centroid Ο = 27.2px).
> **WITH** (alpha-weighted centroid): pinned to center (Ο = 0.2px β ~135Γ more stable).
### 4. Pixel-art post-processing β quantization + grid snap
AI "pixel art" isn't really pixel art β it's a high-res image with anti-aliasing and gradients
and thousands of colors. A **shared palette** is extracted across all frames via median-cut
(per-frame quantization would flicker), with a perceptually-weighted color distance
`2drΒ² + 4dgΒ² + 3dbΒ²`. The real block size of the fake pixels is estimated from the mode of
same-color run lengths (an *unfake* technique), then **grid snap** fills each block with its
dominant color on a shared grid. Identity inspection runs *before* quantization, so drift
detection isn't dulled by the palette reduction.

> Real generated character (bottom row is a 4Γ zoom crop). **WITHOUT** (raw): 7,834 colors,
> blurry edges. **WITH** (shared-palette median-cut + grid snap): 12 colors, crisp dot grid.
### Identity & quality scoring
Two orthogonal axes verify the character stays consistent: a **64-bin RGB color histogram**
(intersection similarity, leave-one-out + base comparison to catch both outlier and batch drift)
and a **dHash perceptual hash** (9Γ8 grayscale, structure-sensitive and color-invariant β catches
silhouette changes the histogram can't). A **motion-presence** metric catches the opposite defect
(frames too *similar* β effectively a still image). These fold into a 0β100 `ScoreFrames`:
```
start at 100
β (35 + 10Β·|FoundβExpected|) frame-count accuracy (largest penalty)
β 13Β·errors β 3Β·warnings
β 12 (motion < 0.01 with 2+ frames β effectively static)
β 10 (dHash identity < 0.55 β structural collapse)
β excellent (β₯85) / good (β₯70) / fair (β₯50) / poor
```
### PerfectPixel vs. an ordinary AI tool
| Aspect | Ordinary AI tool | PerfectPixel |
|--------|------------------|--------------|
| Background removal | Fixed RGB chroma threshold | YCbCr chrominance matting + flood fill + morphology + self-diagnostic fallback |
| Frame segmentation | Equal split / connected-component | Projection profile + DP global-optimum cut |
| Anchor stability | Left to chance | Alpha-weighted centroid + manifest foot pivot |
| Identity consistency | Left to chance | Color histogram + dHash structural (2-axis) + regen loop |
| Quality measurement | Human eyeballing | 0β100 multi-axis score + headless regression tracking |
| Compression robustness | Fragile to JPEG noise | Luma-free chrominance space, inherently robust to 4:2:0 subsampling |
---
## Supported AI providers
Select a provider in Settings and enter the API key; it is validated, then saved to
`~/Library/Application Support/perfectpixel/config.json` (mode `0600`). Keys can also be injected
via environment variables or a `.env` file.
| Provider | Default model | API key env var |
|----------|---------------|-----------------|
| **Gemini** (default) | `gemini-3-pro-image` (Nano Banana Pro) | `GEMINI_API_KEY` / `GOOGLE_API_KEY` |
| **OpenRouter** | `google/gemini-3-pro-image-preview` | `OPENROUTER_API_KEY` |
| **fal.ai** | `fal-ai/nano-banana-pro` | `FAL_KEY` / `FAL_API_KEY` |
| **BytePlus** | `seedream-4-0-250828` (Seedream 4.0) | `BYTEPLUS_API_KEY` / `ARK_API_KEY` |
> Config-file keys take precedence over environment variables. The first provider with a key is
> auto-activated.
## Install & run
**Requirements**
- [Go](https://go.dev/dl/) 1.25+
- [Node.js](https://nodejs.org/) 18+ (frontend build)
- [Wails CLI v2](https://wails.io/docs/gettingstarted/installation):
`go install github.com/wailsapp/wails/v2/cmd/wails@latest`
- Check platform dependencies with `wails doctor`.
**Development (HMR)**
```bash
git clone https://github.com/gykim80/perfectpixel-studio.git
cd perfectpixel-studio
./dev.sh # or: wails dev
```
`./dev.sh` locates the wails CLI and runs `frontend/npm install` once on first launch.
**Production build**
```bash
wails build # produces the distributable app under build/bin/
```
**API keys**
Enter keys in Settings after launch, or drop a `.env` in the project root:
```bash
cp .env.example .env # fill in only the provider keys you use
```
## Usage
1. **Create a character** β enter a description, pick a style, generate the base character.
2. **Add motions** β choose from 100+ presets (walk, attack, β¦) or type your own to generate an
animation strip.
3. **8-direction set** *(optional)* β generate a direction set from the grid; the front strip
serves as the motion reference for other directions.
4. **Review & regenerate** β check via frame preview and animation playback, add feedback, regen.
5. **Export** β pick a folder and save engine-ready output in one pass.
## Export format
A directory named after the character is created under the chosen folder:
```