| name | pixel-art-studio |
| description | Create production-quality pixel art and animations programmatically. Use when the user asks to "create pixel art", "draw a sprite", "make pixel animation", "generate sprite sheet", "convert image to pixel art", "pixelate this image", "make a pixel character", "пиксель арт", "пиксельная графика", "спрайт", "像素画", "像素艺术", "도트 그래픽", "픽셀 아트", "8-bit/16-bit/hi-bit style", "retro game art", "Aseprite-like output", "indie game sprite". Covers single-frame sprites, frame-by-frame animations, walk cycles, idle/attack/death animations, sprite sheets, GIF/APNG export, image-to-pixel-art preprocessing (downsample + quantize + dither), 30+ bundled palettes (NES, GameBoy, PICO-8, Endesga 32/64, DawnBringer 16/32, Sweetie 16, Resurrect 64, Korean 오방색/단청, Chinese 故宫/青花/五行, Russian Stoneshard-inspired), 5 dithering algorithms (Bayer 2/4/8, Floyd-Steinberg, Atkinson, Ordered, Blue Noise), automated quality scoring (orphan pixels, doublies, banding, pillow-shading, AI-slop detection), and Generator-Evaluator review via the pixel-art-reviewer agent.
|
| version | 1.0.0 |
Pixel Art Studio
Programmatic pixel art creation with palette discipline, dithering, animation, and automated quality control. Designed for production-quality output, not "look-pixelated filter on a photo."
When to use this skill
If user provides only a vague description ("a cat sprite"), pick the standard sprite workflow with 32×32 + Endesga 32 palette as the safe default, then offer to iterate.
Prerequisites
pip install Pillow numpy
pip install scikit-image scipy
Pillow is mandatory; numpy is mandatory; the rest are optional (the scripts will degrade gracefully).
Core principle: design discipline > pixel quantity
A 16×16 sprite with deliberate cluster choices reads better than a 64×64 with random pixel noise. Always start from the smallest grid that conveys the subject, expand only when needed for detail.
The four pillars of quality (encoded in scripts/quality_check.py):
- Per-pixel hygiene — no orphan single pixels, no parallel doublies, no banded ramps
- Cluster coherence — pixel groups read as recognizable shapes, not noise
- Palette discipline — limited (≤32 typically), with hue rotation across luminance ramp
- Silhouette readability — render-as-solid-shape and verify subject is recognizable
When in doubt, run quality_check.py after generation and fix issues until score ≥ 80/100.
Workflow 1: Single-frame sprite
Step 1 — Pick canvas size
| Subject complexity | Canvas | Examples |
|---|
| Icon / glyph | 8×8 | heart, key, arrow, smiley |
| Simple sprite | 16×16 | NES character, item, tile |
| Standard sprite ⭐ | 32×32 | indie character, animal, prop |
| Detailed character | 48×48 - 64×64 | hi-bit hero, boss, building |
| Mobile RPG humanoid (CN/KR) | 48×72 | 8-direction walking character |
| Hero / portrait | 96×96 - 128×128 | promotional art, big boss |
When user is vague: 32×32.
Step 2 — Pick palette
Three modes:
- Bundled palette (recommended): Use
scripts/palette.py --list to enumerate. Default for vague subjects: Endesga 32.
- Style-anchored palette: subject-specific recommendations in
references/02-palette-theory.md
- Custom palette: 4-16 hex colors curated by hand. Validate with palette ramp checker.
Common style → palette mapping:
| User intent | Palette |
|---|
| Generic, modern indie | endesga-32 or endesga-64 |
| 8-bit retro / Famicom feel | nes or pico-8 |
| Mono / GameBoy DMG | gameboy-dmg |
| Soft pastel / cute | sweetie-16 |
| Atmospheric / cinematic | apollo or slso8 |
| Industrial / cool | steam-lords |
| Chinese xianxia / palace | gugong-red-wall or qinghua |
| Korean traditional | obangsaek (5-color) |
| Dark fantasy (Stoneshard-style) | stoneshard-inspired |
Step 3 — Design layer-by-layer
Always think in this order, not free-form:
- Silhouette (darkest color, just outline) — does shape read at intended size?
- Base fill — primary 1-2 colors covering largest areas
- Cell shading — pick 3-4 discrete shades, place them per a single light direction (default: top-left)
- Hue shift — shadows shift cooler+desaturated (toward blue-violet); highlights warmer+saturated (toward yellow-orange). Hue rotation across ramp ≥ 30°
- Selective AA — only on staircase patterns longer than 1×1, using intermediate-color halftone strip
- Details — eyes, patterns, small features. Each pixel must belong to a cluster ≥ 2-3 pixels (no orphans)
NEVER do pillow shading: dark border + light center regardless of light source. Auto-detected as anti-pattern in quality_check.py.
Step 4 — Generate JSON
Use the Sparse Coordinate JSON format. Minimal example:
{
"width": 16,
"height": 16,
"background": "transparent",
"pixel_size": 16,
"palette_ref": "endesga-32",
"pixels": [
{"x": 7, "y": 4, "color": "#a8ca58"},
...
]
}
For animation, use the multi-frame extended schema (frames array — see Workflow 2).
Step 5 — Render PNG
python ${CLAUDE_PLUGIN_ROOT}/skills/creative/pixel-art-studio/scripts/render.py \
sprite.json -o sprite.png
Step 6 — Quality check
python ${CLAUDE_PLUGIN_ROOT}/skills/creative/pixel-art-studio/scripts/quality_check.py sprite.png
Output is JSON. Score ≥ 80 = ship. Score 60-80 = fix issues listed. Score < 60 = redesign.
Step 7 — Display + iterate
Read the PNG with the Read tool to show user. Offer fixes for any quality issues flagged.
Workflow 2: Animation
Frame counts (production-validated)
Pick from this table — do NOT improvise frame counts.
| Animation | Min | Standard ⭐ | Premium | FPS |
|---|
| Idle (breathing) | 2 | 4 | 6-8 | 6 |
| Walk | 4 (Celeste) | 6 (Shovel Knight) | 8-12 | 8 |
| Run | 6 | 8 | 10 | 10 |
| Attack | 3 | 5 | 6-8 | 10-12 |
| Death | 4 | 6-8 | 10+ | 8-10 |
| Hit reaction | 1 | 2-3 | — | 10 |
Cultural variations:
- Western indie: 8-12 fps standard
- Chinese mobile RPG: 4-frame walk @ 200ms (5 fps) is documented standard
- Korean dot mobile: 6 frames @ 8-12 fps; chibi 4-frame walk
- Russian indie: typically follows Western, sometimes "Punch Club rule" (draw 1×, render 2-3×)
Animation principles (Disney → pixel art)
Only 3 of 12 translate without modification:
- Timing — wind-up frames longer, action shortest, recovery eases
- Anticipation — crouch before jump, wind-up before attack
- Squash & stretch — even 1 pixel of compression on landing is effective
For walk cycles: 4-frame minimum is [contact, recoil, passing, high-point] and back. Don't add frames just to "smooth" — add anticipation/follow-through instead.
For attacks: [anticipation (slow), strike (1 frame, fast), recovery (eases back)]. Slowing down anticipation + speeding up action ≫ adding frames.
Smear frames (Korean Skul-style)
For fast attacks/throws, insert 1-2 stretched intermediate frames (smear). Heavy in Skul, less in Sanabi. See references/04-animation.md.
JSON schema for animations
{
"width": 32,
"height": 32,
"background": "transparent",
"palette_ref": "endesga-32",
"frames": [
{"id": 0, "duration_ms": 120, "pixels": [...]},
{"id": 1, "duration_ms": 120, "pixels": [...]},
{"id": 2, "duration_ms": 120, "pixels": [...]},
{"id": 3, "duration_ms": 120, "pixels": [...]}
],
"tags": [
{"name": "walk", "from": 0, "to": 3, "direction": "forward"}
]
}
direction ∈ forward | reverse | pingpong (Aseprite convention).
Render animation
python scripts/animate.py walk.json --format gif -o walk.gif
python scripts/animate.py walk.json --format apng -o walk.apng
python scripts/animate.py walk.json --format spritesheet -o walk_sheet.png --layout horizontal
Animation quality check
python scripts/quality_check.py --animation walk.json
Checks: palette stability across frames (no off-palette colors), pixel rate consistency (sub-pixel-AA matches), total mass conservation (8-pixel torso stays 8 pixels across frames), per-frame quality scores.
Workflow 3: Image-to-pixel-art preprocessing
When user provides a real photo / hi-res illustration and asks for pixel art version.
Pipeline (in scripts/preprocess.py):
- Downsample to target grid via
Image.NEAREST (NOT bicubic — that introduces fractional pixels = AI-slop signal)
- Extract palette via k-means or median cut (configurable colors: 8/16/32/64)
- Quantize to extracted or chosen palette
- Optional dithering to soften gradients (Floyd-Steinberg/Atkinson for photos; Bayer for halftone style)
- Manual cleanup pass — agent reviews output and lists orphans, doublies for edits
Usage:
python scripts/preprocess.py photo.jpg --target-size 64x64 --palette aap-64 --dither floyd-steinberg -o pixel.png
Important: AI-generated art (Stable Diffusion, Midjourney) is NOT pixel art even if it looks pixelated. It usually has fractional pixel widths and noise instead of dithering. Always run preprocess pipeline + quality check on AI output before treating it as pixel art.
Workflow 4: Sprite sheet
For game engines (Unity, Godot, Unreal) wanting a single PNG with all frames.
python scripts/animate.py character.json --format spritesheet \
--layout grid --rows 4 --cols 8 -o character_sheet.png
Conventions:
- 1-2 px transparent padding between cells (configurable)
- Power-of-2 final dimensions when possible (engine-friendly)
- Optional
.json metadata file alongside (Aseprite-compatible format)
Workflow 5: Quality review
When user asks "review this pixel art" or "is this good":
python scripts/quality_check.py existing_sprite.png --verbose
Returns JSON with:
- per-pixel hygiene: orphan count, doublies count, banding bands
- palette analysis: unique color count, ramp hue rotation, banding score
- silhouette readability: solid-fill recognizability (heuristic)
- anti-AI-slop signals: blurry edges count, fractional widths, gradient-over-flat detection
- overall score: 0-100
For Generator-Evaluator review (independent agent), invoke the pixel-art-reviewer agent (see agents/pixel-art-reviewer.md). It runs quality_check.py + reads the image with fresh context and writes a verdict.
Palette management
List bundled palettes
python scripts/palette.py --list
Returns 30+ palettes grouped by category:
- Hardware-authentic: nes, gameboy-dmg, pico-8, ega
- Lospec community: db16, db32, aap-64, endesga-32, endesga-64, sweetie-16, resurrect-64, apollo, steam-lords, slso8
- Cultural: obangsaek (KR), gugong-red-wall (CN), qinghua (CN), wuxing (CN), stoneshard-inspired (RU dark fantasy)
Extract palette from image
python scripts/palette.py --extract photo.jpg --colors 16 --method median-cut
Methods: kmeans (slow, high quality), median-cut (default, balanced), octree (fast).
Generate hue-shifted ramp
python scripts/palette.py --ramp "#5b3a3a" --steps 5 --hue-shift 40
Generates a 5-step ramp from dark→bright with proper hue rotation (Endesga rule). Use this when you need a fresh material color (skin tone, metal, leather) without a full palette.
Cultural style guides (when relevant)
The skill respects multiple cultural canons. Match the user's stated style:
| User mentions | Read this |
|---|
| 武侠 / 仙侠 / wuxia / xianxia | references/07-cultural-styles.md#chinese-xianxia |
| 故宫 / palace / red wall | Use gugong-red-wall palette |
| 道袍 / robe animation | Add sleeve secondary animation channel |
| 도트 / dot graphic / Korean | references/07-cultural-styles.md#korean-dot |
| 산나비 / Sanabi / Skul | "Hand-drawn dot" quality discriminator |
| MapleStory / 메이플스토리풍 | High-detail 64-128px portraits, costume-friendly |
| Punch Club / Stoneshard | Mandatory contour darker than darkest pixel |
| Loop Hero | Multi-tier sprite consistency mode |
| Celeste | 320×180 base resolution, 4-frame animations |
| Hyper Light Drifter | 480×270, "pixel impressionism", no outlines |
Mandatory rules (will be quality-checked)
- No orphan pixels unless intentional texture (sparkle, stippling). Default cap: 5% of total pixels.
- No doublies — parallel double-thickness lines from accidental brush. Hard rule.
- No pillow shading — dark-border-light-center regardless of light source. Hard rule.
- Palette ≤ stated cap — if palette is
endesga-32, output must use ≤32 unique colors.
- Hue rotation ≥ 30° across any luminance ramp ≥ 4 colors. Soft warning, not hard error.
- Selective AA — never on 45° lines, never on perfectly straight lines.
- Outline (when present) darker than darkest object pixel (Punch Club / Stoneshard rule).
Gotchas
- Pillow
Image.quantize defaults to median cut. For better quality on photos use method=Image.Quantize.LIBIMAGEQUANT if pyImageQuant is installed; otherwise MEDIANCUT is fine.
- GIF format limits: ≤256 colors, no semi-transparency. For semi-transparent animations use APNG (better) or WebP (modern but inconsistent compatibility).
- Sub-pixel "anti-aliasing" trick: animate the AA values between frames to suggest motion smaller than a pixel. Looks pro but doubles the AA pixel budget.
- CN walking timing: Chinese mobile games sometimes use 5fps (200ms/frame) walks — looks slow to Western eye but is documented standard. Don't "fix" it.
- 45° lines never get AA — common newbie mistake. AA on staircase patterns longer than 1×1 only.
- Indexed PNG vs RGBA PNG: indexed is smaller, faster, game-engine-friendly; RGBA preserves alpha. Default to RGBA in render.py; offer
--indexed flag when palette is fixed.
- Aseprite source files (
.aseprite) preserve tags, layers, palette in original form. If user has Aseprite installed, offer to export .aseprite alongside PNG.
- Background-color confusion:
"background": "transparent" ≠ "#FFFFFF". Default to transparent for game sprites; use solid color only for non-cutout art.
- AI pixel art is not pixel art: SD/Midjourney outputs need
preprocess.py pipeline. Don't trust their pixel grid alignment.
Troubleshooting
| Symptom | Cause | Fix |
|---|
| "Pillow not installed" | Missing dependency | pip install Pillow |
| Garbled output | Pixel coordinates outside grid | Check 0 ≤ x < width, 0 ≤ y < height |
| Colors look wrong | Hex shorthand or named color mismatch | Use full #RRGGBB hex |
| Image looks blurry | Pillow used BILINEAR resize | Use Image.NEAREST for pixel art |
| Quality score < 60 | Multiple quality issues | Read full JSON output; common fixes: reduce palette to ≤32 colors, remove orphan pixels, redo with single light source |
| GIF has color bands | Limited 256-palette quantization | Switch to APNG or use --no-quantize |
| Animation jitters | Inconsistent pixel positions across frames | Run quality_check.py --animation to identify frame with mass deviation |
| Pillow shading detected | Anti-pattern shading | Re-shade with explicit light source (top-left), darkest pixels in shadow side only |
| Doublies detected | Two parallel 1-px lines | Merge into single 2-px line OR remove the redundant line |
Reference index
| Topic | File |
|---|
| Drawing techniques (cluster, AA, jaggies, doublies, outlining) | references/01-techniques.md |
| Palette theory, dithering, banding | references/02-palette-theory.md |
| Shading, light, materials | references/03-shading-materials.md |
| Animation principles, frame counts, smear, sub-pixel | references/04-animation.md |
| Quality rubric + anti-AI-slop checklist | references/05-quality-rubric.md |
| Tools and libraries (Aseprite, Pillow, pyxelate, etc.) | references/06-tools-and-libraries.md |
| Cultural styles (CN/KR/RU/Western) | references/07-cultural-styles.md |
| Extended JSON schema spec | references/08-json-schema.md |
Companion agent
For independent quality review (Generator-Evaluator pattern): invoke pixel-art-reviewer agent. It runs the quality scripts + reads the rendered image with fresh context, returning PASS/HOLD/REJECT verdict — see agents/pixel-art-reviewer.md for invocation pattern.