Introduction
Pixelsrc is a GenAI-native pixel art format. It's text-based JSONL that you (or an AI) generate, then the pxl command converts to PNG, GIF, spritesheets, and more.
Why Pixelsrc?
Traditional pixel art tools produce binary files that are difficult for AI to generate and humans to version control. Pixelsrc takes a different approach:
- Pure text - No binary data. Every sprite is human-readable JSONL
- Semantic tokens - Use names like
{skin}and{hair}instead of hex coordinates - Streaming-friendly - JSONL format means each line is self-contained
- Lenient by default - Small mistakes get corrected automatically
- AI-first design - Every feature is designed for reliable AI generation
Quick Example
Here's a simple coin sprite in Pixelsrc format:
{"type": "palette", "name": "coin", "colors": {"_": "#0000", "gold": "#FFD700", "shine": "#FFFACD"}}
{"type": "sprite", "name": "coin", "size": [4, 4], "palette": "coin", "regions": {
"gold": {"union": [{"rect": [1, 0, 2, 1]}, {"rect": [0, 1, 4, 2]}, {"rect": [1, 3, 2, 1]}], "z": 0},
"shine": {"points": [[1, 1]], "z": 1}
}}
Render it with:
pxl render coin.pxl -o coin.png
What You Can Do
With Pixelsrc, you can:
- Create sprites using semantic color tokens
- Build animations from frame sequences
- Compose scenes by layering sprites
- Generate assets with AI assistance
- Export to PNG, GIF, spritesheets, and game engine formats
- Validate your files for common mistakes
- Format for consistent, readable code
Who Is This For?
Pixelsrc is designed for:
- AI systems (Claude, GPT, etc.) generating game assets
- Indie developers wanting quick prototyping
- Pixel artists wanting text-based version control
- Roguelike developers needing procedural assets
Getting Started
Head to Installation to set up Pixelsrc, then try the Quick Start guide.
If you prefer learning by example, check out the Persona Guides for workflow examples tailored to your use case.
Installation
Pixelsrc provides the pxl command-line tool for working with pixel art files.
From Source (Rust)
If you have Rust installed, you can build from source:
# Clone the repository
git clone https://github.com/scbrown/pixelsrc.git
cd pixelsrc
# Build and install
cargo install --path .
# Verify installation
pxl --version
From Releases
Pre-built binaries are available on the GitHub Releases page for:
- Linux (x86_64)
- macOS (x86_64, Apple Silicon)
- Windows (x86_64)
Download the appropriate binary for your platform and add it to your PATH.
Verify Installation
After installation, verify that pxl is available:
pxl --version
You should see output like:
pxl 0.1.0
Next Steps
Once installed, head to the Quick Start guide to create your first sprite.
Quick Start
Let's create your first sprite in under 5 minutes.
Step 1: Create a Pixelsrc File
Create a file called star.pxl with this content:
// star.pxl - A simple 3x3 star
{
type: "palette",
name: "star",
colors: {
_: "transparent",
y: "#FFD700",
},
}
{
type: "sprite",
name: "star",
size: [3, 3],
palette: "star",
regions: {
y: {
union: [
{ points: [[1, 0]] }, // Top
{ rect: [0, 1, 3, 1] }, // Middle row
{ points: [[1, 2]] }, // Bottom
],
},
},
}
This defines:
- A palette named "star" with two colors: transparent (
_) and yellow (y) - A sprite named "star" that uses regions to draw a 3x3 star shape
Step 2: Render to PNG
Run the render command:
pxl render star.pxl -o star.png
You now have star.png - a 3x3 pixel star!
Step 3: Scale It Up
For a larger preview, use the --scale flag:
pxl render star.pxl -o star_8x.png --scale 8
This creates an 8x scaled version (24x24 pixels).
Step 4: Preview in Terminal
For quick iteration, use the show command to preview directly in your terminal:
pxl show star.pxl
This displays the sprite using ANSI true-color in your terminal.
Step 5: Validate Your File
Check for common mistakes:
pxl validate star.pxl
If everything is correct, you'll see no output (success). If there are issues, you'll get helpful warnings.
What's Next?
- Learn about Core Concepts like palettes, sprites, and regions
- Create Your First Animation
- Explore the Format Specification for all available features
Tips
- Use semantic token names like
skin,outline,shadowinstead of generic names - The
_token is the conventional name for transparent pixels - Use shapes (
rect,circle,ellipse) for efficiency over individual points - Higher
zvalues draw on top of lower ones
Core Concepts
Understanding these core concepts will help you work effectively with Pixelsrc.
Objects and Types
Pixelsrc files contain objects in JSON5 format. Each object has a type field:
| Type | Purpose | Required Fields |
|---|---|---|
palette | Define named colors | name, colors |
sprite | Structured regions | name, size, palette, regions |
animation | Frame sequence | name, frames |
composition | Layer sprites | name, size, layers |
variant | Modify existing sprite | name, base, changes |
Palettes
A palette defines colors with semantic names:
{
type: "palette",
name: "hero",
colors: {
_: "transparent",
skin: "#FFCC99",
hair: "#8B4513",
outline: "#000000",
},
}
Key points:
- Token names are simple identifiers:
skin,hair,outline - Colors use hex format:
#RGB,#RGBA,#RRGGBB, or#RRGGBBAA _is the conventional token for transparency- Palettes must be defined before sprites that reference them
Tokens
Tokens are identifiers that represent colors:
_ → transparent (convention)
skin → semantic name for skin color
outline → semantic name for outline
dark_hair → underscores OK for multi-word names
Benefits of semantic tokens:
- Readable: Region definitions reference meaningful names
- Maintainable: Change a color in one place (the palette), update everywhere
- AI-friendly: LLMs can reason about
shadowmore reliably than hex values
Sprites
A sprite is defined using structured regions:
{
type: "sprite",
name: "cross",
size: [3, 3],
palette: "colors",
regions: {
r: {
union: [
{ points: [[1, 0], [1, 2]] }, // Vertical
{ rect: [0, 1, 3, 1] }, // Horizontal
],
},
},
}
Key points:
size: Required[width, height]regions: Map of token names to shape definitionsz: Controls draw order (higher draws on top)palette: Name of a defined palette or inline colors object
Shape Primitives
| Shape | Syntax | Description |
|---|---|---|
rect | [x, y, w, h] | Filled rectangle |
stroke | [x, y, w, h] | Rectangle outline |
points | [[x, y], ...] | Individual pixels |
line | [[x1, y1], ...] | Connected line |
circle | [cx, cy, r] | Filled circle |
ellipse | [cx, cy, rx, ry] | Filled ellipse |
polygon | [[x, y], ...] | Filled polygon |
Compound Shapes
Combine shapes using union, subtract, or intersect:
regions: {
body: {
union: [
{ rect: [2, 0, 4, 2] },
{ rect: [0, 2, 8, 4] },
],
z: 0,
},
}
Animations
An animation sequences multiple sprites:
{
type: "animation",
name: "walk",
frames: ["walk_1", "walk_2", "walk_3"],
duration: 100,
}
Key points:
frames: Array of sprite names in orderduration: Milliseconds per frame (default: 100)loop: Whether to loop (default: true)
Compositions
A composition layers multiple sprites:
{
type: "composition",
name: "scene",
size: [16, 16],
layers: [
{ sprite: "background", x: 0, y: 0 },
{ sprite: "hero", x: 4, y: 4 },
],
}
Layers are rendered bottom-to-top (first layer is background).
File Format
Pixelsrc uses JSON5 format:
- Supports comments (
// comment) - Trailing commas allowed
- Unquoted keys
- Multiple objects separated by whitespace
- Files typically use
.pxlextension
This format is:
- Human-readable with comments
- AI-friendly for generation
- Easy to edit manually
Lenient Mode
By default, Pixelsrc is lenient:
- Missing tokens render as magenta (visible but not breaking)
- Small mistakes don't halt rendering
- Provides helpful warnings
Use --strict mode for validation in CI/CD pipelines.
Your First Animation
Let's create a simple blinking star animation.
Step 1: Define the Palette
First, create a file called blink.pxl and add a palette:
{
type: "palette",
name: "star",
colors: {
_: "transparent",
y: "#FFD700", // Yellow (gold)
w: "#FFFFFF", // White (bright)
},
}
Step 2: Create Animation Frames
Add two sprite frames - one normal, one bright:
{
type: "sprite",
name: "star_normal",
size: [3, 3],
palette: "star",
regions: {
y: {
union: [
{ points: [[1, 0], [1, 2]] },
{ rect: [0, 1, 3, 1] },
],
},
},
}
{
type: "sprite",
name: "star_bright",
size: [3, 3],
palette: "star",
regions: {
w: {
union: [
{ points: [[1, 0], [1, 2]] },
{ rect: [0, 1, 3, 1] },
],
},
},
}
Step 3: Define the Animation
Add the animation object that sequences the frames:
{
type: "animation",
name: "blink",
frames: ["star_normal", "star_normal", "star_normal", "star_bright"],
duration: 150,
}
This creates a blink effect:
- Three frames of normal yellow (450ms total)
- One frame of bright white (150ms)
- Then loops
Complete File
Your blink.pxl should look like:
// blink.pxl - A blinking star animation
{
type: "palette",
name: "star",
colors: {
_: "transparent",
y: "#FFD700",
w: "#FFFFFF",
},
}
{
type: "sprite",
name: "star_normal",
size: [3, 3],
palette: "star",
regions: {
y: {
union: [
{ points: [[1, 0], [1, 2]] },
{ rect: [0, 1, 3, 1] },
],
},
},
}
{
type: "sprite",
name: "star_bright",
size: [3, 3],
palette: "star",
regions: {
w: {
union: [
{ points: [[1, 0], [1, 2]] },
{ rect: [0, 1, 3, 1] },
],
},
},
}
{
type: "animation",
name: "blink",
frames: ["star_normal", "star_normal", "star_normal", "star_bright"],
duration: 150,
}
Step 4: Render as GIF
Export the animation as a GIF:
pxl render blink.pxl --gif --animation blink -o blink.gif --scale 8
This creates blink.gif - an 8x scaled animated GIF of your blinking star!
Step 5: Export as Spritesheet
For game engines, export as a horizontal spritesheet:
pxl render blink.pxl --spritesheet --animation blink -o blink_sheet.png --scale 4
This creates a horizontal strip with all frames side by side.
Animation Options
The animation object supports these fields:
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Animation identifier |
frames | array | required | Sprite names in sequence |
duration | number | 100 | Milliseconds per frame |
loop | boolean | true | Whether to loop |
Tips
- Reuse frames - Repeat sprite names in the
framesarray for timing control - Frame duration - Lower values = faster animation
- Scale - Always scale up for previews; pixel art is small!
- Validate first - Run
pxl validatebefore rendering to catch issues
Next Steps
- Learn about Variants to create frame variations efficiently
- Explore Compositions to layer animated sprites
- Check the GIF Export guide for advanced options
The Sketcher
You want to quickly visualize ideas. Pixel-perfect polish can come later—right now you need to see if your concept works.
Your Workflow
- Create a
.pxlfile with minimal setup - Preview in terminal with
pxl show - Iterate rapidly until the concept feels right
- Export when ready
Quick Setup
Create sketch.pxl:
{
type: "palette",
name: "sketch",
colors: {
_: "transparent",
x: "#000000",
o: "#FFFFFF",
},
}
That's it. One palette, two colors. Now sketch:
{
type: "sprite",
name: "idea",
size: [5, 5],
palette: "sketch",
regions: {
x: {
stroke: [0, 0, 5, 5],
z: 0,
},
o: {
union: [
{ rect: [1, 1, 3, 1] },
{ rect: [1, 3, 3, 1] },
{ points: [[1, 2], [3, 2]] },
],
z: 1,
},
},
}
Terminal Preview
Skip the PNG export—preview directly:
pxl show sketch.pxl
Your sprite appears in the terminal using ANSI colors. Fast feedback, no file clutter.
Tips for Rapid Iteration
Use Short Token Names
When sketching, single-character tokens are faster to type:
{
_: "transparent",
x: "#000",
o: "#FFF",
r: "#F00",
b: "#00F",
}
Keep Sprites Small
Start with 8x8 or 16x16. You can always upscale later:
pxl render sketch.pxl -o preview.png --scale 4
Multiple Ideas in One File
Keep multiple sprites in a single file:
{ type: "sprite", name: "idea_v1", size: [8, 8], palette: "sketch", regions: { ... } }
{ type: "sprite", name: "idea_v2", size: [8, 8], palette: "sketch", regions: { ... } }
{ type: "sprite", name: "idea_v3", size: [8, 8], palette: "sketch", regions: { ... } }
Show a specific one:
pxl show sketch.pxl --name idea_v2
Don't Worry About Mistakes
Pixelsrc is lenient by default. Missing tokens render as magenta, invalid shapes get skipped. The goal is momentum—fix issues later.
When You're Ready for More
Once your sketch is solid:
- Add semantic token names (see The Sprite Artist)
- Create animation frames (see The Animator)
- Export to PNG:
pxl render sketch.pxl -o sketch.png
Example: Character Silhouette
Start with a simple silhouette to nail the proportions:
{
type: "palette",
name: "silhouette",
colors: {
_: "transparent",
s: "#000000",
},
}
{
type: "sprite",
name: "hero",
size: [7, 7],
palette: "silhouette",
regions: {
s: {
union: [
// Head
{ rect: [2, 0, 3, 2] },
// Body
{ rect: [1, 2, 5, 2] },
// Arms
{ points: [[0, 4], [6, 4]] },
// Legs
{ rect: [2, 4, 1, 3] },
{ rect: [4, 4, 1, 3] },
],
z: 0,
},
},
}
Try changing s to #4169E1 (blue) to see color, or adjust the regions to make the character taller or add more detail.
Once the shape feels right, you can add detail colors, animate it, or hand it off to your sprite artist persona.
The Sprite Artist
You create polished, production-ready sprites. Every pixel matters. You want semantic organization, reusable palettes, and clean composition.
Your Workflow
- Design a thoughtful palette with semantic tokens
- Build sprites using meaningful color names
- Create variants for different states or colors
- Compose complex scenes from layered sprites
- Export at the right resolution
Semantic Palettes
The foundation of good sprite art is a well-designed palette:
{
type: "palette",
name: "character",
colors: {
_: "transparent",
outline: "#1A1A2E",
skin: "#FFD5C8",
skin_shadow: "#E8B4A0",
hair: "#4A2C2A",
hair_highlight: "#6B4444",
eye: "#2D4059",
cloth: "#16537E",
cloth_shadow: "#0D3A5C",
},
}
Why semantic tokens matter:
- Readability:
skin_shadowis clearer than#E8B4A0 - Maintainability: Change a color once, update everywhere
- Reusability: Share palettes across multiple sprites
- AI-friendly: LLMs can reason about
outlinereliably
Building Sprites
With your palette ready, create sprites using structured regions:
{
type: "sprite",
name: "hero_idle",
size: [7, 8],
palette: "character",
regions: {
// Hair on top
hair: { rect: [2, 0, 3, 2], z: 0 },
hair_highlight: { points: [[2, 1]], z: 1 },
// Face
outline: {
union: [
{ points: [[1, 2], [5, 2]] },
{ points: [[1, 7], [4, 7]] },
],
z: 0,
},
skin: { rect: [2, 2, 3, 3], z: 1 },
skin_shadow: { points: [[3, 4]], z: 2 },
eye: { points: [[2, 3], [4, 3]], z: 2 },
// Body
cloth: { rect: [2, 5, 3, 2], z: 0 },
cloth_shadow: { points: [[2, 6], [4, 6]], z: 1 },
},
}
Variants
Create variations without duplicating entire sprites:
{
type: "variant",
name: "hero_damaged",
base: "hero_idle",
palette: {
skin: "#FFB8A8",
skin_shadow: "#D89080",
},
}
The variant inherits everything from hero_idle but overrides the skin colors to show damage.
Common variant use cases:
- Damage/heal states
- Different color schemes (red team vs blue team)
- Seasonal variations
- Lighting conditions (day/night)
Compositions
Layer sprites to create complex scenes:
{
type: "composition",
name: "scene",
size: [64, 64],
layers: [
{ sprite: "background_grass", x: 0, y: 0 },
{ sprite: "tree", x: 8, y: 24 },
{ sprite: "hero_idle", x: 28, y: 48 },
{ sprite: "ui_healthbar", x: 2, y: 2 },
],
}
Layers render bottom-to-top, so the first layer is the background.
Organizing Your Files
For larger projects, split files by purpose:
assets/
├── palettes/
│ ├── characters.pxl
│ └── environment.pxl
├── sprites/
│ ├── hero.pxl
│ ├── enemies.pxl
│ └── items.pxl
└── scenes/
└── level1.pxl
Use includes to reference palettes:
{ type: "include", path: "../palettes/characters.pxl" }
{
type: "sprite",
name: "hero",
size: [16, 16],
palette: "character",
regions: { ... },
}
Export Tips
High-Quality PNG
pxl render hero.pxl -o hero.png --scale 1
Use --scale 1 for pixel-perfect output, then scale in your game engine.
Preview Before Export
pxl show hero.pxl --name hero_idle
Validation
Before committing:
pxl validate hero.pxl --strict
Strict mode catches warnings that lenient mode ignores.
Example: Complete Character
{
type: "palette",
name: "knight",
colors: {
_: "transparent",
outline: "#1A1A2E",
armor: "#7F8C8D",
armor_light: "#95A5A6",
armor_dark: "#5D6D7E",
visor: "#2C3E50",
plume: "#C0392B",
plume_dark: "#922B21",
},
}
{
type: "sprite",
name: "knight_idle",
size: [8, 8],
palette: "knight",
regions: {
// Plume on helmet
plume: {
union: [
{ rect: [2, 0, 3, 1] },
{ rect: [1, 1, 5, 1] },
],
z: 0,
},
plume_dark: { points: [[1, 1]], z: 1 },
// Helmet
outline: {
union: [
{ points: [[1, 2], [5, 2]] },
{ points: [[0, 3], [6, 3]] },
{ points: [[0, 4], [6, 4]] },
{ points: [[1, 5], [5, 5]] },
{ points: [[1, 7], [4, 7]] },
],
z: 0,
},
armor_light: { points: [[1, 3], [5, 3]], z: 1 },
armor: {
union: [
{ rect: [2, 2, 3, 1] },
{ rect: [2, 3, 3, 1] },
{ rect: [2, 5, 3, 1] },
{ rect: [1, 6, 5, 1] },
],
z: 1,
},
armor_dark: {
union: [
{ rect: [2, 4, 3, 1] },
{ points: [[2, 6], [4, 6]] },
],
z: 2,
},
visor: { points: [[3, 3]], z: 2 },
},
}
{
type: "variant",
name: "knight_gold",
base: "knight_idle",
palette: {
armor: "#F39C12",
armor_light: "#F7DC6F",
armor_dark: "#D4AC0D",
},
}
Try changing armor to #FFD700 (gold) and plume to #4169E1 (blue) for a royal knight.
This gives you a silver knight, a gold variant, and a reusable palette for additional knight sprites.
The Animator
You bring sprites to life with motion. Walk cycles, attack animations, idle bobbing—you think in frames and timing.
Your Workflow
- Create keyframe sprites
- Define animation sequences
- Preview and adjust timing
- Export to GIF or spritesheet
Animation Basics
An animation references a sequence of sprites:
{
type: "sprite",
name: "coin_1",
size: [6, 6],
palette: "coin",
regions: { body: { ellipse: [3, 3, 3, 3], z: 0 } },
}
{
type: "sprite",
name: "coin_2",
size: [6, 6],
palette: "coin",
regions: { body: { ellipse: [3, 3, 2, 3], z: 0 } },
}
{
type: "animation",
name: "coin_spin",
frames: ["coin_1", "coin_2"],
duration: 100,
}
Key properties:
- frames: Array of sprite names in play order
- duration: Milliseconds per frame (default: 100)
- loop: Whether to loop (default: true)
Example: Coin Spin
A classic 4-frame coin rotation:
{
type: "palette",
name: "coin",
colors: {
_: "transparent",
gold: "#FFD700",
gold_light: "#FFEC8B",
gold_dark: "#DAA520",
shine: "#FFFFFF",
},
}
// Frame 1: Full face
{
type: "sprite",
name: "coin_1",
size: [6, 6],
palette: "coin",
regions: {
gold: {
union: [
{ rect: [1, 0, 4, 1] },
{ rect: [0, 1, 6, 4] },
{ rect: [1, 5, 4, 1] },
],
z: 0,
},
gold_light: { rect: [1, 1, 2, 1], z: 1 },
shine: { points: [[1, 2]], z: 2 },
gold_dark: { points: [[4, 4]], z: 1 },
},
}
// Frame 2: Turning
{
type: "sprite",
name: "coin_2",
size: [6, 6],
palette: "coin",
regions: {
gold: {
union: [
{ rect: [2, 0, 2, 1] },
{ rect: [1, 1, 4, 4] },
{ rect: [2, 5, 2, 1] },
],
z: 0,
},
gold_light: { rect: [2, 1, 1, 1], z: 1 },
shine: { points: [[2, 2]], z: 2 },
gold_dark: { points: [[3, 4]], z: 1 },
},
}
// Frame 3: Edge view
{
type: "sprite",
name: "coin_3",
size: [6, 6],
palette: "coin",
regions: {
gold: { rect: [2, 0, 2, 6], z: 0 },
gold_light: { points: [[2, 1]], z: 1 },
gold_dark: { points: [[3, 4]], z: 1 },
},
}
// Frame 4: Returning
{
type: "sprite",
name: "coin_4",
size: [6, 6],
palette: "coin",
regions: {
gold: {
union: [
{ rect: [2, 0, 2, 1] },
{ rect: [1, 1, 4, 4] },
{ rect: [2, 5, 2, 1] },
],
z: 0,
},
gold_light: { rect: [3, 1, 1, 1], z: 1 },
shine: { points: [[3, 2]], z: 2 },
gold_dark: { points: [[2, 4]], z: 1 },
},
}
{
type: "animation",
name: "coin_spin",
frames: ["coin_1", "coin_2", "coin_3", "coin_4"],
duration: 120,
loop: true,
}
Preview Animations
Preview in terminal:
pxl show coin.pxl --name coin_spin
The animation plays in your terminal using ANSI colors.
Export to GIF
pxl render coin.pxl --name coin_spin -o coin.gif
For scaled output:
pxl render coin.pxl --name coin_spin -o coin.gif --scale 4
Timing Tips
Frame Duration
- Fast action (attacks, impacts): 50-80ms per frame
- Standard motion (walking, running): 80-120ms per frame
- Slow motion (idle breathing, floating): 150-250ms per frame
Frame Count Guidelines
| Animation Type | Typical Frames |
|---|---|
| Idle breathing | 2-4 frames |
| Walk cycle | 4-8 frames |
| Run cycle | 6-8 frames |
| Attack | 3-6 frames |
| Jump | 4-6 frames |
| Death | 4-8 frames |
Ease-In/Ease-Out
Hold keyframes longer than in-between frames:
{
type: "animation",
name: "attack",
frames: [
"attack_windup",
"attack_windup",
"attack_swing",
"attack_impact",
"attack_impact",
"attack_recover",
],
duration: 80,
}
By repeating attack_windup and attack_impact, you create anticipation and follow-through.
Walk Cycle Example
A basic 4-frame walk:
{
type: "sprite",
name: "walk_1",
size: [8, 12],
palette: "character",
regions: {
body: { rect: [2, 0, 4, 8], z: 0 },
leg_l: { rect: [2, 8, 2, 4], z: 1 },
leg_r: { rect: [4, 8, 2, 4], z: 1 },
},
}
{
type: "sprite",
name: "walk_2",
size: [8, 12],
palette: "character",
regions: {
body: { rect: [2, 0, 4, 8], z: 0 },
leg_l: { rect: [1, 8, 2, 4], z: 1 },
leg_r: { rect: [5, 8, 2, 4], z: 1 },
},
}
{
type: "animation",
name: "walk_right",
frames: ["walk_1", "walk_2", "walk_1", "walk_2"],
duration: 100,
}
For mirrored walk (walking left), you can use transforms in compositions or create separate sprites.
Non-Looping Animations
For one-shot animations like death or victory:
{
type: "animation",
name: "death",
frames: ["death_1", "death_2", "death_3", "death_final"],
duration: 120,
loop: false,
}
Organizing Animation Files
Structure for a character with multiple animations:
hero/
├── hero_palette.pxl
├── hero_idle.pxl
├── hero_walk.pxl
├── hero_attack.pxl
└── hero_animations.pxl
The hero_animations.pxl file includes all sprites and defines animations:
{ type: "include", path: "hero_palette.pxl" }
{ type: "include", path: "hero_idle.pxl" }
{ type: "include", path: "hero_walk.pxl" }
{ type: "include", path: "hero_attack.pxl" }
{ type: "animation", name: "hero_idle", frames: ["idle_1", "idle_2"], duration: 300 }
{ type: "animation", name: "hero_walk", frames: ["walk_1", "walk_2", "walk_3", "walk_4"], duration: 100 }
{ type: "animation", name: "hero_attack", frames: ["attack_1", "attack_2", "attack_3"], duration: 80 }
Next Steps
- Export to spritesheets for game engines (see The Game Developer)
- Learn about transforms for flipping and rotating (see Format Specification)
The Tool Builder
You build pipelines and automation. You want reliable validation, reproducible builds, and seamless CI/CD integration.
Your Workflow
- Set up a project with
pxl init - Configure validation rules
- Integrate with build systems
- Add CI/CD checks
Project Setup
Initialize a Pixelsrc project:
pxl init
This creates pxl.toml with default configuration:
[project]
name = "my-sprites"
version = "1.0.0"
[build]
input = "src/**/*.pxl"
output = "dist"
[validation]
strict = true
Validation Pipeline
Basic Validation
Check all files for errors:
pxl validate src/
Strict Mode
Catch warnings in CI:
pxl validate src/ --strict
Strict mode fails on:
- Missing palette definitions
- Undefined tokens
- Row length mismatches
- Unused palettes or sprites
Validation Output
$ pxl validate src/ --strict
src/hero.pxl:15: warning: undefined token {typo} in sprite "hero_idle"
src/items.pxl:8: error: palette "missing" not found
Validation failed: 1 error, 1 warning
Build System
Single File Build
pxl render hero.pxl -o dist/hero.png
Batch Building
Build all sprites in a directory:
pxl build src/ -o dist/
This renders all sprites and animations to the output directory.
Build Configuration
Configure in pxl.toml:
[build]
input = "src/**/*.pxl"
output = "dist"
scale = 1
[build.png]
enabled = true
[build.gif]
enabled = true
scale = 2
[build.spritesheet]
enabled = true
format = "json"
Watch Mode
Auto-rebuild on changes:
pxl build src/ -o dist/ --watch
Formatting
Keep files consistent:
pxl fmt src/
Format configuration in pxl.toml:
[format]
indent = 2
sort_keys = true
compact_small_grids = true
Format Check (CI)
pxl fmt src/ --check
Exits with non-zero status if files need formatting.
CI/CD Integration
GitHub Actions Example
name: Validate Sprites
on: [push, pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pxl
run: cargo install pixelsrc
- name: Check formatting
run: pxl fmt src/ --check
- name: Validate sprites
run: pxl validate src/ --strict
- name: Build assets
run: pxl build src/ -o dist/
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: sprites
path: dist/
Pre-commit Hook
Add to .git/hooks/pre-commit:
#!/bin/bash
pxl fmt src/ --check || exit 1
pxl validate src/ --strict || exit 1
Analysis Tools
Diff Sprites
Compare two versions of a file:
pxl diff old/hero.pxl new/hero.pxl
Analyze Usage
Find unused palettes and sprites:
pxl analyze src/
Output:
Unused palettes:
- old_palette (src/palettes.pxl:15)
Unused sprites:
- hero_v1 (src/hero.pxl:30)
- test_sprite (src/test.pxl:5)
Statistics:
- 12 palettes defined
- 48 sprites defined
- 8 animations defined
Import/Export Workflows
Import from PNG
Convert existing pixel art:
pxl import hero.png -o hero.pxl
Options:
--palette-name: Name for generated palette--sprite-name: Name for generated sprite--max-colors: Limit palette size
Batch Import
for f in assets/*.png; do
pxl import "$f" -o "src/$(basename "$f" .png).pxl"
done
Makefile Integration
SOURCES := $(wildcard src/**/*.pxl)
OUTPUTS := $(patsubst src/%.pxl,dist/%.png,$(SOURCES))
.PHONY: all validate format clean
all: validate $(OUTPUTS)
validate:
pxl validate src/ --strict
format:
pxl fmt src/
dist/%.png: src/%.pxl
@mkdir -p $(dir $@)
pxl render $< -o $@
clean:
rm -rf dist/
Best Practices
Directory Structure
project/
├── pxl.toml
├── src/
│ ├── palettes/
│ │ └── common.pxl
│ ├── sprites/
│ │ ├── characters/
│ │ ├── items/
│ │ └── ui/
│ └── animations/
├── dist/ # Generated output
└── .github/
└── workflows/
└── sprites.yml
Versioning
Track .pxl source files in git. Add dist/ to .gitignore if building in CI.
Documentation
Use the explain command to generate documentation:
pxl explain src/hero.pxl > docs/hero.md
This creates readable documentation of all objects in the file.
The Game Developer
You're building a game and need production-ready assets. Spritesheets, atlas formats, and smooth integration with your engine.
Your Workflow
- Create sprites and animations in Pixelsrc
- Export to spritesheets or atlas formats
- Import into your game engine
- Iterate without breaking references
Quick Preview
Before exporting, verify your sprites render correctly:
{
type: "palette",
name: "hero",
colors: {
_: "transparent",
outline: "#1A1A2E",
body: "#4169E1",
skin: "#FFCC99",
},
}
{
type: "sprite",
name: "hero_idle",
size: [5, 4],
palette: "hero",
regions: {
outline: {
union: [
{ rect: [1, 0, 3, 1] },
{ points: [[0, 1], [4, 1]] },
],
z: 0,
},
skin: { rect: [1, 1, 3, 1], z: 1 },
body: {
union: [
{ rect: [1, 2, 3, 1] },
{ points: [[1, 3], [3, 3]] },
],
z: 0,
},
},
}
Spritesheet Export
Basic Spritesheet
Export all sprites to a single spritesheet:
pxl render characters.pxl -o spritesheet.png --spritesheet
This arranges sprites in a grid and generates metadata.
With Metadata
Generate JSON atlas alongside the PNG:
pxl render characters.pxl -o characters.png --spritesheet --atlas json
Output:
characters.png- The spritesheet imagecharacters.json- Frame coordinates and metadata
Atlas Formats
Pixelsrc supports common game engine formats:
# Generic JSON (works with most engines)
pxl render sprites.pxl -o atlas.png --spritesheet --atlas json
# Aseprite format
pxl render sprites.pxl -o atlas.png --spritesheet --atlas aseprite
# TexturePacker format
pxl render sprites.pxl -o atlas.png --spritesheet --atlas texturepacker
Atlas JSON Structure
The JSON atlas contains everything your engine needs:
{
"frames": {
"hero_idle": {
"frame": {"x": 0, "y": 0, "w": 16, "h": 16},
"sourceSize": {"w": 16, "h": 16},
"spriteSourceSize": {"x": 0, "y": 0, "w": 16, "h": 16}
},
"hero_walk_1": {
"frame": {"x": 16, "y": 0, "w": 16, "h": 16},
"sourceSize": {"w": 16, "h": 16},
"spriteSourceSize": {"x": 0, "y": 0, "w": 16, "h": 16}
}
},
"animations": {
"hero_walk": {
"frames": ["hero_walk_1", "hero_walk_2", "hero_walk_3", "hero_walk_4"],
"duration": 100,
"loop": true
}
},
"meta": {
"image": "characters.png",
"size": {"w": 256, "h": 256},
"scale": 1
}
}
Engine Integration Examples
Godot
Load the atlas in GDScript:
var atlas_data = load("res://assets/characters.json")
var texture = load("res://assets/characters.png")
func get_sprite_rect(sprite_name: String) -> Rect2:
var frame = atlas_data.frames[sprite_name].frame
return Rect2(frame.x, frame.y, frame.w, frame.h)
Unity
Use with Unity's Sprite Atlas or custom importer:
[System.Serializable]
public class PixelsrcAtlas {
public Dictionary<string, FrameData> frames;
public Dictionary<string, AnimationData> animations;
}
// Load and create sprites
var json = Resources.Load<TextAsset>("characters");
var atlas = JsonUtility.FromJson<PixelsrcAtlas>(json.text);
Phaser
this.load.json('atlas_data', 'assets/characters.json');
this.load.image('atlas_image', 'assets/characters.png');
// In create()
const data = this.cache.json.get('atlas_data');
// Use frame coordinates from data.frames
Love2D
local json = require("json")
local atlas_data = json.decode(love.filesystem.read("characters.json"))
local atlas_image = love.graphics.newImage("characters.png")
function draw_sprite(name, x, y)
local frame = atlas_data.frames[name].frame
local quad = love.graphics.newQuad(
frame.x, frame.y, frame.w, frame.h,
atlas_image:getDimensions()
)
love.graphics.draw(atlas_image, quad, x, y)
end
Spritesheet Options
Grid Configuration
Control sprite arrangement:
# Fixed columns
pxl render sprites.pxl -o sheet.png --spritesheet --columns 8
# With padding between sprites
pxl render sprites.pxl -o sheet.png --spritesheet --padding 2
# Power-of-two dimensions (GPU friendly)
pxl render sprites.pxl -o sheet.png --spritesheet --pot
Scaling
Export at different scales:
# 1x for mobile
pxl render sprites.pxl -o sheet_1x.png --spritesheet --scale 1
# 2x for retina
pxl render sprites.pxl -o sheet_2x.png --spritesheet --scale 2
Animation Integration
Frame Timing
Access animation data from the atlas:
const anim = atlas.animations["hero_walk"];
// anim.frames = ["hero_walk_1", "hero_walk_2", ...]
// anim.duration = 100 (ms per frame)
// anim.loop = true
Playing Animations
class AnimatedSprite {
constructor(animData) {
this.frames = animData.frames;
this.duration = animData.duration;
this.loop = animData.loop;
this.currentFrame = 0;
this.elapsed = 0;
}
update(dt) {
this.elapsed += dt;
if (this.elapsed >= this.duration) {
this.elapsed -= this.duration;
this.currentFrame++;
if (this.currentFrame >= this.frames.length) {
this.currentFrame = this.loop ? 0 : this.frames.length - 1;
}
}
}
getCurrentFrameName() {
return this.frames[this.currentFrame];
}
}
Build Pipeline
Watch and Rebuild
During development, auto-rebuild on changes:
pxl build src/ -o assets/ --watch --spritesheet
Multiple Spritesheets
Organize by category for memory management:
pxl render src/characters/*.pxl -o assets/characters.png --spritesheet --atlas json
pxl render src/items/*.pxl -o assets/items.png --spritesheet --atlas json
pxl render src/ui/*.pxl -o assets/ui.png --spritesheet --atlas json
Best Practices
Naming Conventions
Use consistent naming for easy engine integration:
hero_idle_1
hero_idle_2
hero_walk_1
hero_walk_2
hero_attack_1
hero_attack_2
This allows pattern-based loading: hero_walk_* gets all walk frames.
Separate Source and Output
game/
├── sprites/ # Pixelsrc source files (.pxl)
│ ├── characters/
│ └── items/
├── assets/ # Generated output (in .gitignore)
│ ├── characters.png
│ ├── characters.json
│ └── items.png
└── pxl.toml
Stable References
Keep sprite names stable. Your game code references these names—changing them breaks things. Use variants for different versions:
{"type": "sprite", "name": "hero_idle", ...}
{"type": "variant", "name": "hero_idle_v2", "base": "hero_idle", ...}
Test v2 without breaking hero_idle references.
Next Steps
- Set up CI/CD for asset building (see The Tool Builder)
- Learn about animation best practices (see The Animator)
- Export to specific formats (see Export Formats)
The AI Enthusiast
You want to generate pixel art using AI. Claude, GPT, or other LLMs—you're harnessing AI to create game assets at scale.
Why Pixelsrc for AI?
Pixelsrc was designed with AI generation in mind:
- Text-based: LLMs excel at generating structured text
- Semantic tokens:
skinis more reliable than hex codes - JSON5 format: Human-readable with comments and trailing commas
- Lenient parsing: Small AI mistakes don't break everything
- Deterministic output: Same input = same rendered image
Your Workflow
- Write or refine a system prompt
- Give the AI examples and constraints
- Generate sprites iteratively
- Validate and render output
Getting Started
Use the Built-in Prompts
Pixelsrc includes optimized system prompts:
pxl prompts show sprite
This displays a prompt tuned for sprite generation. Copy it into your AI conversation.
List Available Prompts
pxl prompts list
Available prompts:
sprite- Single sprite generationanimation- Animation sequencescharacter- Full character with variantstileset- Tileable environment pieces
Prompting Strategies
Be Specific About Size
Create a 16x16 sprite of a treasure chest.
AI tends to make sprites too large without constraints.
Provide Palette Constraints
Use only these colors:
- _: transparent
- outline: #1a1a2e (dark outline)
- wood: #8b4513 (wood brown)
- metal: #c0c0c0 (metal gray)
- gold: #ffd700 (gold accents)
Limiting the palette improves consistency and reduces errors.
Show Examples
Include a working example in your prompt:
Here's an example of the format:
{
type: "palette",
name: "chest",
colors: {
_: "transparent",
wood: "#8B4513",
metal: "#C0C0C0",
},
}
{
type: "sprite",
name: "chest_closed",
size: [6, 5],
palette: "chest",
regions: {
metal: { rect: [1, 0, 4, 1], z: 1 },
wood: {
union: [
{ rect: [0, 1, 6, 3] },
{ rect: [1, 4, 4, 1] },
],
z: 0,
},
},
}
Now create a 12x10 sprite of a key using similar style.
Request Semantic Naming
Use descriptive token names like handle, blade, shine rather than single letters.
This makes AI output more maintainable.
Iteration Workflow
Generate → Validate → Refine
- Generate: Ask AI for a sprite
- Save: Copy output to
sprite.pxl - Validate:
pxl validate sprite.pxl - Preview:
pxl show sprite.pxl - Refine: Ask AI to fix issues or adjust
Common AI Fixes
If the AI makes mistakes, you can ask for corrections:
The region coordinates are outside the sprite bounds. The sprite is 16x16,
so all coordinates must be within [0,0] to [15,15].
Please regenerate with valid region bounds.
The sprite is using "green" but the palette doesn't define that token.
Add green to the palette or use an existing color.
Lenient Mode for Prototyping
During iteration, lenient mode (the default) is your friend:
pxl show sprite.pxl # Shows sprite even with minor errors
Missing tokens render as magenta, helping you spot issues visually.
Batch Generation
Character Variants
Ask AI to generate multiple variants:
Generate a base knight sprite, then create variants:
1. knight_idle - Standing pose
2. knight_walk_1, knight_walk_2, knight_walk_3, knight_walk_4 - Walk cycle
3. knight_attack_1, knight_attack_2, knight_attack_3 - Attack sequence
Use the same palette for all sprites.
Asset Packs
Generate cohesive sets:
Create a dungeon tileset with these 16x16 tiles:
1. floor - Stone floor
2. wall_top - Top of wall
3. wall_front - Front-facing wall
4. door_closed - Closed wooden door
5. door_open - Open door
6. torch - Wall torch with flame
Use a consistent dark fantasy palette.
System Prompt Template
Here's a template for reliable AI generation:
You are a pixel art generator that outputs Pixelsrc format (JSON5).
Rules:
1. Output valid JSON5, one object per line or formatted with newlines
2. Define palettes before sprites that use them
3. Use semantic token names: skin, hair, outline (not single letters)
4. Sprites need size: [width, height] and regions with shapes
5. Shapes: rect [x, y, w, h], points [[x,y],...], circle [cx, cy, r], ellipse [cx, cy, rx, ry]
6. _ is the conventional transparent color token
Format example:
{
type: "palette",
name: "example",
colors: {
_: "transparent",
main: "#FF0000",
},
}
{
type: "sprite",
name: "example",
size: [3, 3],
palette: "example",
regions: {
main: {
union: [
{ points: [[1, 0]] },
{ rect: [0, 1, 3, 1] },
{ points: [[1, 2]] },
],
z: 0,
},
},
}
When I request a sprite, respond with ONLY valid Pixelsrc JSON5. No explanations.
Validation in AI Pipelines
Automated Validation
import subprocess
import json
def validate_pixelsrc(content: str) -> tuple[bool, str]:
"""Validate Pixelsrc content, return (success, error_message)."""
with open("temp.pxl", "w") as f:
f.write(content)
result = subprocess.run(
["pxl", "validate", "temp.pxl", "--strict", "--json"],
capture_output=True,
text=True
)
if result.returncode == 0:
return True, ""
return False, result.stderr
Retry Loop
def generate_sprite(prompt: str, max_retries: int = 3) -> str:
for attempt in range(max_retries):
response = ai_generate(prompt)
valid, error = validate_pixelsrc(response)
if valid:
return response
# Ask AI to fix the error
prompt = f"The previous output had this error: {error}\nPlease fix and regenerate."
raise Exception("Failed to generate valid sprite")
Suggestions and Fixes
Get AI-Friendly Suggestions
pxl suggest sprite.pxl
This outputs suggestions the AI can understand and act on.
Prime Your Conversation
pxl prime sprite.pxl
Generates context about the file that helps AI understand existing work.
Best Practices
Start Simple
Begin with small sprites (8x8, 12x12) before attempting larger ones.
Provide Visual Reference
If possible, describe what you want in detail:
A 16x16 potion bottle:
- Glass bottle shape (rounded bottom, narrow neck)
- Purple liquid filling 2/3 of the bottle
- Cork stopper at top
- Small highlight on glass
- Dark outline around entire sprite
Iterate on Style
Once you get a sprite you like, ask AI to generate more in the same style:
Great! Now create a health potion, mana potion, and speed potion
using the same visual style and palette.
Save Your Prompts
Keep successful prompts in a prompts/ directory for reuse.
Next Steps
- Explore System Prompts for optimized templates
- See Best Practices for advanced techniques
- Browse the Example Gallery for inspiration
Format Overview
Pixelsrc uses a structured format for defining pixel art. Sprites are described using geometric regions rather than pixel grids, making them context-efficient and edit-friendly.
File Format
Pixelsrc files use JSON5 syntax with one or more objects per file. Each object has a type field that identifies what kind of element it defines.
File Extension: .pxl
Format Features:
- Comments (
// ...and/* ... */) - Trailing commas
- Unquoted keys
- Multi-line strings
// hero.pxl - A complete character definition
{
type: "palette",
name: "hero",
colors: {
_: "transparent",
outline: "#000000",
skin: "#FFD5B4",
},
}
{
type: "sprite",
name: "hero",
size: [16, 16],
palette: "hero",
regions: {
outline: { stroke: [0, 0, 16, 16] },
skin: { fill: "inside(outline)" },
},
}
Object Types
Every Pixelsrc object requires a type field. The supported types are:
| Type | Purpose | Learn More |
|---|---|---|
palette | Define named color tokens with roles and relationships | Palette |
sprite | Define a pixel image using regions | Sprite |
state_rules | Define visual states and effects | State Rules |
animation | Sequence sprites over time | Animation |
variant | Create color variations | Variant |
composition | Layer sprites together | Composition |
Structured Format
The core innovation of Pixelsrc is the region-based approach:
// Instead of pixel grids like this:
// grid: ["{o}{o}{o}", "{o}{s}{o}", "{o}{o}{o}"]
// We define semantic regions:
regions: {
outline: { stroke: [0, 0, 3, 3] },
skin: { fill: "inside(outline)" }
}
Advantages:
- Scales with semantic complexity, not pixel count
- Easy to edit (change a number, not rewrite rows)
- Semantic meaning is explicit (roles, relationships)
- AI-optimized (describe intent, compiler resolves pixels)
Token System
Tokens are named color identifiers defined in palettes:
{
type: "palette",
name: "example",
colors: {
_: "transparent", // conventional transparent
skin: "#FFD5B4", // semantic color name
dark_hair: "#4A3728" // underscores for multi-word
}
}
Token names are referenced directly in regions (no braces):
regions: {
skin: { fill: "inside(outline)" }, // token name = region name
dark_hair: { rect: [2, 0, 12, 4] }
}
Design Philosophy
Lenient by default, strict when requested.
When AI makes small mistakes, Pixelsrc fills the gaps and continues. This design choice makes the format reliable for AI generation while allowing strict validation for production pipelines.
Lenient Mode (Default)
| Error | Behavior |
|---|---|
| Unknown token | Render as magenta #FF00FF |
| Region outside canvas | Clipped with warning |
| Duplicate name | Last definition wins |
| Invalid color | Use magenta placeholder |
| Missing palette | All regions render white with warning |
Strict Mode (--strict)
All warnings become errors. Processing stops at first error with non-zero exit code. Use this mode in CI/CD pipelines.
Example File
A complete Pixelsrc file demonstrating the structured format:
// character.pxl
{
type: "palette",
name: "hero",
colors: {
_: "transparent",
outline: "#000000",
skin: "#FFD5B4",
"skin-shadow": "#D4A574",
hair: "#8B4513",
eye: "#4169E1",
},
roles: {
outline: "boundary",
skin: "fill",
eye: "anchor",
"skin-shadow": "shadow",
},
relationships: {
"skin-shadow": { type: "derives-from", target: "skin" },
},
}
{
type: "sprite",
name: "hero",
size: [16, 16],
palette: "hero",
regions: {
// Background
_: "background",
// Head outline
"head-outline": { stroke: [4, 0, 8, 10], round: 2 },
// Hair at top
hair: { fill: "inside(head-outline)", y: [0, 4] },
// Face below hair
skin: {
fill: "inside(head-outline)",
y: [4, 10],
except: ["eye"],
},
// Eyes (symmetric)
eye: {
rect: [5, 5, 2, 2],
symmetric: "x",
},
},
}
Color Formats
Pixelsrc supports multiple color formats:
| Format | Example | Description |
|---|---|---|
#RGB | #F00 | Expands to #RRGGBB (red) |
#RGBA | #F00F | Expands to #RRGGBBAA (red, opaque) |
#RRGGBB | #FF0000 | Fully opaque color |
#RRGGBBAA | #FF000080 | With alpha channel |
rgb() | rgb(255, 0, 0) | CSS RGB notation |
hsl() | hsl(0, 100%, 50%) | CSS HSL notation |
| Named | red, transparent | CSS named colors |
The alpha channel controls transparency: 00 = fully transparent, FF = fully opaque.
See Color Formats Reference for full documentation including oklch() and color-mix().
CSS Variables
Palettes support CSS custom properties for dynamic theming:
{
type: "palette",
name: "themed",
colors: {
"--primary": "#4169E1",
_: "transparent",
main: "var(--primary)",
shadow: "color-mix(in oklch, var(--primary) 70%, black)",
},
}
See CSS Variables for full documentation.
Stream Processing
Pixelsrc files use streaming JSON5 parsing:
- Objects are parsed as complete JSON5 values
- Objects are processed in order of appearance
- Palettes must be defined before sprites that reference them
- Regions within a sprite must define dependencies before dependents
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success (lenient: may have warnings) |
| 1 | Error (strict: any warning; lenient: fatal error) |
| 2 | Invalid arguments |
Palette
A palette defines named color tokens for use in sprites. Palettes separate color definitions from sprite structure, making it easy to create color variations and maintain consistent color schemes.
Basic Syntax
{
type: "palette",
name: "hero",
colors: {
_: "transparent",
skin: "#FFCC99",
hair: "#8B4513",
},
}
Fields
| Field | Required | Description |
|---|---|---|
type | Yes | Must be "palette" |
name | Yes | Unique identifier, referenced by sprites |
colors | Yes | Map of token names to color values |
roles | No | Semantic roles for tokens |
relationships | No | Token relationships |
Example
{
type: "palette",
name: "hero",
colors: {
_: "transparent",
skin: "#FFCC99",
hair: "#8B4513",
shirt: "#4169E1",
outline: "#000000",
},
roles: {
outline: "boundary",
skin: "fill",
hair: "fill",
shirt: "fill",
},
}
Color Formats
Palettes accept colors in these formats:
| Format | Example | Description |
|---|---|---|
#RGB | #F00 | Short hex (expands to #FF0000) |
#RGBA | #F008 | Short hex with alpha |
#RRGGBB | #FF0000 | Full hex |
#RRGGBBAA | #FF000080 | Full hex with alpha |
transparent | - | Fully transparent |
CSS color formats are also supported: rgb(), hsl(), hwb(), named colors, etc. See Color Formats for details.
Semantic Roles
Define the semantic purpose of each token:
{
type: "palette",
name: "character",
colors: {
_: "transparent",
outline: "#000000",
skin: "#FFD5B4",
"skin-shadow": "#D4A574",
eye: "#4169E1",
},
roles: {
outline: "boundary", // Edge-defining
skin: "fill", // Interior mass
"skin-shadow": "shadow", // Shading
eye: "anchor", // Critical detail
},
}
Available roles:
boundary- Edge-defining outlinesanchor- Critical details (eyes, etc.)fill- Large interior areasshadow- Shading regionshighlight- Light regions
Relationships
Define how tokens relate to each other:
{
type: "palette",
name: "character",
colors: {
skin: "#FFD5B4",
"skin-shadow": "#D4A574",
"skin-hi": "#FFF0E0",
},
relationships: {
"skin-shadow": { type: "derives-from", target: "skin" },
"skin-hi": { type: "derives-from", target: "skin" },
},
}
Relationship types:
derives-from- Color derived from anothercontained-within- Region contained in anotheradjacent-to- Regions share boundarypaired-with- Symmetric regions
Reserved Tokens
_- Conventional token for transparency (widely used but not enforced)
Referencing Palettes
Sprites reference palettes by name:
{ type: "palette", name: "mono", colors: { on: "#FFF", off: "#000" } }
{ type: "sprite", name: "dot", size: [1, 1], palette: "mono", regions: { on: { rect: [0, 0, 1, 1] } } }
Inline Colors
Define colors directly in the sprite:
{
type: "sprite",
name: "dot",
size: [1, 1],
palette: { on: "#FFF", _: "transparent" },
regions: { on: { rect: [0, 0, 1, 1] } },
}
Built-in Palettes
Reference built-in palettes with the @ prefix:
{ type: "sprite", name: "retro", palette: "@gameboy", ... }
See Built-in Palettes for available options.
Order Matters
Palettes must be defined before sprites that reference them:
// Correct order
{ type: "palette", name: "hero", colors: { ... } }
{ type: "sprite", name: "hero_sprite", palette: "hero", ... }
Multiple Sprites, One Palette
A palette can be shared across multiple sprites:
{
type: "palette",
name: "ui",
colors: {
_: "transparent",
bg: "#2D2D2D",
border: "#4A4A4A",
text: "#FFFFFF",
},
}
{ type: "sprite", name: "button", palette: "ui", ... }
{ type: "sprite", name: "panel", palette: "ui", ... }
{ type: "sprite", name: "icon", palette: "ui", ... }
Changing a color in the palette updates all sprites that use it.
CSS Variables
Palettes support CSS custom properties (variables) for dynamic theming and reusable color definitions. This follows standard CSS variable syntax with full support for fallback values and nested references.
Basic Syntax
Define variables with the -- prefix and reference them with var():
{
"type": "palette",
"name": "themed",
"colors": {
"--primary": "#4169E1",
"--accent": "#FFD700",
"{_}": "transparent",
"{main}": "var(--primary)",
"{highlight}": "var(--accent)"
}
}
Variable Definition
Tests CSS custom property definition syntax (--name: value)
{"type": "palette", "name": "theme_colors", "colors": {"{_}": "#00000000", "{primary}": "#FF0000", "{secondary}": "#00FF00"}}
{"type": "sprite", "name": "theme_example", "palette": "theme_colors", "size": [2, 2], "regions": {"primary": {"rect": [0, 0, 1, 2]}, "secondary": {"rect": [1, 0, 1, 2]}}}
Variables are palette entries with keys starting with --:
| Syntax | Description |
|---|---|
"--name": "value" | Define a variable |
"--kebab-case": "#hex" | Convention: use kebab-case |
Variables are resolved in a two-pass process:
- Collection pass: All
--nameentries are collected - Resolution pass:
var()references are expanded
This allows forward references - a color can use var(--name) even if --name is defined later in the palette.
Variable References
Tests var() reference resolution
{"type": "palette", "name": "var_resolution", "colors": {"{_}": "#00000000", "{a}": "#FF0000", "{b}": "#00FF00"}}
{"type": "sprite", "name": "resolved_colors", "palette": "var_resolution", "size": [2, 2], "regions": {"a": {"rect": [0, 0, 1, 2]}, "b": {"rect": [1, 0, 1, 2]}}}
Reference variables with var(--name) or var(--name, fallback):
| Syntax | Description |
|---|---|
var(--name) | Simple reference |
var(--name, fallback) | Reference with fallback value |
var(name) | Also works (-- prefix optional) |
Fallback Values
Tests var(--name, fallback) syntax
{"type": "palette", "name": "simple_fallback", "colors": {"{_}": "#00000000", "{fb}": "#FF0000"}}
{"type": "palette", "name": "nested_fallback", "colors": {"{_}": "#00000000", "{nf}": "#00FF00"}}
{"type": "palette", "name": "color_mix_fallback", "colors": {"{_}": "#00000000", "{mf}": "#0000FF"}}
{"type": "sprite", "name": "fallback_demo", "palette": "simple_fallback", "size": [2, 2], "regions": {"fb": {"rect": [0, 0, 2, 2]}}}
{"type": "sprite", "name": "nested_fallback_result", "palette": "nested_fallback", "size": [2, 2], "regions": {"nf": {"rect": [0, 0, 2, 2]}}}
{"type": "sprite", "name": "mix_fallback_result", "palette": "color_mix_fallback", "size": [2, 2], "regions": {"mf": {"rect": [0, 0, 2, 2]}}}
Fallbacks are used when a variable is undefined:
{
"colors": {
"--primary": "#FF0000",
"{main}": "var(--primary)",
"{alt}": "var(--secondary, #00FF00)"
}
}
If --secondary is not defined, {alt} uses the fallback #00FF00.
Nested References
Fallbacks can contain var() references:
{
"colors": {
"--base": "#FF0000",
"{color}": "var(--override, var(--base))"
}
}
This resolves --override if defined, otherwise falls back to --base.
Use Cases
Theming
Define a theme with variables, then reference them:
{"type": "palette", "name": "dark_theme", "colors": {
"--bg": "#1A1A2E",
"--fg": "#EAEAEA",
"--accent": "#E94560",
"{_}": "transparent",
"{background}": "var(--bg)",
"{text}": "var(--fg)",
"{highlight}": "var(--accent)"
}}
Color Components
Variables can contain partial values for CSS color functions:
{
"colors": {
"--r": "255",
"--g": "128",
"--b": "0",
"{orange}": "rgb(var(--r), var(--g), var(--b))"
}
}
Or HSL components:
{
"colors": {
"--hue": "240",
"--sat": "100%",
"--light": "50%",
"{blue}": "hsl(var(--hue), var(--sat), var(--light))"
}
}
Optional Overrides
Use fallbacks for optional customization:
{
"colors": {
"--primary": "#4169E1",
"{main}": "var(--primary)",
"{alt}": "var(--alt-color, var(--primary))"
}
}
If --alt-color is defined, it's used; otherwise falls back to --primary.
Error Handling
Lenient Mode (Default)
In lenient mode, errors produce warnings and use magenta (#FF00FF) as a fallback:
- Undefined variable without fallback: magenta
- Circular reference detected: magenta
$ pxl render sprite.jsonl
Warning: variable error for '{color}': undefined variable '--missing' with no fallback
Strict Mode
In strict mode (--strict), variable errors cause the command to fail:
$ pxl render sprite.jsonl --strict
Error: variable error for '{color}': undefined variable '--missing' with no fallback
Circular References
Variables that reference each other create a circular dependency:
{
"colors": {
"--a": "var(--b)",
"--b": "var(--a)",
"{color}": "var(--a)"
}
}
This produces an error: circular dependency: --a -> --b -> --a
Best Practices
- Use descriptive names:
--primary-bgover--bg1 - Group related variables: Keep theme colors together
- Provide fallbacks for optional variables: Enables safe customization
- Use kebab-case: Matches CSS convention (
--my-color) - Avoid deep nesting: Keep reference chains shallow for readability
Compatibility
CSS variables follow the CSS Custom Properties specification with these notes:
- Variable names are normalized (with or without
--prefix) - Whitespace in
var()is trimmed - All CSS color formats work in variables (hex, rgb, hsl, named colors)
- Resolution depth is limited to prevent stack overflow (100 levels)
Example
Complete example with theming:
{"type": "palette", "name": "dracula", "colors": {
"--bg": "#282A36",
"--fg": "#F8F8F2",
"--comment": "#6272A4",
"--cyan": "#8BE9FD",
"--green": "#50FA7B",
"--orange": "#FFB86C",
"--pink": "#FF79C6",
"--purple": "#BD93F9",
"--red": "#FF5555",
"--yellow": "#F1FA8C",
"{_}": "transparent",
"{bg}": "var(--bg)",
"{outline}": "var(--comment)",
"{skin}": "var(--orange)",
"{hair}": "var(--purple)",
"{eyes}": "var(--cyan)",
"{shirt}": "var(--pink)"
}}
{"type": "sprite", "name": "character", "size": [5, 5], "palette": "dracula", "regions": {
"hair": {"union": [{"rect": [1, 0, 3, 1]}, {"points": [[0, 1], [4, 1]]}], "z": 0},
"skin": {"union": [{"rect": [1, 1, 3, 1]}, {"points": [[2, 2]]}, {"rect": [1, 3, 3, 1]}], "z": 1},
"outline": {"points": [[0, 2], [4, 2]], "z": 0},
"eyes": {"points": [[1, 2], [3, 2]], "z": 2},
"shirt": {"rect": [1, 4, 3, 1], "z": 0}
}}
This creates a character sprite using Dracula theme colors, with all colors defined as variables for easy theme switching.
CSS Colors
Pixelsrc supports the full range of CSS color formats, allowing you to use familiar web color syntax in your palette definitions.
Hex Colors
Tests hex color formats: #rgb, #rrggbb, #rrggbbaa
{"type": "palette", "name": "hex_short", "colors": {"{_}": "#00000000", "{r}": "#F00", "{g}": "#0F0", "{b}": "#00F"}}
{"type": "palette", "name": "hex_full", "colors": {"{_}": "#00000000", "{r}": "#FF0000", "{g}": "#00FF00", "{b}": "#0000FF"}}
{"type": "palette", "name": "hex_alpha", "colors": {"{_}": "#00000000", "{a1}": "#FF000080", "{a2}": "#FF0000C0", "{a3}": "#FF0000FF"}}
{"type": "sprite", "name": "rgb_short", "palette": "hex_short", "size": [3, 1], "regions": {"r": {"points": [[0, 0]]}, "g": {"points": [[1, 0]]}, "b": {"points": [[2, 0]]}}}
{"type": "sprite", "name": "alpha_gradient", "palette": "hex_alpha", "size": [3, 1], "regions": {"a1": {"points": [[0, 0]]}, "a2": {"points": [[1, 0]]}, "a3": {"points": [[2, 0]]}}}
Standard hexadecimal color notation:
| Format | Example | Description |
|---|---|---|
#RGB | #F00 | Short form (expands to #RRGGBB) |
#RRGGBB | #FF0000 | Full form |
#RRGGBBAA | #FF0000FF | With alpha channel |
RGB Colors
Tests rgb() and rgba() color functions
{"type": "palette", "name": "rgb_basic", "colors": {"{_}": "#00000000", "{r}": "rgb(255, 0, 0)", "{g}": "rgb(0, 255, 0)", "{b}": "rgb(0, 0, 255)"}}
{"type": "palette", "name": "rgba_alpha", "colors": {"{_}": "#00000000", "{a1}": "rgba(255, 0, 0, 0.5)", "{a2}": "rgba(255, 0, 0, 0.75)", "{a3}": "rgba(255, 0, 0, 1.0)"}}
{"type": "sprite", "name": "rgb_demo", "palette": "rgb_basic", "size": [3, 1], "regions": {"r": {"points": [[0, 0]]}, "g": {"points": [[1, 0]]}, "b": {"points": [[2, 0]]}}}
{"type": "sprite", "name": "rgba_gradient", "palette": "rgba_alpha", "size": [3, 1], "regions": {"a1": {"points": [[0, 0]]}, "a2": {"points": [[1, 0]]}, "a3": {"points": [[2, 0]]}}}
Functional RGB notation:
{
"colors": {
"{red}": "rgb(255, 0, 0)",
"{green}": "rgb(0, 255, 0)",
"{semi}": "rgba(255, 0, 0, 0.5)"
}
}
| Format | Example | Description |
|---|---|---|
rgb(r, g, b) | rgb(255, 128, 0) | RGB values (0-255) |
rgba(r, g, b, a) | rgba(255, 128, 0, 0.5) | With alpha (0.0-1.0) |
rgb(r g b) | rgb(255 128 0) | Space-separated (CSS4) |
rgb(r g b / a) | rgb(255 128 0 / 50%) | CSS4 with alpha |
HSL Colors
Tests hsl() and hsla() color functions
{"type": "palette", "name": "hsl_basic", "colors": {"{_}": "#00000000", "{r}": "hsl(0, 100%, 50%)", "{g}": "hsl(120, 100%, 50%)", "{b}": "hsl(240, 100%, 50%)"}}
{"type": "palette", "name": "hsl_saturation", "colors": {"{_}": "#00000000", "{s0}": "hsl(0, 0%, 50%)", "{s50}": "hsl(0, 50%, 50%)", "{s100}": "hsl(0, 100%, 50%)"}}
{"type": "palette", "name": "hsl_lightness", "colors": {"{_}": "#00000000", "{l25}": "hsl(0, 100%, 25%)", "{l50}": "hsl(0, 100%, 50%)", "{l75}": "hsl(0, 100%, 75%)"}}
{"type": "sprite", "name": "hsl_demo", "palette": "hsl_basic", "size": [3, 1], "regions": {"r": {"points": [[0, 0]]}, "g": {"points": [[1, 0]]}, "b": {"points": [[2, 0]]}}}
{"type": "sprite", "name": "saturation_demo", "palette": "hsl_saturation", "size": [3, 1], "regions": {"s0": {"points": [[0, 0]]}, "s50": {"points": [[1, 0]]}, "s100": {"points": [[2, 0]]}}}
Hue-saturation-lightness notation:
{
"colors": {
"{red}": "hsl(0, 100%, 50%)",
"{muted}": "hsl(0, 50%, 50%)",
"{dark}": "hsl(0, 100%, 25%)"
}
}
| Parameter | Range | Description |
|---|---|---|
| Hue | 0-360 | Color wheel angle (0=red, 120=green, 240=blue) |
| Saturation | 0-100% | Color intensity (0=gray, 100=vivid) |
| Lightness | 0-100% | Brightness (0=black, 50=normal, 100=white) |
OKLCH Colors
Tests oklch() color function (perceptually uniform)
{"type": "palette", "name": "oklch_basic", "colors": {"{_}": "#00000000", "{r}": "oklch(0.6 0.2 30)", "{g}": "oklch(0.8 0.2 150)", "{b}": "oklch(0.5 0.2 270)"}}
{"type": "palette", "name": "oklch_lightness", "colors": {"{_}": "#00000000", "{l25}": "oklch(0.25 0.2 30)", "{l50}": "oklch(0.5 0.2 30)", "{l75}": "oklch(0.75 0.2 30)"}}
{"type": "palette", "name": "oklch_chroma", "colors": {"{_}": "#00000000", "{c0}": "oklch(0.6 0 30)", "{c1}": "oklch(0.6 0.1 30)", "{c2}": "oklch(0.6 0.2 30)"}}
{"type": "sprite", "name": "oklch_demo", "palette": "oklch_basic", "size": [3, 1], "regions": {"r": {"points": [[0, 0]]}, "g": {"points": [[1, 0]]}, "b": {"points": [[2, 0]]}}}
Perceptually uniform color space for consistent brightness:
{
"colors": {
"{vibrant}": "oklch(70% 0.15 30)",
"{muted}": "oklch(70% 0.05 30)"
}
}
| Parameter | Range | Description |
|---|---|---|
| Lightness | 0-100% | Perceived brightness |
| Chroma | 0-0.4 | Color intensity |
| Hue | 0-360 | Color angle |
OKLCH is ideal for generating consistent color palettes where colors have the same perceived brightness.
HWB Colors
Tests hwb() color function (hue-whiteness-blackness)
{"type": "palette", "name": "hwb_basic", "colors": {"{_}": "#00000000", "{r}": "hwb(0 0% 0%)", "{g}": "hwb(120 0% 0%)", "{b}": "hwb(240 0% 0%)"}}
{"type": "palette", "name": "hwb_whiteness", "colors": {"{_}": "#00000000", "{w0}": "hwb(0 0% 0%)", "{w25}": "hwb(0 25% 0%)", "{w50}": "hwb(0 50% 0%)"}}
{"type": "palette", "name": "hwb_blackness", "colors": {"{_}": "#00000000", "{b0}": "hwb(0 0% 0%)", "{b25}": "hwb(0 0% 25%)", "{b50}": "hwb(0 0% 50%)"}}
{"type": "sprite", "name": "hwb_demo", "palette": "hwb_basic", "size": [3, 1], "regions": {"r": {"points": [[0, 0]]}, "g": {"points": [[1, 0]]}, "b": {"points": [[2, 0]]}}}
Hue-whiteness-blackness notation:
{
"colors": {
"{pure}": "hwb(0 0% 0%)",
"{tinted}": "hwb(0 20% 0%)",
"{shaded}": "hwb(0 0% 20%)"
}
}
| Parameter | Range | Description |
|---|---|---|
| Hue | 0-360 | Color wheel angle |
| Whiteness | 0-100% | Amount of white mixed in |
| Blackness | 0-100% | Amount of black mixed in |
Named Colors
Tests CSS named colors (red, blue, coral, etc.)
{"type": "palette", "name": "named_basic", "colors": {"{_}": "#00000000", "{r}": "red", "{g}": "green", "{b}": "blue"}}
{"type": "palette", "name": "named_warm", "colors": {"{_}": "#00000000", "{c}": "coral", "{o}": "orange", "{g}": "gold"}}
{"type": "palette", "name": "named_cool", "colors": {"{_}": "#00000000", "{t}": "teal", "{c}": "cyan", "{a}": "aqua"}}
{"type": "palette", "name": "named_neutral", "colors": {"{_}": "#00000000", "{w}": "white", "{s}": "silver", "{g}": "gray"}}
{"type": "sprite", "name": "named_demo", "palette": "named_basic", "size": [3, 1], "regions": {"r": {"points": [[0, 0]]}, "g": {"points": [[1, 0]]}, "b": {"points": [[2, 0]]}}}
{"type": "sprite", "name": "grayscale", "palette": "named_neutral", "size": [3, 1], "regions": {"w": {"points": [[0, 0]]}, "s": {"points": [[1, 0]]}, "g": {"points": [[2, 0]]}}}
CSS named colors are supported:
{
"colors": {
"{primary}": "royalblue",
"{accent}": "gold",
"{bg}": "darkslategray"
}
}
All 147 CSS named colors are available, including transparent.
color-mix()
Tests color-mix() function for blending colors
{"type": "palette", "name": "color_mix_basic", "colors": {"{_}": "#00000000", "{m}": "color-mix(in srgb, red 50%, blue 50%)"}}
{"type": "palette", "name": "shadows_oklch", "colors": {"{_}": "#00000000", "{s}": "color-mix(in oklch, black 30%, blue)"}}
{"type": "palette", "name": "highlights_srgb", "colors": {"{_}": "#00000000", "{h}": "color-mix(in srgb, white 30%, red)"}}
{"type": "palette", "name": "skin_tones", "colors": {"{_}": "#00000000", "{b}": "color-mix(in srgb, #f5d0c5 80%, #8b4513)"}}
{"type": "sprite", "name": "shaded_square", "palette": "color_mix_basic", "size": [2, 2], "regions": {"m": {"rect": [0, 0, 2, 2]}}}
Blend two colors together:
{
"colors": {
"{blend}": "color-mix(in srgb, red 50%, blue)"
}
}
| Syntax | Description |
|---|---|
color-mix(in srgb, color1, color2) | 50/50 blend |
color-mix(in srgb, color1 70%, color2) | 70% color1, 30% color2 |
color-mix(in oklch, color1, color2) | Blend in OKLCH space |
Blending in OKLCH often produces more visually pleasing results for gradients and transitions.
Best Practices
- Use hex for exact colors: When you know the exact RGB values
- Use HSL for variations: Easy to create lighter/darker/muted versions
- Use OKLCH for palettes: Consistent perceived brightness across colors
- Use color-mix for blends: Create intermediate shades programmatically
- Use named colors for readability: When the name matches your intent
CSS Timing Functions
Timing functions control the pacing of animations, determining how intermediate values are calculated between keyframes.
Named Timing Functions
Named Timing Functions
Animation easing using named timing functions: linear, ease, ease-in, ease-out, ease-in-out.
{"type": "palette", "name": "box_colors", "colors": {"{_}": "#00000000", "{b}": "#00FF00"}}
{"type": "sprite", "name": "box_left", "palette": "box_colors", "size": [2, 2], "regions": {"b": {"rect": [0, 0, 2, 2]}}}
{"type": "sprite", "name": "box_center", "palette": "box_colors", "size": [2, 2], "regions": {"b": {"rect": [0, 0, 2, 2]}}}
{"type": "sprite", "name": "box_right", "palette": "box_colors", "size": [2, 2], "regions": {"b": {"rect": [0, 0, 2, 2]}}}
{"type": "animation", "name": "linear_slide", "duration": "500ms", "timing_function": "linear", "keyframes": {"from": {"sprite": "box_left"}, "to": {"sprite": "box_right"}}}
{"type": "animation", "name": "ease_slide", "duration": "500ms", "timing_function": "ease", "keyframes": {"from": {"sprite": "box_left"}, "to": {"sprite": "box_right"}}}
{"type": "animation", "name": "ease_in_slide", "duration": "500ms", "timing_function": "ease-in", "keyframes": {"from": {"sprite": "box_left"}, "to": {"sprite": "box_right"}}}
{"type": "animation", "name": "ease_out_slide", "duration": "500ms", "timing_function": "ease-out", "keyframes": {"from": {"sprite": "box_left"}, "to": {"sprite": "box_right"}}}
{"type": "animation", "name": "ease_in_out_slide", "duration": "500ms", "timing_function": "ease-in-out", "keyframes": {"from": {"sprite": "box_left"}, "to": {"sprite": "box_right"}}}
Standard CSS easing functions:
| Function | Description | Use Case |
|---|---|---|
linear | Constant speed | Mechanical motion, progress bars |
ease | Slow start/end, fast middle | General-purpose, natural feel |
ease-in | Slow start, fast end | Objects accelerating |
ease-out | Fast start, slow end | Objects decelerating |
ease-in-out | Slow start and end | Smooth transitions |
Usage
{
"type": "animation",
"name": "slide",
"timing_function": "ease-out",
"duration": "500ms",
"keyframes": {
"0%": { "sprite": "box", "transform": "translate(0, 0)" },
"100%": { "sprite": "box", "transform": "translate(10, 0)" }
}
}
cubic-bezier()
Cubic Bezier Timing
Custom easing curves using cubic-bezier(x1, y1, x2, y2).
{"type": "palette", "name": "ball_colors", "colors": {"{_}": "#00000000", "{b}": "#FFFF00"}}
{"type": "sprite", "name": "ball_top", "palette": "ball_colors", "size": [2, 2], "regions": {"b": {"rect": [0, 0, 2, 2]}}}
{"type": "sprite", "name": "ball_middle", "palette": "ball_colors", "size": [2, 2], "regions": {"b": {"rect": [0, 0, 2, 2]}}}
{"type": "sprite", "name": "ball_bottom", "palette": "ball_colors", "size": [2, 2], "regions": {"b": {"rect": [0, 0, 2, 2]}}}
{"type": "animation", "name": "bounce_fall", "duration": "800ms", "timing_function": "cubic-bezier(0.5, 0, 0.5, 1)", "keyframes": {"0%": {"sprite": "ball_top"}, "50%": {"sprite": "ball_middle"}, "100%": {"sprite": "ball_bottom"}}}
{"type": "animation", "name": "snap_ease", "duration": "500ms", "timing_function": "cubic-bezier(0.68, -0.55, 0.27, 1.55)", "keyframes": {"from": {"sprite": "ball_top"}, "to": {"sprite": "ball_bottom"}}}
{"type": "animation", "name": "smooth_decel", "duration": "500ms", "timing_function": "cubic-bezier(0.25, 0.1, 0.25, 1.0)", "keyframes": {"from": {"sprite": "ball_top"}, "to": {"sprite": "ball_bottom"}}}
Custom easing curves using cubic Bezier control points:
{
"timing_function": "cubic-bezier(0.68, -0.55, 0.27, 1.55)"
}
| Parameter | Range | Description |
|---|---|---|
| x1 | 0-1 | First control point X |
| y1 | any | First control point Y |
| x2 | 0-1 | Second control point X |
| y2 | any | Second control point Y |
Common Custom Curves
| Name | cubic-bezier | Effect |
|---|---|---|
| Bounce | cubic-bezier(0.68, -0.55, 0.27, 1.55) | Overshoots then settles |
| Snap | cubic-bezier(0.5, 0, 0.5, 1.5) | Quick with overshoot |
| Smooth | cubic-bezier(0.4, 0, 0.2, 1) | Material Design standard |
steps()
Steps Timing Function
Discrete step-based timing using steps(n) and step-start/step-end.
{"type": "palette", "name": "step_colors", "colors": {"{_}": "#00000000", "{s}": "#00FFFF"}}
{"type": "sprite", "name": "step1", "palette": "step_colors", "size": [2, 2], "regions": {"s": {"rect": [0, 0, 2, 2]}}}
{"type": "sprite", "name": "step2", "palette": "step_colors", "size": [2, 2], "regions": {"s": {"rect": [0, 0, 2, 2]}}}
{"type": "sprite", "name": "step3", "palette": "step_colors", "size": [2, 2], "regions": {"s": {"rect": [0, 0, 2, 2]}}}
{"type": "sprite", "name": "step4", "palette": "step_colors", "size": [2, 2], "regions": {"s": {"rect": [0, 0, 2, 2]}}}
{"type": "animation", "name": "steps_4", "duration": "1s", "timing_function": "steps(4)", "keyframes": {"0%": {"sprite": "step1"}, "25%": {"sprite": "step2"}, "50%": {"sprite": "step3"}, "75%": {"sprite": "step4"}, "100%": {"sprite": "step1"}}}
{"type": "animation", "name": "steps_jump_start", "duration": "1s", "timing_function": "steps(4, jump-start)", "keyframes": {"from": {"sprite": "step1"}, "to": {"sprite": "step4"}}}
{"type": "animation", "name": "steps_jump_end", "duration": "1s", "timing_function": "steps(4, jump-end)", "keyframes": {"from": {"sprite": "step1"}, "to": {"sprite": "step4"}}}
{"type": "animation", "name": "step_start_instant", "duration": "500ms", "timing_function": "step-start", "keyframes": {"from": {"sprite": "step1"}, "to": {"sprite": "step4"}}}
{"type": "animation", "name": "step_end_delayed", "duration": "500ms", "timing_function": "step-end", "keyframes": {"from": {"sprite": "step1"}, "to": {"sprite": "step4"}}}
Discrete stepping for frame-by-frame animation:
{
"timing_function": "steps(4, jump-start)"
}
| Syntax | Description |
|---|---|
steps(n) | n equal steps, default jump-end |
steps(n, jump-start) | Change at start of each step |
steps(n, jump-end) | Change at end of each step |
steps(n, jump-none) | Stay at start/end values |
steps(n, jump-both) | Include both start and end |
Aliases
| Alias | Equivalent |
|---|---|
step-start | steps(1, jump-start) |
step-end | steps(1, jump-end) |
Use Cases
Sprite sheet animation:
{
"timing_function": "steps(4)",
"keyframes": {
"0%": { "sprite": "walk_1" },
"25%": { "sprite": "walk_2" },
"50%": { "sprite": "walk_3" },
"75%": { "sprite": "walk_4" },
"100%": { "sprite": "walk_1" }
}
}
Blinking cursor:
{
"timing_function": "steps(1)",
"keyframes": {
"0%": { "opacity": 1 },
"50%": { "opacity": 0 },
"100%": { "opacity": 1 }
}
}
Timing in Pixel Art
For pixel art animations, consider:
- Use
steps()for sprite swaps: Prevents interpolation between discrete sprites - Use
linearfor mechanical motion: Conveyor belts, rotating gears - Use
ease-outfor impacts: Objects hitting the ground - Use
ease-in-outfor breathing/idle: Smooth, organic motion
Per-Keyframe Timing
Timing functions can be applied at keyframe level:
{
"keyframes": {
"0%": {
"sprite": "ball",
"transform": "translate(0, 0)",
"timing_function": "ease-in"
},
"50%": {
"sprite": "ball",
"transform": "translate(0, -10)",
"timing_function": "ease-out"
},
"100%": {
"sprite": "ball",
"transform": "translate(0, 0)"
}
}
}
This creates a ball that accelerates upward (ease-in) then decelerates downward (ease-out).
CSS Keyframes
Keyframes define the states of an animation at specific points in time. Pixelsrc uses CSS-style keyframe syntax for animations.
Percentage Keyframes
Percentage Keyframes
Animation using 0%, 50%, 100% keyframes with opacity and sprite changes.
{"type": "palette", "name": "walk_colors", "colors": {"{_}": "#00000000", "{c}": "#FF0000"}}
{"type": "sprite", "name": "walk_1", "palette": "walk_colors", "size": [2, 2], "regions": {"c": {"rect": [0, 0, 2, 2]}}}
{"type": "sprite", "name": "walk_2", "palette": "walk_colors", "size": [2, 2], "regions": {"c": {"rect": [0, 0, 2, 2]}}}
{"type": "animation", "name": "fade_walk", "duration": "1s", "timing_function": "ease-in-out", "keyframes": {"0%": {"sprite": "walk_1", "opacity": 0.0}, "50%": {"sprite": "walk_2", "opacity": 1.0}, "100%": {"sprite": "walk_1", "opacity": 0.0}}}
Define keyframes using percentage values:
{
"type": "animation",
"name": "bounce",
"duration": "1s",
"keyframes": {
"0%": { "sprite": "ball", "transform": "translate(0, 0)" },
"50%": { "sprite": "ball", "transform": "translate(0, -10)" },
"100%": { "sprite": "ball", "transform": "translate(0, 0)" }
}
}
| Percentage | Meaning |
|---|---|
0% | Animation start |
50% | Animation midpoint |
100% | Animation end |
| Any value | Intermediate state |
from/to Keywords
From/To Keyframes
Animation using from/to aliases (equivalent to 0%/100%).
{"type": "palette", "name": "dot_colors", "colors": {"{_}": "#00000000", "{d}": "#FF0000"}}
{"type": "sprite", "name": "dot", "palette": "dot_colors", "size": [1, 1], "regions": {"d": {"points": [[0, 0]]}}}
{"type": "animation", "name": "fade_in", "duration": "1s", "keyframes": {"from": {"sprite": "dot", "opacity": 0.0}, "to": {"sprite": "dot", "opacity": 1.0}}}
Use from and to as aliases for 0% and 100%:
{
"type": "animation",
"name": "fade",
"duration": "500ms",
"keyframes": {
"from": { "sprite": "icon", "opacity": 0 },
"to": { "sprite": "icon", "opacity": 1 }
}
}
| Keyword | Equivalent |
|---|---|
from | 0% |
to | 100% |
Sprite Changes
Sprite Changes at Keyframes
Animation that changes sprites at different keyframes (idle -> jump -> land -> idle).
{"type": "palette", "name": "char_colors", "colors": {"{_}": "#00000000", "{c}": "#0000FF"}}
{"type": "sprite", "name": "char_idle", "palette": "char_colors", "size": [2, 2], "regions": {"c": {"rect": [0, 0, 2, 2]}}}
{"type": "sprite", "name": "char_jump", "palette": "char_colors", "size": [2, 2], "regions": {"c": {"rect": [0, 0, 2, 2]}}}
{"type": "sprite", "name": "char_land", "palette": "char_colors", "size": [2, 2], "regions": {"c": {"rect": [0, 0, 2, 2]}}}
{"type": "animation", "name": "jump_cycle", "duration": "800ms", "keyframes": {"0%": {"sprite": "char_idle"}, "25%": {"sprite": "char_jump"}, "75%": {"sprite": "char_land"}, "100%": {"sprite": "char_idle"}}}
Switch between different sprites at keyframes:
{
"type": "animation",
"name": "walk",
"duration": "400ms",
"timing_function": "steps(4)",
"keyframes": {
"0%": { "sprite": "walk_1" },
"25%": { "sprite": "walk_2" },
"50%": { "sprite": "walk_3" },
"75%": { "sprite": "walk_4" },
"100%": { "sprite": "walk_1" }
}
}
Use steps() timing when switching sprites to prevent blending between frames.
Transform Animations
Transform Animations
Animations using CSS transforms (rotate, scale) at keyframes.
{"type": "palette", "name": "shape_colors", "colors": {"{_}": "#00000000", "{s}": "#FF00FF"}}
{"type": "sprite", "name": "shape", "palette": "shape_colors", "size": [2, 2], "regions": {"s": {"rect": [0, 0, 2, 2]}}}
{"type": "animation", "name": "spin", "duration": "1s", "timing_function": "linear", "keyframes": {"0%": {"sprite": "shape", "transform": "rotate(0deg)"}, "100%": {"sprite": "shape", "transform": "rotate(360deg)"}}}
{"type": "animation", "name": "pulse", "duration": "500ms", "timing_function": "ease-in-out", "keyframes": {"0%": {"sprite": "shape", "transform": "scale(1)", "opacity": 1.0}, "50%": {"sprite": "shape", "transform": "scale(1.5)", "opacity": 0.5}, "100%": {"sprite": "shape", "transform": "scale(1)", "opacity": 1.0}}}
Animate CSS transforms across keyframes:
{
"type": "animation",
"name": "spin",
"duration": "1s",
"timing_function": "linear",
"keyframes": {
"0%": { "sprite": "star", "transform": "rotate(0deg)" },
"100%": { "sprite": "star", "transform": "rotate(360deg)" }
}
}
Supported Transform Properties
| Property | Example | Description |
|---|---|---|
translate | translate(10, 5) | Move position |
rotate | rotate(90deg) | Rotate clockwise |
scale | scale(2) | Scale size |
flip | flip(x) | Mirror horizontally/vertically |
Combined Transforms
Multiple transforms can be combined:
{
"keyframes": {
"0%": {
"sprite": "ship",
"transform": "translate(0, 0) rotate(0deg) scale(1)"
},
"100%": {
"sprite": "ship",
"transform": "translate(20, -10) rotate(45deg) scale(1.5)"
}
}
}
Opacity
Animate transparency:
{
"keyframes": {
"0%": { "sprite": "ghost", "opacity": 1.0 },
"50%": { "sprite": "ghost", "opacity": 0.3 },
"100%": { "sprite": "ghost", "opacity": 1.0 }
}
}
| Value | Description |
|---|---|
1.0 | Fully opaque |
0.5 | 50% transparent |
0.0 | Fully transparent |
Keyframe Interpolation
Values between keyframes are interpolated based on the timing function:
- Transforms: Position, rotation, and scale interpolate smoothly
- Opacity: Interpolates linearly
- Sprites: Use
steps()timing to avoid blending
Best Practices
- Use
steps()for sprite swaps: Prevents blurry transitions - Keep transform chains consistent: Same transforms in same order across keyframes
- Use
from/tofor simple animations: More readable than0%/100% - Add intermediate keyframes for complex motion: Control the path, not just start/end
- Match loop points: For looping animations, ensure
100%matches0%
Unsupported CSS Features
Pixelsrc deliberately excludes certain CSS features to maintain simplicity, GenAI reliability, and focus on pixel art use cases. This page documents what's not supported and why.
Design Philosophy
Pixelsrc follows a GenAI-first design philosophy: every feature should be reliably generatable by LLMs. CSS features are excluded when they:
- Add complexity without pixel art benefit - Features designed for responsive web layouts
- Require browser/runtime context - Features that depend on DOM state or inheritance
- Have poor GenAI reliability - Syntax that LLMs frequently generate incorrectly
- Exceed scope - Full CSS engine features beyond color/transform/timing
Colors
Not Supported
| Feature | Syntax | Why Excluded |
|---|---|---|
lab() | lab(50% 40 59) | Rarely used; oklch() covers perceptual needs better |
lch() | lch(50% 59 40) | Superseded by oklch() with better perceptual uniformity |
color() | color(display-p3 1 0.5 0) | Wide-gamut displays irrelevant for pixel art |
currentColor | currentColor | No CSS cascade; colors are explicit per-palette |
| System colors | Canvas, CanvasText | No browser context; pixel art needs explicit colors |
| Relative color syntax | hsl(from red h s 50%) | Complex syntax; use color-mix() instead |
Rationale
Perceptual color spaces: Pixelsrc supports oklch() as the modern perceptual color space. Older lab() and lch() are excluded because:
oklch()has better perceptual uniformity thanlch()- Supporting multiple perceptual spaces adds complexity without benefit
- GenAI is more reliable generating
oklch()due to its simpler mental model
Context-dependent colors: currentColor and system colors require a CSS cascade or browser context that doesn't exist in Pixelsrc. All colors must be explicitly defined in palettes.
Wide-gamut colors: The color() function for display-p3 and other wide-gamut spaces targets modern displays with extended color ranges. Pixel art is rendered to standard sRGB and doesn't benefit from this.
Relative color syntax: While powerful, this CSS5 feature has complex syntax that GenAI struggles to generate correctly. The same effect can be achieved more reliably with color-mix().
Variables
Not Supported
| Feature | Syntax | Why Excluded |
|---|---|---|
:root scope | :root { --color: red } | No CSS cascade; palette is the scope |
@property | @property --color {...} | Typed custom properties require CSS engine |
| Variables in grid tokens | {var(--name)} | Tokens are literal names, not CSS values |
| Variables in sprite/palette names | "name": "var(--x)" | Names are identifiers, not expressions |
calc() | calc(100% - 10px) | Math expressions require CSS engine |
Rationale
No CSS cascade: Pixelsrc palettes are flat maps, not cascading stylesheets. There's no :root or inheritance - each palette defines its own scope. Variables are resolved within their palette only.
Tokens vs. values: Grid tokens ({skin}, {hair}) are literal names that map to palette entries. They're not CSS values and can't contain expressions. This keeps parsing simple and reliable.
No runtime math: calc() and other CSS math functions require a layout engine to resolve. Pixelsrc resolves colors at parse time, not runtime.
Why var() works for colors: Inside palette colors, var() references other entries:
{
"colors": {
"--base": "#FF0000",
"{main}": "var(--base)"
}
}
This is palette-scoped variable resolution, not full CSS custom properties.
Timing Functions
Not Supported
| Feature | Syntax | Why Excluded |
|---|---|---|
linear() with stops | linear(0, 0.25 75%, 1) | Complex piecewise timing rarely needed |
spring() | spring(1 100 10 0) | Proposed CSS; not standardized |
Supported
| Feature | Syntax | Notes |
|---|---|---|
linear | linear | Constant speed |
ease | ease | Smooth acceleration/deceleration |
ease-in | ease-in | Slow start |
ease-out | ease-out | Slow end |
ease-in-out | ease-in-out | Slow start and end |
cubic-bezier() | cubic-bezier(0.4, 0, 0.2, 1) | Custom easing curves |
steps() | steps(4, end) | Discrete frame steps |
Rationale
Named timing functions: The standard set (ease, ease-in, ease-out, ease-in-out, linear) covers 99% of animation needs. GenAI reliably generates these.
cubic-bezier() for custom curves: When named functions aren't enough, cubic-bezier() provides full control. This is the standard approach.
steps() for pixel art: Frame-by-frame animation is core to pixel art. steps(n) provides discrete, frame-accurate timing.
linear() with stops: The CSS linear() function with multiple stops creates piecewise linear timing. This is rarely needed for pixel art and adds complexity. Use multiple keyframes instead.
spring(): This is a proposed CSS feature for spring physics. It's not standardized and has poor GenAI support. Physics-based animation is out of scope for Pixelsrc.
Transforms
Not Supported
| Feature | Syntax | Why Excluded |
|---|---|---|
skew() | skew(10deg) | Distorts pixel grid; produces blurry output |
matrix() | matrix(1, 0, 0, 1, 0, 0) | Low-level; use individual transforms |
| 3D transforms | rotateX(), translateZ() | 2D only; pixel art is flat |
transform-origin | transform-origin: top left | Adds complexity; anchor points handled differently |
Supported
| Feature | Syntax | Notes |
|---|---|---|
translate() | translate(10, 5) | Pixel-level movement |
rotate() | rotate(90deg) | Best at 90-degree increments |
scale() | scale(2) | Integer scales for crisp pixels |
flip() | flip(x) / flip(y) | Pixel-perfect mirroring |
Rationale
2D only: Pixel art is inherently 2D. 3D transforms (rotateX, rotateY, rotateZ, translateZ, perspective) add complexity without benefit.
Pixel-perfect transforms: Pixelsrc prioritizes crisp pixel output:
rotate()works best at 90-degree incrementsscale()works best with integer factorstranslate()uses integer pixel offsets
No skew: skew() transforms distort the pixel grid, producing anti-aliased edges that blur the art. If you need skewed sprites, create them in your source art.
No matrix: matrix() is the low-level representation of 2D transforms. It's harder for GenAI to generate correctly and harder for humans to read. Use individual transform functions instead.
Anchor points: CSS transform-origin sets the pivot point for transforms. In Pixelsrc, anchor points are handled by the anchor parameter on squash/stretch operations, or implied by the transform order.
Blend Modes
Not Supported
| Feature | Why Excluded |
|---|---|
hue | Requires HSL color space conversion per-pixel |
saturation | Requires HSL color space conversion per-pixel |
color | Requires HSL color space conversion per-pixel |
luminosity | Requires HSL color space conversion per-pixel |
plus-lighter | Non-standard; limited support |
plus-darker | Non-standard; limited support |
Supported
| Mode | Use Case |
|---|---|
normal | Standard alpha compositing |
multiply | Shadows, color tinting |
screen | Glows, highlights |
overlay | Contrast enhancement |
add | Additive glow effects |
subtract | Special effects |
difference | Masks, inversions |
darken | Shadow overlays |
lighten | Highlight overlays |
Rationale
HSL-based blend modes: hue, saturation, color, and luminosity blend modes require converting pixels to HSL, manipulating components, and converting back. This is expensive and rarely used in pixel art. The supported modes cover common pixel art compositing needs.
Non-standard modes: plus-lighter and plus-darker are not widely supported and have inconsistent implementations. Use add or multiply instead.
Animation
Not Supported
| Feature | Syntax | Why Excluded |
|---|---|---|
| Multiple animations | animation: a 1s, b 2s | One animation per sprite; use compositions |
animation-delay | animation-delay: 500ms | Use keyframe percentages instead |
animation-direction | animation-direction: reverse | Use explicit keyframe ordering |
animation-fill-mode | animation-fill-mode: forwards | Animations loop or stop; no fill state |
animation-play-state | animation-play-state: paused | Runtime control not applicable |
Supported
| Feature | Description |
|---|---|
| Keyframes | 0%, 50%, 100% (or from, to) |
| Duration | "duration": "500ms" or "duration": 500 |
| Timing function | "timing_function": "ease-in-out" |
| Loop | "loop": true or "loop": false |
| Frame arrays | "frames": ["walk_1", "walk_2"] |
| Palette cycling | Color rotation animations |
| Frame tags | Named sub-sequences |
Rationale
One animation at a time: Pixelsrc animations target single sprites. For complex scenes with multiple animated elements, use compositions with multiple layers.
Keyframe-based delays: Instead of animation-delay, incorporate timing into keyframe percentages:
{
"keyframes": {
"0%": {"sprite": "idle"},
"50%": {"sprite": "idle"},
"60%": {"sprite": "blink"},
"100%": {"sprite": "idle"}
}
}
The first 50% acts as a delay before the blink.
Explicit direction: Instead of animation-direction: reverse, define keyframes in the desired order or use palette cycling with "direction": "reverse".
No runtime state: animation-fill-mode and animation-play-state are runtime concepts. Pixelsrc renders to static files (PNG, GIF) or spritesheet. Playback control is the game engine's responsibility.
Summary
Pixelsrc intentionally limits CSS feature scope to maintain:
- Reliability - Features GenAI can generate correctly on first attempt
- Simplicity - Flat palettes, explicit colors, 2D transforms
- Focus - Pixel art optimization, not web layout
For features not supported, there's usually a simpler alternative:
| Instead of... | Use... |
|---|---|
lab(), lch() | oklch() |
| Relative color syntax | color-mix() |
calc() | Pre-computed values |
skew() | Source art |
| 3D transforms | 2D transforms |
animation-delay | Keyframe percentages |
| Multiple animations | Compositions |
If you need a feature not supported, consider whether the complexity is worth it for pixel art, or if a simpler approach achieves the same result.
Sprite
A sprite defines a pixel art image using named regions. Each region maps to a color token from the palette and describes its shape geometrically.
Basic Syntax
{
type: "sprite",
name: "string (required)",
size: [width, height],
palette: "string (required)",
regions: {
token: { shape_definition },
token: { shape_definition },
},
}
Fields
| Field | Required | Description |
|---|---|---|
type | Yes | Must be "sprite" |
name | Yes | Unique identifier |
size | Yes | [width, height] in pixels |
palette | Yes | Palette name to use for colors |
regions | Yes | Map of token names to region definitions |
Optional Fields
| Field | Description |
|---|---|
background | Token to fill empty pixels (default: _) |
origin | Anchor point [x, y] for transforms |
metadata | Custom data passthrough for game engines |
state-rules | Name of state rules to apply |
Example
{
type: "sprite",
name: "coin",
size: [8, 8],
palette: "gold",
regions: {
_: "background",
outline: { stroke: [1, 1, 6, 6], round: 2 },
gold: { fill: "inside(outline)" },
shine: { points: [[3, 3], [4, 2]] },
},
}
Regions
The regions field maps token names to shape definitions. Tokens must exist in the referenced palette.
Simple Shapes
regions: {
// Individual pixels
eye: { points: [[5, 6], [10, 6]] },
// Filled rectangle
body: { rect: [2, 4, 12, 8] },
// Rectangle outline
outline: { stroke: [0, 0, 16, 16] },
// Filled circle
head: { circle: [8, 4, 3] },
// Line
mouth: { line: [[5, 10], [10, 10]] },
}
Fill Operations
Fill inside a boundary:
regions: {
outline: { stroke: [0, 0, 16, 16] },
skin: { fill: "inside(outline)" },
}
Fill with exclusions:
regions: {
outline: { stroke: [0, 0, 16, 16] },
eye: { rect: [5, 5, 2, 2], symmetric: "x" },
skin: {
fill: "inside(outline)",
except: ["eye"],
},
}
Symmetry
Auto-mirror regions across an axis:
regions: {
// Creates eyes at [5, 6] and [10, 6] for 16-wide sprite
eye: {
points: [[5, 6]],
symmetric: "x",
},
}
Background
The special "background" value fills all unoccupied pixels:
regions: {
_: "background",
// ... other regions ...
}
See Regions & Shapes for complete documentation of all shape primitives and modifiers.
Palette Options
Named Palette
Reference a palette defined earlier in the file:
{
type: "palette",
name: "hero_colors",
colors: { /* ... */ },
}
{
type: "sprite",
name: "hero",
palette: "hero_colors",
size: [16, 16],
regions: { /* ... */ },
}
Built-in Palette
Reference a built-in palette with @ prefix:
{
type: "sprite",
name: "retro",
palette: "@gameboy",
size: [8, 8],
regions: { /* ... */ },
}
Metadata
Attach additional data for game engine integration:
{
type: "sprite",
name: "player_attack",
size: [32, 32],
palette: "hero",
regions: { /* ... */ },
origin: [16, 32],
metadata: {
boxes: {
hurt: { x: 4, y: 0, w: 24, h: 32 },
hit: { x: 20, y: 8, w: 20, h: 16 },
},
},
}
Common Metadata Fields
| Field | Purpose |
|---|---|
origin | Sprite anchor point [x, y] |
boxes.hurt | Damage-receiving region |
boxes.hit | Damage-dealing region |
boxes.collide | Physics collision boundary |
boxes.trigger | Interaction trigger zone |
Nine-Slice
Create scalable sprites where corners stay fixed while edges and center stretch:
{
type: "sprite",
name: "button",
size: [16, 16],
palette: "ui",
regions: { /* ... */ },
nine_slice: {
left: 4,
right: 4,
top: 4,
bottom: 4,
},
}
Render at different sizes:
pxl render button.pxl --nine-slice 64x32 -o button_wide.png
Transforms (Derived Sprites)
Create derived sprites by applying op-style transforms to an existing sprite:
{
type: "sprite",
name: "hero_outlined",
source: "hero",
transform: [
{ op: "sel-out", fallback: "outline" },
],
}
Op-style transforms support both geometric operations (mirror-h, rotate:90) and effects (sel-out, dither, shadow).
See Transforms for the full list of operations.
Note: For animated transforms (in keyframes), use CSS transform strings instead. See Animation.
Complete Example
// hero.pxl
{
type: "palette",
name: "hero",
colors: {
_: "transparent",
outline: "#000000",
skin: "#FFD5B4",
hair: "#8B4513",
eye: "#4169E1",
shirt: "#E74C3C",
},
roles: {
outline: "boundary",
eye: "anchor",
skin: "fill",
},
}
{
type: "sprite",
name: "hero",
size: [16, 24],
palette: "hero",
regions: {
// Background
_: "background",
// Head
"head-outline": { stroke: [4, 0, 8, 10], round: 2 },
hair: { fill: "inside(head-outline)", y: [0, 4] },
skin: {
fill: "inside(head-outline)",
y: [4, 10],
except: ["eye"],
},
// Eyes (symmetric)
eye: { rect: [5, 5, 2, 2], symmetric: "x" },
// Body
"body-outline": { stroke: [3, 10, 10, 14] },
shirt: { fill: "inside(body-outline)" },
},
origin: [8, 24],
metadata: {
boxes: {
collide: { x: 4, y: 10, w: 8, h: 14 },
},
},
}
Error Handling
Lenient Mode (Default)
| Error | Behavior |
|---|---|
| Unknown token | Render as magenta #FF00FF |
| Region outside canvas | Clip to canvas with warning |
| Forward reference in fill | Error (must define dependencies first) |
| Missing palette | All regions render white with warning |
Strict Mode
All warnings become errors. Use --strict flag for CI validation.
Regions & Shapes
Regions define the visual structure of a sprite using geometric primitives. Each region is a named area that maps to a color token from the palette.
Overview
{
type: "sprite",
name: "hero",
size: [16, 16],
palette: "hero",
regions: {
outline: { stroke: [0, 0, 16, 16] },
skin: { fill: "inside(outline)", y: [4, 12] },
eye: { points: [[5, 6]], symmetric: "x" }
}
}
Unlike the legacy grid format, regions scale with semantic complexity rather than pixel count. A 64x64 sprite takes the same space as an 8x8 sprite if they have similar structure.
Shape Primitives
Points
Individual pixels at specific coordinates.
eye: { points: [[2, 3], [5, 3]] }
Line
Bresenham line between points.
mouth: { line: [[3, 6], [5, 6]] }
// Multiple segments
crack: { line: [[0, 0], [2, 3], [4, 2]] }
Optional: thickness (default: 1)
Rectangle
Filled rectangle.
body: { rect: [2, 4, 12, 8] } // [x, y, width, height]
Optional: round (corner radius)
Stroke
Rectangle outline (unfilled).
outline: { stroke: [0, 0, 16, 16] }
Optional: thickness, round
Ellipse
Filled ellipse.
head: { ellipse: [8, 4, 6, 4] } // [cx, cy, rx, ry]
Circle
Shorthand for equal-radius ellipse.
dot: { circle: [4, 4, 2] } // [cx, cy, r]
Polygon
Filled polygon from vertices. Winding order doesn't matter - clockwise or counter-clockwise produces identical results.
hair: {
polygon: [[4, 0], [12, 0], [14, 4], [2, 4]]
}
Tip: Keep polygons simple (3-5 vertices). Complex polygons with 6+ vertices may cause fill artifacts. Use
unionof simpler shapes instead.
Path
SVG-lite path syntax.
complex: {
path: "M2,0 L6,0 L8,2 L8,6 L6,8 L2,8 L0,6 L0,2 Z"
}
Supported commands:
M x,y- Move toL x,y- Line toH x- Horizontal line toV y- Vertical line toZ- Close path
Curves (C, Q, A) are not supported as they don't make sense for pixel art.
Fill
Flood fill inside a boundary.
skin: { fill: "inside(outline)" }
Optional: seed: [x, y] (starting point, auto-detected if omitted)
Compound Operations
Union
Combine multiple shapes.
hair: {
union: [
{ rect: [2, 0, 12, 2] },
{ rect: [0, 2, 16, 2] }
]
}
Subtract
Remove shapes from a base.
face: {
base: { rect: [2, 4, 12, 8] },
subtract: [
{ points: [[5, 6], [10, 6]] } // eye holes
]
}
Or using token references:
skin: {
fill: "inside(outline)",
except: ["eye", "mouth"]
}
Intersect
Keep only overlapping area.
visor: {
intersect: [
{ rect: [2, 4, 12, 4] },
{ fill: "inside(helmet)" }
]
}
Modifiers
Symmetry
Auto-mirror across axis.
eye: {
points: [[4, 6]],
symmetric: "x" // mirrors to [11, 6] for 16-wide sprite
}
Values:
"x"- Horizontal mirror (around canvas center)"y"- Vertical mirror (around canvas center)"xy"- Both axes (4-way symmetry)8- Mirror around specific x-coordinate
Range Constraints
Limit region to specific rows or columns.
hair: {
fill: "inside(outline)",
y: [0, 4] // rows 0-4 inclusive
}
"left-arm": {
fill: "inside(outline)",
x: [0, 8] // columns 0-8 inclusive
}
Containment Validation
Validate that region stays within another.
pupil: {
points: [[4, 6]],
within: "eye" // compiler validates this
}
Note: within is a validation constraint checked after all regions are resolved. It's distinct from fill: "inside(X)" which is a pixel-affecting operation.
Adjacency Validation
Ensure region touches another.
shadow: {
fill: "inside(outline)",
"adjacent-to": "skin",
y: [10, 14]
}
Z-Order
Explicit render order (default: definition order).
detail: {
points: [[8, 8]],
z: 100 // renders on top
}
Transform Modifiers
Repeat
Tile a shape.
bricks: {
rect: [0, 0, 4, 2],
repeat: [8, 16],
spacing: [1, 1],
"offset-alternate": true
}
Geometric Transform
Apply rotation, translation, scale.
sword: {
line: [[0, 0], [0, 8]],
transform: "rotate(45deg) translate(12, 4)"
}
Supported transforms:
translate(x, y)rotate(angle)- supportsdegorradscale(x, y)orscale(n)flip-x,flip-y
Jitter
Controlled randomness.
grass: {
points: [[0, 15], [4, 15], [8, 15], [12, 15]],
jitter: { y: [-2, 0] },
seed: 42
}
Auto-Generation
Auto-Outline
Generate outline around a region.
outline: {
"auto-outline": "body",
thickness: 1
}
Auto-Shadow
Generate drop shadow.
shadow: {
"auto-shadow": "body",
offset: [1, 1]
}
Background
Shorthand to fill all unoccupied pixels.
_: "background"
Region Resolution Order
Regions are processed in two passes:
-
Shape resolution (definition order): Each region's pixels are computed. Pixel-affecting operations (
fill: "inside(X)",except: [X],auto-outline: X) require X to be defined earlier. -
Validation (after all resolved): Constraints (
within,adjacent-to) are checked. These can reference any region.
This enables streaming while catching errors:
regions: {
outline: { stroke: [0, 0, 16, 16] }, // defined first
skin: { fill: "inside(outline)" }, // OK: outline exists
eye: { rect: [5, 6, 2, 2] },
pupil: { points: [[6, 7]], within: "eye" } // OK: within is validation-only
}
Error (forward reference in pixel-affecting operation):
regions: {
skin: { fill: "inside(outline)" }, // ERROR: outline not yet defined
outline: { stroke: [0, 0, 16, 16] }
}
Visual Reference
Here's how common shapes map to coordinates:
CIRCLE: circle: [cx, cy, r]
Example: circle: [16, 12, 8]
0 4 8 12 16 20 24 28 32
4 │ ●●●●●●● │ ← radius 8
8 │ ●● ●● │
12 │ ● (16,12) ● │ ← center
16 │ ●● ●● │
20 │ ●●●●●●● │
RECTANGLE: rect: [x, y, w, h]
Example: rect: [8, 16, 16, 8]
0 4 8 12 16 20 24 28 32
16 │ ████████████████ │ ← y=16 (top)
20 │ ████████████████ │
24 │ ████████████████ │ ← y=24 (top + height)
↑ ↑
x=8 x=24 (x + width)
POLYGON (Triangle): polygon: [[16,4], [4,28], [28,28]]
0 4 8 12 16 20 24 28 32
4 │ ▲ (16,4) │ ← tip
12 │ █████ │
20 │ █████████ │
28 │ (4,28) █████████████ (28,28) │ ← base
COMBINED: Skull silhouette (circle + rect + polygon)
0 4 8 12 16 20 24 28 32
4 │ ●●●●●●● │
8 │ ●●●●●●●●●●● │ ← circle (cranium)
12 │ ●●●●●●●●●●●●● │
16 │ ●●●●●●●●●●● │
20 │ ████████████████ │
24 │ ████████████████ │ ← rect (mid-face)
28 │ ████████████████ │
32 │ ██████████████ │
36 │ ████████████ │ ← polygon (jaw taper)
Design tip: Start with 2-3 basic shapes (circle + rect + polygon) for silhouettes. Get proportions right before adding detail.
Semantic Metadata
Semantic metadata adds meaning to your sprites through roles and relationships. This enables smarter tooling, better transforms, and clearer intent.
Overview
{
type: "palette",
name: "hero",
colors: {
_: "transparent",
outline: "#000000",
skin: "#FFD5B4",
"skin-shadow": "#D4A574",
eye: "#4169E1"
},
roles: {
outline: "boundary",
eye: "anchor",
skin: "fill",
"skin-shadow": "shadow"
},
relationships: {
"skin-shadow": {
type: "derives-from",
target: "skin"
},
pupil: {
type: "contained-within",
target: "eye"
}
}
}
Roles
Roles classify tokens by their visual/functional purpose. They're defined in the palette and inform how tools handle each region during transforms, scaling, and analysis.
Available Roles
| Role | Meaning | Transform Behavior |
|---|---|---|
boundary | Edge-defining (outlines) | High priority, preserve connectivity |
anchor | Critical details (eyes, buttons) | Must survive transforms (min 1px) |
fill | Interior mass (skin, clothes) | Can shrink, low priority |
shadow | Depth indicators | Derives from parent |
highlight | Light indicators | Derives from parent |
Defining Roles
{
type: "palette",
name: "character",
colors: {
outline: "#000000",
skin: "#FFD5B4",
eye: "#4169E1",
"skin-shadow": "#D4A574",
shine: "#FFFFFF"
},
roles: {
outline: "boundary",
skin: "fill",
eye: "anchor",
"skin-shadow": "shadow",
shine: "highlight"
}
}
Role Guidelines
boundary: Use for outlines and edges that define the sprite's silhouette. These pixels maintain connectivity even during aggressive scaling.
anchor: Use for small, critical details that identify the sprite - eyes, buttons, logos. These are protected during transforms and will be preserved even at minimum 1px size.
fill: Use for large interior regions - skin, clothing, backgrounds. These can be safely reduced when scaling down.
shadow: Use for darker variants that add depth. Tooling understands these derive from a base color.
highlight: Use for lighter variants that add emphasis. Same relationship understanding as shadows.
Relationships
Relationships define semantic connections between tokens. They help tooling understand how colors relate and validate that sprites maintain their intended structure.
Relationship Types
| Relationship | Meaning |
|---|---|
derives-from | Color derived from another token |
contained-within | Spatially inside another region |
adjacent-to | Must touch specified region |
paired-with | Symmetric relationship |
derives-from
Indicates a color is a variant of another. Used for shadow/highlight relationships.
relationships: {
"skin-shadow": {
type: "derives-from",
target: "skin"
},
"skin-highlight": {
type: "derives-from",
target: "skin"
}
}
contained-within
Indicates a region should be spatially inside another. Tooling validates this constraint.
relationships: {
pupil: {
type: "contained-within",
target: "eye"
},
"button-icon": {
type: "contained-within",
target: "button"
}
}
adjacent-to
Indicates a region should touch another. Useful for outlines and borders.
relationships: {
outline: {
type: "adjacent-to",
target: "skin"
}
}
paired-with
Indicates symmetric or complementary regions. Tooling maintains their relationship during transforms.
relationships: {
"left-eye": {
type: "paired-with",
target: "right-eye"
},
"left-arm": {
type: "paired-with",
target: "right-arm"
}
}
Usage in CLI
Show Roles
Display role annotations on a sprite:
pxl show sprite.pxl --roles
Validate Relationships
Check that all relationships are satisfied:
pxl validate sprite.pxl --strict
In strict mode, relationship violations become errors:
contained-withinchecked against actual pixel positionsadjacent-toverified that regions share at least one edgepaired-withvalidated for structural consistency
Import with Analysis
Auto-detect roles and relationships during import:
pxl import sprite.png --analyze
This uses heuristics to infer semantic metadata:
- 1px edges become
boundary - Small isolated regions (< 4px) become
anchor - Large interior regions become
fill - Darker variants detected as
derives-fromshadows
Example
Complete palette with semantic metadata:
{
type: "palette",
name: "hero",
colors: {
_: "transparent",
outline: "#000000",
skin: "#FFD5B4",
"skin-shadow": "#D4A574",
hair: "#8B4513",
eye: "#4169E1",
pupil: "#000000",
shirt: "#E74C3C",
"shirt-shadow": "#C0392B"
},
roles: {
outline: "boundary",
skin: "fill",
"skin-shadow": "shadow",
hair: "fill",
eye: "anchor",
pupil: "anchor",
shirt: "fill",
"shirt-shadow": "shadow"
},
relationships: {
"skin-shadow": { type: "derives-from", target: "skin" },
"shirt-shadow": { type: "derives-from", target: "shirt" },
pupil: { type: "contained-within", target: "eye" }
}
}
This metadata enables:
- Smarter scaling: Eyes preserved, skin can shrink
- Color variant generation: Tool knows shadow relationships
- Validation: Pupil must stay inside eye region
- Better imports: Heuristics can suggest this structure automatically
State Rules
State rules define visual states for sprites without creating separate sprite definitions. Apply filters, animations, and style changes based on selectors.
Overview
{
type: "state_rules",
name: "combat",
rules: {
".damaged [token]": {
filter: "brightness(2)",
animation: "flash 0.1s 3"
},
".poisoned [token=skin]": {
filter: "hue-rotate(80deg)"
},
"[role=boundary]": {
filter: "drop-shadow(0 0 1px black)"
}
}
}
Basic Structure
{
type: "state_rules",
name: "string (required)",
rules: {
"selector": { effect },
"selector": { effect }
}
}
Fields
| Field | Required | Description |
|---|---|---|
type | Yes | Must be "state_rules" |
name | Yes | Unique identifier for this rule set |
rules | Yes | Map of CSS selectors to state effects |
Selectors
State rules use a CSS-like selector syntax to target tokens.
Token Selectors
| Selector | Example | Meaning |
|---|---|---|
[token=name] | [token=eye] | Exact token match |
[token*=str] | [token*=skin] | Token contains substring |
[token] | [token] | Any token (wildcard) |
Role Selectors
| Selector | Example | Meaning |
|---|---|---|
[role=type] | [role=boundary] | Match by role |
State Classes
| Selector | Example | Meaning |
|---|---|---|
.state | .damaged | Match when state class is active |
Combined Selectors
Combine selectors for specific targeting:
// Skin tokens when damaged
".damaged [token=skin]": { filter: "brightness(1.5)" }
// All boundary roles when selected
".selected [role=boundary]": { filter: "brightness(1.2)" }
// Any token with "shadow" in the name
"[token*=shadow]": { opacity: 0.8 }
State Effects
filter
Apply CSS filter effects.
".damaged [token]": {
filter: "brightness(2)"
}
".frozen [token=skin]": {
filter: "hue-rotate(180deg) saturate(0.5)"
}
"[role=boundary]": {
filter: "drop-shadow(0 0 1px black)"
}
Supported filters:
brightness(n)- Adjust brightness (1 = normal)contrast(n)- Adjust contrastsaturate(n)- Adjust saturationhue-rotate(deg)- Shift hueinvert(n)- Invert colorsgrayscale(n)- Convert to grayscalesepia(n)- Apply sepia tonedrop-shadow(x y blur color)- Add shadow
animation
Apply CSS animation.
".damaged [token]": {
animation: "flash 0.1s 3"
}
".floating [token]": {
animation: "bob 1s ease-in-out infinite"
}
Animation format: name duration [timing] [iterations]
opacity
Adjust transparency.
".fading [token]": {
opacity: 0.5
}
".invisible [token]": {
opacity: 0
}
Examples
Combat States
{
type: "state_rules",
name: "combat",
rules: {
// Flash white when hit
".damaged [token]": {
filter: "brightness(2)",
animation: "flash 0.1s 3"
},
// Green tint when poisoned
".poisoned [token=skin]": {
filter: "hue-rotate(80deg)"
},
".poisoned [token*=skin]": {
filter: "hue-rotate(80deg)"
},
// Blue tint when frozen
".frozen [token]": {
filter: "hue-rotate(180deg) saturate(0.5)"
},
// Red tint when burning
".burning [token]": {
filter: "sepia(1) saturate(3) hue-rotate(-30deg)"
}
}
}
UI States
{
type: "state_rules",
name: "ui",
rules: {
// Highlight on hover
".hover [role=boundary]": {
filter: "brightness(1.3)"
},
// Dim when disabled
".disabled [token]": {
filter: "grayscale(1)",
opacity: 0.5
},
// Glow when selected
".selected [role=boundary]": {
filter: "drop-shadow(0 0 2px gold)"
},
// Pulse when active
".active [token]": {
animation: "pulse 0.5s ease-in-out infinite"
}
}
}
Visual Effects
{
type: "state_rules",
name: "effects",
rules: {
// Outline glow for all boundary tokens
"[role=boundary]": {
filter: "drop-shadow(0 0 1px black)"
},
// Highlight anchors (eyes, buttons)
"[role=anchor]": {
filter: "contrast(1.1)"
},
// Subtle shadow enhancement
"[role=shadow]": {
opacity: 0.9
}
}
}
Usage
Applying State Rules
State rules are applied at render time. The runtime determines which state classes are active and applies matching rules.
# Render with state class
pxl render sprite.pxl --state damaged -o damaged.png
# Multiple states
pxl render sprite.pxl --state "damaged poisoned" -o hurt.png
Pre-rendering States
For game engines, you can pre-render all states:
pxl render sprite.pxl --all-states -o sprites/
This creates separate output files for each state combination.
Referencing State Rules
Associate state rules with a sprite in the same file:
{
type: "state_rules",
name: "hero_states",
rules: { /* ... */ }
}
{
type: "sprite",
name: "hero",
palette: "hero",
regions: { /* ... */ },
"state-rules": "hero_states"
}
Limitations
MVP Selector Support
The current implementation supports a subset of CSS selectors:
Supported:
[token=name]- Exact match[token*=str]- Contains[role=type]- Role match.state- State class
Not supported (deferred):
:not()pseudo-class- Descendant/child combinators
^=,$=,|=attribute operators- Multiple classes (
.a.b)
Filter Limitations
Filters are applied per-token. Complex multi-token effects may require composition or custom rendering.
Animation
Animations define sequences of sprites that play over time. Pixelsrc supports two animation formats:
- CSS Keyframes (recommended): Percentage-based keyframes with CSS timing functions
- Frame Array (legacy): Simple list of sprite names
Both formats support palette cycling, frame tags, and secondary motion (attachments).
CSS Keyframes Format
The CSS keyframes format uses percentage-based keyframes, familiar to web developers and AI models. This is the recommended approach for new animations.
Syntax
{
type: "animation",
name: "fade_in",
keyframes: {
"0%": { sprite: "dot", opacity: 0.0 },
"50%": { sprite: "dot", opacity: 1.0 },
"100%": { sprite: "dot", opacity: 0.0 },
},
duration: "500ms",
timing_function: "ease-in-out",
}
Keyframe Fields
| Field | Required | Default | Description |
|---|---|---|---|
type | Yes | - | Must be "animation" |
name | Yes | - | Unique identifier |
keyframes | Yes | - | Map of percentage keys to keyframe objects |
duration | No | 100 | Total animation duration (ms or CSS time string) |
timing_function | No | "linear" | CSS timing function for easing |
loop | No | true | Whether animation loops |
Keyframe Object Fields
Each keyframe can specify any combination of these properties:
| Field | Description |
|---|---|
sprite | Sprite name to display at this keyframe |
opacity | Opacity from 0.0 (transparent) to 1.0 (opaque) |
offset | Position offset [x, y] in pixels |
transform | CSS transform string (e.g., "rotate(45deg)") — see Transforms |
Percentage Keys
Keyframe keys are percentages of the total animation duration:
"0%"- Start of animation"50%"- Halfway through"100%"- End of animation"from"- Alias for"0%""to"- Alias for"100%"
Duration Format
Duration accepts both raw milliseconds and CSS time strings:
duration: 500 // 500 milliseconds
duration: "500ms" // 500 milliseconds
duration: "1s" // 1000 milliseconds
duration: "0.5s" // 500 milliseconds
Timing Functions
The timing_function field accepts CSS easing functions:
| Function | Description |
|---|---|
linear | Constant speed (default) |
ease | Smooth acceleration and deceleration |
ease-in | Slow start, fast end |
ease-out | Fast start, slow end |
ease-in-out | Slow start and end |
cubic-bezier(x1,y1,x2,y2) | Custom bezier curve |
steps(n, position) | Discrete steps |
Examples
Fade in animation:
{
type: "animation",
name: "fade_in",
keyframes: {
from: { sprite: "dot", opacity: 0.0 },
to: { sprite: "dot", opacity: 1.0 },
},
duration: "1s",
timing_function: "ease",
}
Walk cycle with opacity:
{
type: "animation",
name: "fade_walk",
keyframes: {
"0%": { sprite: "walk_1", opacity: 0.0 },
"50%": { sprite: "walk_2", opacity: 1.0 },
"100%": { sprite: "walk_1", opacity: 0.0 },
},
duration: "500ms",
timing_function: "ease-in-out",
}
Rotating animation:
{
type: "animation",
name: "spin",
keyframes: {
"0%": { sprite: "star", transform: "rotate(0deg)" },
"100%": { sprite: "star", transform: "rotate(360deg)" },
},
duration: 1000,
timing_function: "linear",
}
Frame Array Format (Legacy)
The frame array format provides a simple list of sprite names. Use this for straightforward frame-by-frame animations.
Syntax
{
type: "animation",
name: "walk",
frames: ["walk_1", "walk_2", "walk_3", "walk_4"],
duration: 100,
loop: true,
}
Fields
| Field | Required | Default | Description |
|---|---|---|---|
type | Yes | - | Must be "animation" |
name | Yes | - | Unique identifier |
frames | Yes | - | Array of sprite names in order |
duration | No | 100 | Milliseconds per frame |
loop | No | true | Whether animation loops |
Frame References
Frames reference sprites or compositions by name. They must be defined earlier in the file:
{ type: "palette", name: "hero", colors: { ... } }
{ type: "sprite", name: "idle_1", size: [8, 8], palette: "hero", regions: { ... } }
{ type: "sprite", name: "idle_2", size: [8, 8], palette: "hero", regions: { ... } }
{ type: "animation", name: "idle", frames: ["idle_1", "idle_2"], duration: 500 }
Compositions as Frames
Animations can reference compositions, enabling layered character animation. This is useful when you want to animate individual body parts (arm, eyes, mouth) while keeping the base body static:
// Layer sprites
{"type": "sprite", "name": "body", ...}
{"type": "sprite", "name": "arm_down", ...}
{"type": "sprite", "name": "arm_wave1", ...}
{"type": "sprite", "name": "arm_wave2", ...}
// Compositions combining body + arm positions
{"type": "composition", "name": "char_pose1", "size": [32, 48], "cell_size": [32, 48],
"sprites": {"B": "body", "A": "arm_down"},
"layers": [{"map": ["B"]}, {"map": ["A"]}]}
{"type": "composition", "name": "char_pose2", "size": [32, 48], "cell_size": [32, 48],
"sprites": {"B": "body", "A": "arm_wave1"},
"layers": [{"map": ["B"]}, {"map": ["A"]}]}
{"type": "composition", "name": "char_pose3", "size": [32, 48], "cell_size": [32, 48],
"sprites": {"B": "body", "A": "arm_wave2"},
"layers": [{"map": ["B"]}, {"map": ["A"]}]}
// Animation using compositions as frames
{"type": "animation", "name": "wave", "frames": ["char_pose1", "char_pose2", "char_pose3", "char_pose2"], "duration": 200}
This approach avoids duplicating the body sprite for each animation frame.
Palette Cycling
Animate by rotating palette colors instead of changing sprites. This classic technique creates efficient water, fire, and energy effects.
{
type: "animation",
name: "waterfall",
sprite: "water_static",
palette_cycle: {
tokens: ["water1", "water2", "water3", "water4"],
fps: 8,
direction: "forward",
},
}
Palette Cycle Fields
| Field | Required | Default | Description |
|---|---|---|---|
sprite | Yes* | - | Single sprite to cycle (*required if no frames) |
palette_cycle | Yes | - | Cycle definition object or array |
palette_cycle.tokens | Yes | - | Ordered list of tokens to rotate |
palette_cycle.fps | No | 10 | Frames per second for cycling |
palette_cycle.direction | No | "forward" | "forward" or "reverse" |
Multiple Cycles
Run several palette cycles simultaneously:
{
palette_cycle: [
{ tokens: ["water1", "water2", "water3"], fps: 8 },
{ tokens: ["glow1", "glow2"], fps: 4 },
],
}
Frame Tags
Mark frame ranges with semantic names for game engine integration:
{
type: "animation",
name: "player",
frames: ["idle1", "idle2", "run1", "run2", "run3", "run4", "jump", "fall"],
fps: 10,
tags: {
idle: { start: 0, end: 1, loop: true },
run: { start: 2, end: 5, loop: true },
jump: { start: 6, end: 6, loop: false },
fall: { start: 7, end: 7, loop: false },
},
}
Tag Fields
| Field | Required | Default | Description |
|---|---|---|---|
tags | No | - | Map of tag name to tag definition |
tags.{name}.start | Yes | - | Starting frame index (0-based) |
tags.{name}.end | Yes | - | Ending frame index (inclusive) |
tags.{name}.loop | No | true | Whether this segment loops |
tags.{name}.fps | No | inherit | Override FPS for this tag |
Tags allow game engines to play specific sub-animations by name.
Per-Frame Metadata
Define hitboxes and metadata that vary across frames:
{
type: "animation",
name: "attack",
frames: ["attack_1", "attack_2", "attack_3"],
frame_metadata: [
{ boxes: { hit: null } },
{ boxes: { hit: { x: 20, y: 8, w: 20, h: 16 } } },
{ boxes: { hit: { x: 24, y: 4, w: 24, h: 20 } } },
],
}
Frame 1 has no hitbox (null), while frames 2 and 3 have active hit regions.
Secondary Motion (Attachments)
Animate attached elements like hair, capes, or tails that follow the parent animation with configurable delay:
{
type: "animation",
name: "hero_walk",
frames: ["walk_1", "walk_2", "walk_3", "walk_4"],
duration: 100,
attachments: [
{
name: "hair",
anchor: [12, 4],
chain: ["hair_1", "hair_2", "hair_3"],
delay: 1,
follow: "position",
},
{
name: "cape",
anchor: [8, 8],
chain: ["cape_top", "cape_mid", "cape_bottom"],
delay: 2,
follow: "velocity",
z_index: -1,
},
],
}
Attachment Fields
| Field | Required | Default | Description |
|---|---|---|---|
attachments | No | - | Array of attachment definitions |
attachments[].name | Yes | - | Identifier for this attachment |
attachments[].anchor | Yes | - | Attachment point [x, y] on parent sprite |
attachments[].chain | Yes | - | Array of sprite names forming the chain |
attachments[].delay | No | 1 | Frame delay between chain segments |
attachments[].follow | No | "position" | "position", "velocity", or "rotation" |
attachments[].damping | No | 0.8 | Oscillation damping (0.0-1.0) |
attachments[].stiffness | No | 0.5 | Spring stiffness (0.0-1.0) |
attachments[].z_index | No | 0 | Render order (negative = behind parent) |
Follow Modes
| Mode | Behavior |
|---|---|
position | Chain follows parent position directly |
velocity | Chain responds to movement velocity |
rotation | Chain responds to rotation changes |
Duration vs FPS
You can specify timing using either duration (ms per frame) or fps (frames per second):
{ type: "animation", name: "fast", frames: [...], duration: 50 }
{ type: "animation", name: "fast", frames: [...], fps: 20 }
Both examples create the same 20 FPS animation.
Complete Example
A blinking light with fade effect:
// Light sprites
{
type: "palette",
name: "blink",
colors: {
_: "transparent",
on: "#FFFF00",
off: "#888800",
},
}
{
type: "sprite",
name: "light_on",
size: [4, 4],
palette: "blink",
regions: {
on: {
union: [
{ rect: [1, 0, 2, 1] },
{ rect: [0, 1, 4, 2] },
{ rect: [1, 3, 2, 1] },
],
},
},
}
{
type: "sprite",
name: "light_off",
size: [4, 4],
palette: "blink",
regions: {
off: {
union: [
{ rect: [1, 0, 2, 1] },
{ rect: [0, 1, 4, 2] },
{ rect: [1, 3, 2, 1] },
],
},
},
}
// Fade animation
{
type: "animation",
name: "blink_fade",
keyframes: {
"0%": { sprite: "light_on", opacity: 1.0 },
"50%": { sprite: "light_off", opacity: 0.5 },
"100%": { sprite: "light_on", opacity: 1.0 },
},
duration: "1s",
timing_function: "ease-in-out",
}
Rendering Animations
# Render as animated GIF
pxl render animation.pxl -o output.gif
# Render as spritesheet
pxl render animation.pxl --format spritesheet -o sheet.png
# Preview with onion skinning
pxl show animation.pxl --onion 2
Migrating from Frames to Keyframes
Converting from the legacy frame array format to CSS keyframes is straightforward:
Basic Migration
Before (frames format):
{
type: "animation",
name: "walk",
frames: ["walk_1", "walk_2", "walk_3", "walk_4"],
duration: 100,
loop: true,
}
After (keyframes format):
{
type: "animation",
name: "walk",
keyframes: {
"0%": { sprite: "walk_1" },
"25%": { sprite: "walk_2" },
"50%": { sprite: "walk_3" },
"75%": { sprite: "walk_4" },
},
duration: "400ms",
loop: true,
}
Key Differences
| Aspect | Frames Format | Keyframes Format |
|---|---|---|
| Timing | duration is per-frame | duration is total animation time |
| Structure | Flat sprite array | Percentage-keyed objects |
| Properties | Sprite only | Sprite, opacity, offset, transform |
| Easing | N/A | timing_function for interpolation |
Migration Steps
-
Calculate total duration: Multiply per-frame duration by frame count
- 4 frames × 100ms = 400ms total
-
Convert to percentages: Divide frame index by total frames
- Frame 0 → 0%
- Frame 1 → 25% (1/4)
- Frame 2 → 50% (2/4)
- Frame 3 → 75% (3/4)
-
Wrap sprites in keyframe objects:
"walk_1"becomes{ sprite: "walk_1" } -
Add timing function (optional): Use
timing_function: "linear"for frame-accurate timing
When to Migrate
Migrate to keyframes when you need:
- Opacity changes: Fade effects between frames
- Position offsets: Screen shake, bouncing
- Transforms: Rotation, scaling effects
- CSS timing: Easing curves for smoother motion
Keep using frames format for:
- Simple frame-by-frame animations with no interpolation
- Quick prototypes
- Backwards compatibility with existing files
Variant
A variant creates a color variation of an existing sprite without duplicating the structure. This is useful for creating enemy color swaps, team colors, alternate costumes, and similar variations.
Basic Syntax
{
type: "variant",
name: "hero_red",
base: "hero",
palette: { shirt: "#DC143C" },
}
Fields
| Field | Required | Description |
|---|---|---|
type | Yes | Must be "variant" |
name | Yes | Unique identifier for this variant |
base | Yes | Name of the sprite to derive from |
palette | Yes | Color overrides - replaces matching tokens from base |
Example
{
type: "palette",
name: "hero",
colors: {
_: "transparent",
skin: "#FFCC99",
hair: "#8B4513",
shirt: "#4169E1",
},
}
{
type: "sprite",
name: "hero",
size: [8, 12],
palette: "hero",
regions: {
hair: { rect: [2, 0, 4, 2] },
skin: { rect: [2, 2, 4, 3] },
shirt: { rect: [1, 5, 6, 5] },
},
}
{
type: "variant",
name: "hero_red",
base: "hero",
palette: { shirt: "#DC143C" },
}
{
type: "variant",
name: "hero_green",
base: "hero",
palette: { shirt: "#228B22" },
}
All three sprites share the same regions. Only the shirt color differs.
Behavior
- Inherits regions and size from the base sprite
- Only specified tokens are overridden - unspecified tokens keep their base colors
- Base sprite must be defined first - forward references are errors
Partial Overrides
You don't need to override all colors. Only specify the tokens you want to change:
{
type: "sprite",
name: "knight",
size: [16, 16],
palette: {
armor: "#808080",
plume: "#FF0000",
skin: "#FFCC99",
eyes: "#0000FF",
},
regions: {
armor: { rect: [4, 4, 8, 10] },
plume: { rect: [6, 0, 4, 4] },
skin: { rect: [5, 2, 6, 3] },
eyes: { points: [[6, 3], [9, 3]] },
},
}
{
type: "variant",
name: "knight_gold",
base: "knight",
palette: {
armor: "#FFD700",
plume: "#FFFFFF",
},
}
The knight_gold variant has gold armor and white plume, but keeps the same skin and eye colors.
Use Cases
Team Colors
{ type: "sprite", name: "player", size: [8, 8], palette: { team: "#FF0000" }, regions: { team: { rect: [2, 2, 4, 4] } } }
{ type: "variant", name: "player_blue", base: "player", palette: { team: "#0000FF" } }
{ type: "variant", name: "player_green", base: "player", palette: { team: "#00FF00" } }
Enemy Variants
{
type: "sprite",
name: "slime",
size: [8, 8],
palette: { body: "#00FF00", eyes: "#FFFFFF" },
regions: {
body: { ellipse: [4, 5, 3, 2] },
eyes: { points: [[3, 4], [5, 4]] },
},
}
{ type: "variant", name: "slime_fire", base: "slime", palette: { body: "#FF4500" } }
{ type: "variant", name: "slime_ice", base: "slime", palette: { body: "#00CED1" } }
{ type: "variant", name: "slime_poison", base: "slime", palette: { body: "#9400D3" } }
Day/Night Variations
{
type: "sprite",
name: "house_day",
size: [16, 16],
palette: {
sky: "#87CEEB",
window: "#FFFF00",
wall: "#DEB887",
},
regions: {
sky: { rect: [0, 0, 16, 8] },
wall: { rect: [4, 8, 8, 8] },
window: { rect: [6, 10, 4, 3] },
},
}
{
type: "variant",
name: "house_night",
base: "house_day",
palette: {
sky: "#191970",
window: "#FFA500",
},
}
Chaining Variants
Variants can be based on other variants:
{ type: "sprite", name: "base_character", size: [8, 12], palette: { ... }, regions: { ... } }
{ type: "variant", name: "character_evil", base: "base_character", palette: { eyes: "#FF0000" } }
{ type: "variant", name: "character_evil_boss", base: "character_evil", palette: { armor: "#4B0082" } }
The boss inherits both the red eyes from character_evil and the base structure from base_character.
Variants vs Inline Palettes
When to use each approach:
Use variants when:
- You have multiple color swaps of the same sprite
- The base sprite is complex and you don't want to duplicate the regions
- You want to maintain a single source of truth for the structure
Use inline palettes when:
- Each sprite is unique
- You're defining a one-off sprite
- The sprite is simple
Order Matters
The base sprite must be defined before the variant:
// ERROR: variant before base
{ type: "variant", name: "hero_red", base: "hero", palette: { ... } }
{ type: "sprite", name: "hero", ... }
Correct order:
{ type: "sprite", name: "hero", ... }
{ type: "variant", name: "hero_red", base: "hero", palette: { ... } }
Complete Example
// Palette
{
type: "palette",
name: "gem_base",
colors: {
_: "transparent",
shine: "#FFFFFF",
body: "#FF0000",
shadow: "#8B0000",
},
}
// Base gem sprite
{
type: "sprite",
name: "gem",
size: [4, 4],
palette: "gem_base",
regions: {
body: {
union: [
{ rect: [1, 0, 2, 1] },
{ rect: [0, 1, 4, 2] },
{ rect: [1, 3, 2, 1] },
],
z: 0,
},
shine: { points: [[1, 0], [0, 1]], z: 1 },
shadow: { points: [[3, 2], [2, 3]], z: 1 },
},
}
// Color variants
{
type: "variant",
name: "gem_blue",
base: "gem",
palette: {
body: "#0000FF",
shadow: "#00008B",
},
}
{
type: "variant",
name: "gem_green",
base: "gem",
palette: {
body: "#00FF00",
shadow: "#006400",
},
}
{
type: "variant",
name: "gem_purple",
base: "gem",
palette: {
body: "#9400D3",
shadow: "#4B0082",
},
}
Four gem sprites from one structure definition.
Composition
A composition layers multiple sprites onto a canvas. This is useful for building scenes, tile maps, and complex images from smaller sprite components.
Basic Syntax
{
type: "composition",
name: "scene",
size: [width, height],
layers: [
{ sprite: "background", x: 0, y: 0 },
{ sprite: "hero", x: 16, y: 16 },
],
}
Fields
| Field | Required | Default | Description |
|---|---|---|---|
type | Yes | - | Must be "composition" |
name | Yes | - | Unique identifier |
size | Yes | - | Canvas size [width, height] in pixels |
layers | Yes | - | Array of layers, rendered bottom-to-top |
Layer Fields
| Field | Required | Description |
|---|---|---|
sprite | Yes | Sprite name to place |
x | No | X position (default: 0) |
y | No | Y position (default: 0) |
blend | No | Blend mode (default: "normal") |
opacity | No | Layer opacity 0.0-1.0 (default: 1.0) |
Simple Example
{
type: "palette",
name: "scene",
colors: {
_: "transparent",
g: "#228B22",
h: "#FFD700",
t: "#8B4513",
},
}
{
type: "sprite",
name: "grass",
size: [8, 8],
palette: "scene",
regions: { g: { rect: [0, 0, 8, 8] } },
}
{
type: "sprite",
name: "hero",
size: [8, 8],
palette: "scene",
regions: {
h: { rect: [2, 2, 4, 6] },
},
}
{
type: "sprite",
name: "tree",
size: [8, 8],
palette: "scene",
regions: {
g: { ellipse: [4, 2, 3, 2] },
t: { rect: [3, 4, 2, 4] },
},
}
{
type: "composition",
name: "scene",
size: [32, 32],
layers: [
{ sprite: "grass", x: 0, y: 0 },
{ sprite: "grass", x: 8, y: 0 },
{ sprite: "grass", x: 16, y: 0 },
{ sprite: "grass", x: 24, y: 0 },
{ sprite: "tree", x: 4, y: 8 },
{ sprite: "hero", x: 12, y: 16 },
{ sprite: "tree", x: 20, y: 8 },
],
}
Multiple Layers
Layers are rendered bottom-to-top. The first layer is the background:
{
type: "composition",
name: "scene",
size: [64, 64],
layers: [
// Background layer
{ sprite: "sky_background", x: 0, y: 0 },
// Middle layer - ground
{ sprite: "ground", x: 0, y: 48 },
// Foreground - characters
{ sprite: "hero", x: 24, y: 32 },
{ sprite: "enemy", x: 40, y: 32 },
],
}
Blend Modes
Layer blending controls how colors combine when layers overlap:
{
type: "composition",
name: "effects",
size: [32, 32],
layers: [
{ sprite: "background", x: 0, y: 0 },
{ sprite: "shadow", x: 8, y: 8, blend: "multiply", opacity: 0.5 },
{ sprite: "glow", x: 12, y: 4, blend: "add" },
],
}
Blend Mode Reference
| Mode | Description |
|---|---|
normal | Standard alpha compositing |
multiply | Darkens - good for shadows |
screen | Lightens - good for glows |
overlay | Enhances contrast |
add | Additive blending - fire, particles |
subtract | Subtractive blending |
difference | Absolute difference |
darken | Keeps darker color |
lighten | Keeps lighter color |
When to Use Each Mode
multiply - Shadows, tinting, darkening
{ sprite: "shadow", x: 4, y: 4, blend: "multiply", opacity: 0.3 }
screen - Glows, highlights, fog
{ sprite: "glow", x: 8, y: 8, blend: "screen" }
add - Fire, particles, magical effects
{ sprite: "particles", x: 0, y: 0, blend: "add" }
CSS Variables in Compositions
Composition layers support CSS variable references:
{
type: "composition",
name: "themed_scene",
size: [32, 32],
layers: [
{ sprite: "background", x: 0, y: 0 },
{ sprite: "shadow", x: 4, y: 4, blend: "var(--shadow-blend)", opacity: "var(--shadow-opacity)" },
{ sprite: "glow", x: 8, y: 8, blend: "var(--glow-mode, add)" },
],
}
Variables are resolved from the palette's variable registry at render time.
Nested Compositions
Compositions can reference other compositions, enabling hierarchical scene construction:
// Forest tile composition
{
type: "composition",
name: "forest_tile",
size: [16, 16],
layers: [
{ sprite: "grass", x: 0, y: 0 },
{ sprite: "tree", x: 4, y: 0 },
],
}
// Main scene using forest_tile
{
type: "composition",
name: "scene",
size: [32, 32],
layers: [
{ sprite: "forest_tile", x: 0, y: 0 },
{ sprite: "forest_tile", x: 16, y: 0 },
{ sprite: "hero", x: 12, y: 16 },
],
}
Cycle Detection
Circular references are detected and produce an error:
// ERROR: Cycle detected
{ type: "composition", name: "A", layers: [{ sprite: "B" }] }
{ type: "composition", name: "B", layers: [{ sprite: "A" }] }
Complete Example
// Palette
{
type: "palette",
name: "island",
colors: {
_: "transparent",
grass: "#228B22",
water: "#4169E1",
sand: "#F4A460",
},
}
// Tile sprites
{
type: "sprite",
name: "grass_tile",
size: [8, 8],
palette: "island",
regions: { grass: { rect: [0, 0, 8, 8] } },
}
{
type: "sprite",
name: "water_tile",
size: [8, 8],
palette: "island",
regions: { water: { rect: [0, 0, 8, 8] } },
}
{
type: "sprite",
name: "sand_tile",
size: [8, 8],
palette: "island",
regions: { sand: { rect: [0, 0, 8, 8] } },
}
// Island composition
{
type: "composition",
name: "island",
size: [32, 32],
layers: [
// Water background
{ sprite: "water_tile", x: 0, y: 0 },
{ sprite: "water_tile", x: 8, y: 0 },
{ sprite: "water_tile", x: 16, y: 0 },
{ sprite: "water_tile", x: 24, y: 0 },
{ sprite: "water_tile", x: 0, y: 24 },
{ sprite: "water_tile", x: 8, y: 24 },
{ sprite: "water_tile", x: 16, y: 24 },
{ sprite: "water_tile", x: 24, y: 24 },
// Sand border
{ sprite: "sand_tile", x: 8, y: 8 },
{ sprite: "sand_tile", x: 16, y: 8 },
{ sprite: "sand_tile", x: 8, y: 16 },
{ sprite: "sand_tile", x: 16, y: 16 },
// Grass center
{ sprite: "grass_tile", x: 12, y: 12 },
],
}
This creates a 32x32 pixel island with water around the edges, a sand border, and grass in the center.
Transforms
Transforms modify sprites at render time without changing the source definition. Pixelsrc supports two transform systems designed for different use cases.
When to Use Which
| Use Case | System | Example |
|---|---|---|
| Keyframe animations | CSS Transforms | "transform": "rotate(90deg) scale(2)" |
| Derived sprites | Op-style Transforms | "transform": [{"op": "mirror-h"}] |
| Geometric only (rotate, flip, scale) | Either | Context determines choice |
| Effects (dither, outline, shadow) | Op-style only | "transform": [{"op": "sel-out"}] |
Rule of thumb:
- In an
animationkeyframe → use CSS transform strings - In a
spritewithsource→ use op-style transform arrays
CSS Transforms (Keyframe Animations)
CSS transforms use familiar web syntax and are primarily used within keyframe animations. They're designed for pixel art with specific considerations for crisp rendering.
Syntax
{
"type": "animation",
"name": "example",
"keyframes": {
"0%": { "sprite": "star", "transform": "translate(0, 0) rotate(0deg)" },
"100%": { "sprite": "star", "transform": "translate(10, -5) rotate(90deg)" }
},
"duration": "1s"
}
Multiple transforms can be combined in a single string, separated by spaces.
Transform Functions
| Function | Syntax | Description |
|---|---|---|
translate | translate(x, y) | Move by x, y pixels |
rotate | rotate(deg) | Rotate clockwise |
scale | scale(n) or scale(x, y) | Scale uniformly or non-uniformly |
flip | flip(x) or flip(y) | Horizontal or vertical flip |
translate(x, y)
Translate Transform
Position offset using translate(x, y), translateX(x), translateY(y).
{"type": "palette", "name": "arrow_pal", "colors": {"{_}": "#00000000", "{a}": "#FF0000"}}
{"type": "sprite", "name": "arrow_right", "palette": "arrow_pal", "size": [2, 2], "regions": {"a": {"rect": [0, 0, 2, 2]}}}
{"type": "sprite", "name": "arrow_base", "palette": "arrow_pal", "size": [2, 2], "regions": {"a": {"rect": [0, 0, 2, 2]}}}
{"type": "animation", "name": "slide_right", "duration": "500ms", "keyframes": {"0%": {"sprite": "arrow_base", "transform": "translate(0, 0)"}, "100%": {"sprite": "arrow_right", "transform": "translate(8px, 0)"}}}
{"type": "animation", "name": "slide_down", "duration": "500ms", "keyframes": {"0%": {"sprite": "arrow_base", "transform": "translateY(0)"}, "100%": {"sprite": "arrow_base", "transform": "translateY(4px)"}}}
{"type": "animation", "name": "slide_diagonal", "duration": "500ms", "keyframes": {"0%": {"sprite": "arrow_base", "transform": "translate(0, 0)"}, "50%": {"sprite": "arrow_base", "transform": "translate(4px, 4px)"}, "100%": {"sprite": "arrow_base", "transform": "translate(8px, 8px)"}}}
Move the sprite by the specified pixel offset.
"transform": "translate(10, 5)" // Move right 10, down 5
"transform": "translate(-5, 0)" // Move left 5
"transform": "translate(10px, 5px)" // Optional px suffix
| Parameter | Type | Description |
|---|---|---|
x | integer | Horizontal offset (positive = right) |
y | integer | Vertical offset (positive = down, optional, defaults to 0) |
rotate(deg)
Rotate Transform
Rotation using rotate(deg) - pixel art supports 90, 180, 270 degrees.
{"type": "palette", "name": "shape_pal", "colors": {"{_}": "#00000000", "{s}": "#00FF00"}}
{"type": "sprite", "name": "L_shape", "palette": "shape_pal", "size": [2, 2], "regions": {"s": {"points": [[0, 0], [0, 1], [1, 1]]}}}
{"type": "sprite", "name": "arrow_up", "palette": "shape_pal", "size": [2, 2], "regions": {"s": {"rect": [0, 0, 2, 2]}}}
{"type": "animation", "name": "rotate_90", "duration": "500ms", "keyframes": {"0%": {"sprite": "L_shape", "transform": "rotate(0deg)"}, "100%": {"sprite": "L_shape", "transform": "rotate(90deg)"}}}
{"type": "animation", "name": "rotate_180", "duration": "500ms", "keyframes": {"0%": {"sprite": "L_shape", "transform": "rotate(0deg)"}, "100%": {"sprite": "L_shape", "transform": "rotate(180deg)"}}}
{"type": "animation", "name": "rotate_270", "duration": "500ms", "keyframes": {"0%": {"sprite": "L_shape", "transform": "rotate(0deg)"}, "100%": {"sprite": "L_shape", "transform": "rotate(270deg)"}}}
{"type": "animation", "name": "spin_full", "duration": "1s", "keyframes": {"0%": {"sprite": "arrow_up", "transform": "rotate(0deg)"}, "25%": {"sprite": "arrow_up", "transform": "rotate(90deg)"}, "50%": {"sprite": "arrow_up", "transform": "rotate(180deg)"}, "75%": {"sprite": "arrow_up", "transform": "rotate(270deg)"}, "100%": {"sprite": "arrow_up", "transform": "rotate(360deg)"}}}
Rotate the sprite clockwise by the specified angle.
"transform": "rotate(90deg)" // Quarter turn clockwise
"transform": "rotate(180)" // Half turn (deg suffix optional)
"transform": "rotate(270deg)" // Three-quarter turn
Pixel Art Considerations:
For crisp pixel art, use 90-degree increments (0, 90, 180, 270). Other angles work but may produce anti-aliased edges:
| Angle | Result |
|---|---|
| 0, 90, 180, 270 | Pixel-perfect rotation |
| 45, 135, 225, 315 | Diagonal, some blurring |
| Other values | Arbitrary rotation with anti-aliasing |
scale(n) or scale(x, y)
Scale Transform
Scaling using scale(s), scale(x, y), scaleX(x), scaleY(y).
{"type": "palette", "name": "scale_pal", "colors": {"{_}": "#00000000", "{d}": "#0000FF"}}
{"type": "sprite", "name": "dot", "palette": "scale_pal", "size": [1, 1], "regions": {"d": {"points": [[0, 0]]}}}
{"type": "sprite", "name": "square", "palette": "scale_pal", "size": [2, 2], "regions": {"d": {"rect": [0, 0, 2, 2]}}}
{"type": "animation", "name": "scale_up", "duration": "500ms", "keyframes": {"0%": {"sprite": "dot", "transform": "scale(1)"}, "100%": {"sprite": "dot", "transform": "scale(4)"}}}
{"type": "animation", "name": "scale_xy", "duration": "500ms", "keyframes": {"0%": {"sprite": "square", "transform": "scale(1, 1)"}, "50%": {"sprite": "square", "transform": "scale(2, 1)"}, "100%": {"sprite": "square", "transform": "scale(2, 2)"}}}
{"type": "animation", "name": "scale_x_only", "duration": "500ms", "keyframes": {"0%": {"sprite": "square", "transform": "scaleX(1)"}, "100%": {"sprite": "square", "transform": "scaleX(3)"}}}
{"type": "animation", "name": "scale_y_only", "duration": "500ms", "keyframes": {"0%": {"sprite": "square", "transform": "scaleY(1)"}, "100%": {"sprite": "square", "transform": "scaleY(3)"}}}
{"type": "animation", "name": "pulse_scale", "duration": "500ms", "keyframes": {"0%": {"sprite": "dot", "transform": "scale(1)", "opacity": 1.0}, "50%": {"sprite": "dot", "transform": "scale(2)", "opacity": 0.6}, "100%": {"sprite": "dot", "transform": "scale(1)", "opacity": 1.0}}}
Scale the sprite uniformly or non-uniformly.
"transform": "scale(2)" // Double size (uniform)
"transform": "scale(2, 1)" // Double width only
"transform": "scale(1, 0.5)" // Half height
| Parameter | Type | Description |
|---|---|---|
n | float | Uniform scale factor (must be positive) |
x, y | float | Non-uniform scale factors |
Alternative syntax:
"transform": "scaleX(2)" // Scale width only
"transform": "scaleY(0.5)" // Scale height only
Pixel Art Considerations:
Integer scale factors (2, 3, 4) maintain pixel-perfect appearance. Fractional scales may blur:
| Scale | Result |
|---|---|
| 1, 2, 3, 4... | Pixel-perfect scaling |
| 1.5, 2.5... | Blended pixels |
| 0.5, 0.25... | Reduced resolution |
flip(x) or flip(y)
Flip Transform
Flipping sprites using scaleX(-1) and scaleY(-1).
{"type": "palette", "name": "flip_pal", "colors": {"{_}": "#00000000", "{f}": "#FF00FF"}}
{"type": "sprite", "name": "face_right", "palette": "flip_pal", "size": [2, 2], "regions": {"f": {"rect": [0, 0, 2, 2]}}}
{"type": "sprite", "name": "arrow_left", "palette": "flip_pal", "size": [2, 2], "regions": {"f": {"rect": [0, 0, 2, 2]}}}
{"type": "animation", "name": "flip_horizontal", "duration": "500ms", "keyframes": {"0%": {"sprite": "face_right", "transform": "scaleX(1)"}, "100%": {"sprite": "face_right", "transform": "scaleX(-1)"}}}
{"type": "animation", "name": "flip_vertical", "duration": "500ms", "keyframes": {"0%": {"sprite": "face_right", "transform": "scaleY(1)"}, "100%": {"sprite": "face_right", "transform": "scaleY(-1)"}}}
{"type": "animation", "name": "flip_both", "duration": "500ms", "keyframes": {"0%": {"sprite": "face_right", "transform": "scale(1, 1)"}, "50%": {"sprite": "face_right", "transform": "scale(-1, 1)"}, "100%": {"sprite": "face_right", "transform": "scale(-1, -1)"}}}
{"type": "animation", "name": "mirror_walk", "duration": "1s", "keyframes": {"0%": {"sprite": "arrow_left", "transform": "translate(0, 0) scaleX(1)"}, "50%": {"sprite": "arrow_left", "transform": "translate(8px, 0) scaleX(1)"}, "51%": {"sprite": "arrow_left", "transform": "translate(8px, 0) scaleX(-1)"}, "100%": {"sprite": "arrow_left", "transform": "translate(0, 0) scaleX(-1)"}}}
Mirror the sprite horizontally or vertically.
"transform": "flip(x)" // Horizontal mirror
"transform": "flip(y)" // Vertical mirror
"transform": "flip(x) flip(y)" // Both axes
Alternative syntax:
"transform": "flipX()" // Same as flip(x)
"transform": "flipY()" // Same as flip(y)
"transform": "flip(h)" // Horizontal (alias)
"transform": "flip(v)" // Vertical (alias)
Flipping is always pixel-perfect with no quality loss.
Transform Order
Transforms apply in CSS order: translate → rotate → scale → flip.
"transform": "translate(10, 0) rotate(90deg) scale(2)"
This translates first, then rotates, then scales.
Complete Examples
Spinning animation:
{"type": "animation", "name": "spin", "keyframes": {"0%": {"sprite": "star", "transform": "rotate(0deg)"}, "100%": {"sprite": "star", "transform": "rotate(360deg)"}}, "duration": 1000, "timing_function": "linear"}
Pulsing scale effect:
{"type": "animation", "name": "pulse", "keyframes": {"0%": {"sprite": "star", "transform": "scale(1)", "opacity": 1.0}, "50%": {"sprite": "star", "transform": "scale(1.5)", "opacity": 0.5}, "100%": {"sprite": "star", "transform": "scale(1)", "opacity": 1.0}}, "duration": "2s", "timing_function": "ease-in-out"}
Bouncing with translation:
{"type": "animation", "name": "bounce", "keyframes": {"0%": {"sprite": "ball", "transform": "translate(0, 0)"}, "50%": {"sprite": "ball", "transform": "translate(0, -10)"}, "100%": {"sprite": "ball", "transform": "translate(0, 0)"}}, "duration": "500ms", "timing_function": "ease-in-out"}
Flip on hover (walk cycle):
{"type": "sprite", "name": "walk_left", "source": "walk_right", "transform": [{"op": "mirror-h"}]}
Op-style Transforms (Derived Sprites)
Create new sprites derived from existing ones using transforms. These are specified via the transform array on a sprite definition.
Note: Op-style transforms are for sprite derivation, not animations. For animated transforms, see CSS Transforms above.
String Syntax (Simple Operations)
For common geometric operations, use string syntax:
{"type": "sprite", "name": "hero_left", "source": "hero_right", "transform": ["mirror-h"]}
{"type": "sprite", "name": "hero_down", "source": "hero_right", "transform": ["rotate:90"]}
{"type": "sprite", "name": "hero_big", "source": "hero", "transform": ["scale:2,2"]}
{"type": "sprite", "name": "hero_shadow", "source": "hero", "transform": ["shadow:1,1:{shadow}"]}
| Operation | String Syntax | Description |
|---|---|---|
| Mirror H | "mirror-h" | Flip horizontally (left-right) |
| Mirror V | "mirror-v" | Flip vertically (top-bottom) |
| Rotate | "rotate:90" | Rotate 90°, 180°, or 270° clockwise |
| Scale | "scale:2,2" | Scale by X,Y factors |
| Shift | "shift:1,1" | Shift pixels by X,Y offset |
| Shadow | "shadow:1,1:{token}" | Add drop shadow at offset with token |
| Sel-out | "sel-out" or "sel-out:{fallback}" | Selective outline |
Aliases: flip-h = mirror-h, flip-v = mirror-v, rot = rotate
Object Syntax (Advanced Operations)
For operations with multiple parameters, use object syntax:
{
"type": "sprite",
"name": "hero_outlined",
"source": "hero",
"transform": [
{"op": "operation_name", ...options}
]
}
Transforms are applied in array order.
Effect Transforms
Effect transforms modify sprite appearance through arrays of operations.
Dithering Patterns
Apply dithering patterns for gradients, transparency effects, and texture.
{
"type": "sprite",
"name": "gradient",
"source": "solid",
"transform": [
{"op": "dither", "pattern": "checker", "tokens": ["{dark}", "{light}"], "threshold": 0.5}
]
}
Dither Fields
| Field | Required | Default | Description |
|---|---|---|---|
op | Yes | - | Must be "dither" |
pattern | Yes | - | Dither pattern name |
tokens | Yes | - | Two-element array [dark, light] |
threshold | No | 0.5 | Blend threshold (0.0-1.0) |
seed | No | auto | Random seed for noise pattern |
Built-in Patterns
| Pattern | Description |
|---|---|
checker | 2x2 checkerboard |
ordered-2x2 | 2x2 Bayer matrix (4 levels) |
ordered-4x4 | 4x4 Bayer matrix (16 levels) |
ordered-8x8 | 8x8 Bayer matrix (64 levels) |
diagonal | Diagonal line pattern |
horizontal | Horizontal line pattern |
vertical | Vertical line pattern |
noise | Random dither (seeded) |
Gradient Dither
Create smooth gradients across the sprite:
{
"op": "dither-gradient",
"direction": "vertical",
"from": "{sky_light}",
"to": "{sky_dark}",
"pattern": "ordered-4x4"
}
| Field | Required | Default | Description |
|---|---|---|---|
op | Yes | - | Must be "dither-gradient" |
direction | Yes | - | "vertical", "horizontal", or "radial" |
from | Yes | - | Starting color token |
to | Yes | - | Ending color token |
pattern | No | "ordered-4x4" | Dither pattern to use |
Selective Outline (Sel-out)
Selective outline varies the outline color based on the adjacent fill color, creating softer, more natural edges.
{
"transform": [
{"op": "sel-out", "fallback": "{outline}"}
]
}
Sel-out Fields
| Field | Required | Default | Description |
|---|---|---|---|
op | Yes | - | Must be "sel-out" |
fallback | No | "{_}" | Default outline color |
auto_darken | No | 0.3 | Auto-darken factor (0.0-1.0) |
mapping | No | auto | Explicit fill→outline mapping |
Auto-Darken Mode
By default, sel-out automatically darkens each fill color to create its outline:
{"op": "sel-out", "auto_darken": 0.3}
Skin-colored pixels get a darker skin outline, hair pixels get a darker hair outline, etc.
Explicit Mapping
Define exactly which outline color to use for each fill:
{
"op": "sel-out",
"mapping": {
"{skin}": "{skin_dark}",
"{hair}": "{hair_dark}",
"*": "{outline}"
}
}
The * key is the fallback for any unspecified fill colors.
Squash & Stretch
Deform sprites for impact and bounce effects. Classic animation technique.
{
"transform": [
{"op": "squash", "amount": 0.3}
]
}
Squash/Stretch Fields
| Field | Required | Default | Description |
|---|---|---|---|
op | Yes | - | "squash" or "stretch" |
amount | Yes | - | Deformation amount (0.0-1.0) |
anchor | No | "center" | Transform anchor point |
preserve_area | No | true | Maintain sprite area |
Anchor Points
| Value | Description |
|---|---|
"center" | Center of sprite |
"bottom" | Bottom center |
"top" | Top center |
[x, y] | Custom coordinates |
Squash vs Stretch
- Squash: Compress vertically, expand horizontally (landing, impact)
- Stretch: Expand vertically, compress horizontally (jumping, anticipation)
{"type": "sprite", "name": "ball_land", "source": "ball", "transform": [
{"op": "squash", "amount": 0.4, "anchor": "bottom"}
]}
{"type": "sprite", "name": "ball_jump", "source": "ball", "transform": [
{"op": "stretch", "amount": 0.3, "anchor": "bottom"}
]}
Chaining Transforms
Apply multiple transforms in sequence:
{
"type": "sprite",
"name": "hero_processed",
"source": "hero",
"transform": [
{"op": "sel-out", "auto_darken": 0.25},
{"op": "dither", "pattern": "checker", "tokens": ["{shadow}", "{_}"], "threshold": 0.3}
]
}
Transforms apply in array order: first sel-out, then dither.
Complete Example
{"type": "palette", "name": "character", "colors": {
"{_}": "#00000000",
"{skin}": "#FFCC99",
"{skin_dark}": "#CC9966",
"{hair}": "#8B4513",
"{hair_dark}": "#5C2E0A",
"{outline}": "#000000"
}}
{"type": "sprite", "name": "hero_raw", "size": [7, 5], "palette": "character", "regions": {
"hair": {"union": [{"rect": [2, 0, 3, 1]}, {"rect": [1, 1, 5, 1]}], "z": 0},
"skin": {"rect": [1, 2, 5, 2], "z": 0}
}}
{"type": "sprite", "name": "hero", "source": "hero_raw", "transform": [
{"op": "sel-out", "mapping": {
"{skin}": "{skin_dark}",
"{hair}": "{hair_dark}",
"*": "{outline}"
}}
]}
The hero sprite has automatic selective outlining based on the fill colors.
Use Cases
Retro-Style Gradients
{"op": "dither-gradient", "direction": "vertical", "from": "{sky_top}", "to": "{sky_bottom}", "pattern": "ordered-4x4"}
Soft Shadows
{"op": "dither", "pattern": "ordered-2x2", "tokens": ["{shadow}", "{_}"], "threshold": 0.6}
Impact Effects
{"op": "squash", "amount": 0.5, "anchor": "bottom", "preserve_area": true}
Professional Outlines
{"op": "sel-out", "auto_darken": 0.3}
Includes
The include system allows you to reference palettes from external files. This enables sharing color schemes across multiple Pixelsrc files and organizing large projects into modular components.
Syntax
Use the @include: prefix followed by a file path:
{
type: "sprite",
name: "hero",
size: [8, 8],
palette: "@include:shared/colors.pxl",
regions: { ... },
}
How It Works
When Pixelsrc encounters an @include: palette reference:
- Resolves the path relative to the current file's directory
- Opens and parses the included file
- Extracts the first palette object from that file
- Uses that palette for the sprite
Path Resolution
Paths are resolved relative to the file containing the @include: reference:
project/
├── sprites/
│ └── hero.pxl ← Contains @include:../palettes/hero.pxl
└── palettes/
└── hero.pxl ← Palette file
Extension Auto-Detection
If the specified file doesn't exist, Pixelsrc tries alternate extensions:
- Exact path as specified
- Path with
.pxlextension - Path with
.jsonlextension
// All of these work if "colors.pxl" exists:
"palette": "@include:colors"
"palette": "@include:colors.pxl"
Example
shared/palette.pxl
{
type: "palette",
name: "game_colors",
colors: {
_: "transparent",
skin: "#FFCC99",
hair: "#8B4513",
outline: "#000000",
},
}
sprites/hero.pxl
{
type: "sprite",
name: "hero",
size: [6, 4],
palette: "@include:../shared/palette.pxl",
regions: {
hair: { rect: [2, 0, 2, 2], z: 0 },
outline: {
union: [
{ points: [[0, 2], [5, 2]] },
{ rect: [1, 3, 4, 1] },
],
z: 0,
},
skin: { rect: [1, 2, 4, 1], z: 1 },
},
}
Use Cases
Shared Color Schemes
Maintain a single source of truth for colors across multiple sprite files:
assets/
├── palettes/
│ ├── characters.pxl
│ ├── environment.pxl
│ └── ui.pxl
└── sprites/
├── hero.pxl ← @include:../palettes/characters.pxl
├── enemy.pxl ← @include:../palettes/characters.pxl
└── button.pxl ← @include:../palettes/ui.pxl
Theme Variations
Create different color themes by swapping include paths:
// day_scene.pxl
{
type: "sprite",
name: "tree",
size: [8, 12],
palette: "@include:palettes/day.pxl",
regions: { ... },
}
// night_scene.pxl
{
type: "sprite",
name: "tree",
size: [8, 12],
palette: "@include:palettes/night.pxl",
regions: { ... },
}
Library Palettes
Reference palettes from a central library:
{
type: "sprite",
name: "character",
size: [16, 16],
palette: "@include:../../lib/palettes/fantasy.pxl",
regions: { ... },
}
Error Handling
File Not Found
If the include file doesn't exist:
- Lenient mode: Error (cannot substitute a missing palette)
- Strict mode: Error
No Palette in File
If the included file doesn't contain a palette object:
- Error: "No palette found in included file"
The included file must contain at least one {"type": "palette", ...} object.
Circular Includes
Pixelsrc detects circular include chains:
a.pxl includes b.pxl
b.pxl includes c.pxl
c.pxl includes a.pxl ← Circular include error
Comparison with Other Palette Methods
| Method | Best For |
|---|---|
| Inline palette | Simple, self-contained sprites |
| Named palette | Multiple sprites in the same file |
Built-in (@name) | Quick prototyping with standard palettes |
Include (@include:) | Shared palettes across files |
Complete Example
palettes/retro.pxl
{
type: "palette",
name: "retro",
colors: {
_: "transparent",
bg: "#0F380F",
light: "#9BBC0F",
mid: "#8BAC0F",
dark: "#306230",
},
}
sprites/player.pxl
{
type: "sprite",
name: "player",
size: [4, 4],
palette: "@include:../palettes/retro.pxl",
regions: {
light: { rect: [1, 0, 2, 1], z: 0 },
mid: {
union: [
{ points: [[0, 1], [3, 1]] },
{ rect: [0, 2, 4, 1] },
],
z: 0,
},
dark: {
union: [
{ rect: [1, 1, 2, 1] },
{ rect: [1, 3, 2, 1] },
],
z: 1,
},
},
}
sprites/enemy.pxl
{
type: "sprite",
name: "enemy",
size: [4, 4],
palette: "@include:../palettes/retro.pxl",
regions: {
dark: { points: [[0, 0], [3, 0]], z: 0 },
mid: {
union: [
{ rect: [1, 1, 2, 1] },
{ rect: [0, 2, 4, 1] },
{ rect: [1, 3, 2, 1] },
],
z: 0,
},
light: { rect: [1, 2, 2, 1], z: 1 },
},
}
Both sprites share the same palette, and changing retro.pxl updates all sprites that include it.
CLI Overview
The pxl command-line interface provides tools for working with Pixelsrc files. Commands fall into several categories:
Core Workflow
| Command | Description |
|---|---|
| render | Render sprites to PNG, GIF, or atlas formats |
| import | Convert PNG images to Pixelsrc format |
| validate | Check files for errors and common mistakes |
| fmt | Format files for consistent style |
| build | Build all assets according to pxl.toml |
Authoring Tools
| Command | Description |
|---|---|
| new | Create new assets from templates |
| init | Initialize a new Pixelsrc project |
| sketch | Create sprites from simple text grids |
Inspection & Debugging
| Command | Description |
|---|---|
| show | Display sprites with colored terminal output |
| grid | Display grid with row/column coordinates |
| inline | Expand grid with column-aligned spacing |
| explain | Explain objects in human-readable format |
| diff | Compare sprites semantically |
| analyze | Extract corpus metrics from files |
AI Integration
| Command | Description |
|---|---|
| prime | Print format guide for AI context injection |
| prompts | Show GenAI prompt templates |
| suggest | Suggest fixes for common issues |
| alias | Extract repeated patterns into aliases |
Reference Data
| Command | Description |
|---|---|
| palettes | List and inspect built-in palettes |
Global Behavior
File Formats
The CLI supports two file formats:
.pxl- Human-readable Pixelsrc format.jsonl- JSON Lines format (legacy, still supported)
Both formats can contain palettes, sprites, animations, compositions, and other objects.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error (invalid input, missing file, etc.) |
| 2 | Validation failed (with --strict mode) |
See Exit Codes for the complete list.
Common Options
Many commands share common options:
--json- Output as JSON for scripting--strict- Treat warnings as errors--stdin- Read input from stdin
Quick Examples
# Render a sprite to PNG
pxl render sprite.pxl -o output.png
# Validate all files in a directory
pxl validate *.pxl
# Format files in place
pxl fmt *.pxl
# Preview a sprite in terminal
pxl show sprite.pxl
# Build a project
pxl build
# Get AI context for sprite generation
pxl prime --brief
render
Render sprites from a Pixelsrc file to PNG, GIF, or atlas formats.
Usage
pxl render [OPTIONS] <INPUT>
Arguments
| Argument | Description |
|---|---|
<INPUT> | Input file containing palette and sprite definitions (.pxl or .jsonl) |
Options
| Option | Description |
|---|---|
-o, --output <OUTPUT> | Output file or directory (see below) |
-s, --sprite <SPRITE> | Only render the sprite with this name |
-c, --composition <COMPOSITION> | Only render the composition with this name |
--scale <SCALE> | Scale output by integer factor (1-16, default: 1) |
--strict | Treat warnings as errors |
--gif | Output as animated GIF (requires animation in input) |
--spritesheet | Output as spritesheet (horizontal strip of all frames) |
--emoji | Output as emoji art to terminal (for quick preview) |
--animation <ANIMATION> | Select a specific animation by name |
--format <FORMAT> | Atlas format (see below) |
--max-size <MAX_SIZE> | Maximum atlas size (e.g., "512x512") |
--padding <PADDING> | Padding between sprites in atlas (pixels, default: 0) |
--power-of-two | Force power-of-two dimensions for atlas |
--nine-slice <WxH> | Render nine-slice sprite to target size (e.g., "64x32") |
Output Naming
If --output is omitted:
- Single sprite:
{input}_{sprite}.png - Multiple sprites:
{input}_{sprite}.pngfor each
If --output is a file:
- Single sprite: uses the exact filename
- Multiple sprites:
{output}_{sprite}.pngfor each
If --output ends with /:
- Each sprite is written as
{dir}/{sprite}.png
Atlas Formats
The --format option supports:
| Format | Description |
|---|---|
atlas | Generic JSON atlas |
atlas-aseprite | Aseprite-compatible JSON |
atlas-godot | Godot engine format |
atlas-unity | Unity sprite atlas |
atlas-libgdx | LibGDX texture atlas |
Examples
Basic Render Example
Render a simple sprite to PNG output.
{"type": "palette", "name": "simple", "colors": {"_": "#00000000", "b": "#4a90d9", "w": "#ffffff"}}
{"type": "sprite", "name": "icon", "size": [3, 3], "palette": "simple", "regions": {"b": {"union": [{"points": [[1, 0]]}, {"points": [[0, 1], [2, 1]]}, {"points": [[1, 2]]}], "z": 0}, "w": {"points": [[1, 1]], "z": 1}}}
pxl render icon.pxl -o icon.png
Basic rendering
# Render all sprites to PNG files
pxl render character.pxl
# Render to a specific output file
pxl render character.pxl -o hero.png
# Render a specific sprite
pxl render sprites.pxl --sprite hero -o hero.png
Scaling
Scaled Render Example
Render sprites at larger sizes with integer scaling.
{"type": "palette", "name": "pixel", "colors": {"_": "#00000000", "p": "#e43b44", "d": "#a82b3a"}}
{"type": "sprite", "name": "heart", "size": [5, 5], "palette": "pixel", "regions": {"p": {"union": [{"points": [[1, 0], [3, 0]]}, {"points": [[0, 1], [2, 1], [4, 1]]}, {"points": [[0, 2], [4, 2]]}, {"points": [[1, 3]]}, {"points": [[2, 4]]}], "z": 0}, "d": {"union": [{"points": [[1, 1], [3, 1]]}, {"rect": [1, 2, 3, 1]}, {"points": [[2, 3]]}], "z": 1}}}
pxl render heart.pxl --scale 4 -o heart_4x.png
# Scale up 4x (16x16 becomes 64x64)
pxl render character.pxl --scale 4
# Maximum scale factor is 16
pxl render character.pxl --scale 16
Animated output
# Render animation as GIF
pxl render animation.pxl --gif -o walk.gif
# Render specific animation
pxl render character.pxl --animation walk --gif -o walk.gif
# Render as spritesheet (horizontal strip)
pxl render animation.pxl --spritesheet -o walk-strip.png
Quick preview
# Preview as emoji in terminal
pxl render character.pxl --emoji
Atlas generation
# Create a texture atlas
pxl render sprites.pxl --format atlas -o sprites-atlas.png
# Godot-compatible atlas with padding
pxl render sprites.pxl --format atlas-godot --padding 2 -o atlas.png
# Force power-of-two dimensions
pxl render sprites.pxl --format atlas --power-of-two -o atlas.png
# Limit maximum atlas size
pxl render sprites.pxl --format atlas --max-size 512x512 -o atlas.png
Strict mode
# Fail on any warnings
pxl render character.pxl --strict
Nine-slice rendering
Nine-slice (9-patch) sprites are scalable UI elements where corners stay fixed while
edges and center stretch. The sprite must have a nine_slice attribute defined.
# Render a button sprite stretched to 64x32 pixels
pxl render button.pxl --nine-slice 64x32 -o button_wide.png
# Render at 128x48 for a dialog box
pxl render panel.pxl --nine-slice 128x48 -o panel_large.png
The nine_slice attribute on the sprite defines the border widths:
{"nine_slice": {"left": 4, "right": 4, "top": 4, "bottom": 4}}
See the format specification for complete details on the nine-slice format.
See Also
- import - Convert PNG images to Pixelsrc format
- show - Preview sprites in terminal
- build - Build multiple assets according to project config
import
Import a PNG image and convert it to Pixelsrc format.
Usage
pxl import [OPTIONS] <INPUT>
Arguments
| Argument | Description |
|---|---|
<INPUT> | Input PNG file to convert |
Options
| Option | Description |
|---|---|
-o, --output <OUTPUT> | Output file (default: {input}.jsonl, use .pxl extension for new format) |
--max-colors <MAX_COLORS> | Maximum number of colors in the palette (2-256, default: 16) |
-n, --name <NAME> | Name for the generated sprite (default: derived from filename) |
Description
The import command analyzes a PNG image and generates a Pixelsrc file containing:
- A palette with the detected colors
- A sprite with tokens referencing the palette
This is useful for converting existing pixel art into the Pixelsrc format for editing or animation.
Examples
Import Workflow
Convert a PNG image to Pixelsrc format. The import command detects colors and creates a palette and sprite definition.
# Input: hero.png (16x16 pixel art)
pxl import hero.png -o hero.pxl
# Output: hero.pxl contains:
# {"type": "palette", "name": "hero", "colors": {"_": "#00000000", ...}}
# {"type": "sprite", "name": "hero", "size": [16, 16], "palette": "hero", "regions": {...}}
Basic import
# Import with default settings
pxl import hero.png
# Creates hero.jsonl with detected colors and sprite data
Custom output format
# Output as .pxl format (preferred)
pxl import hero.png -o hero.pxl
# Output to specific location
pxl import hero.png -o assets/sprites/hero.pxl
Controlling colors
Color Quantization
Limit the palette size during import for retro-style constraints.
# Limit to 4 colors (Game Boy style)
pxl import hero.png --max-colors 4
# Result: palette has at most 4 colors
# Colors are quantized to fit the constraint
# Limit to 4 colors (Game Boy style)
pxl import hero.png --max-colors 4
# Allow more colors for detailed sprites
pxl import detailed.png --max-colors 64
Custom sprite name
# Name the sprite instead of using filename
pxl import player-idle-frame1.png --name player_idle
Color Quantization
When the source image has more colors than --max-colors, the importer will reduce the color count through quantization. This may result in slight color differences from the original.
For best results:
- Use source images that are already limited to your target palette
- Import at the original resolution (don't scale up before importing)
- Review the generated palette and adjust colors if needed
Tips
- Import works best with clean pixel art (no anti-aliasing)
- Transparent pixels are preserved
- Very similar colors may be merged during import
- Use
pxl showafter import to preview the result
See Also
- render - Render sprites back to PNG
- palettes - Use built-in palettes instead of importing colors
- new - Create new sprites from templates
validate
Validate Pixelsrc files for errors and common mistakes.
Usage
pxl validate [OPTIONS] [FILES]...
Arguments
| Argument | Description |
|---|---|
[FILES]... | Files to validate (omit if using --stdin) |
Options
| Option | Description |
|---|---|
--stdin | Read input from stdin |
--strict | Treat warnings as errors |
--json | Output as JSON |
Description
The validate command checks Pixelsrc files for:
- Syntax errors
- Missing palette references
- Invalid color values
- Undefined tokens in regions
- Invalid shape coordinates
- Other structural issues
By default, the command distinguishes between errors (which cause a non-zero exit) and warnings (informational only). Use --strict to treat all issues as errors.
Examples
Valid File Example
A properly structured file passes validation without errors.
{"type": "palette", "name": "valid", "colors": {"_": "#00000000", "x": "#ff0000"}}
{"type": "sprite", "name": "dot", "size": [1, 1], "palette": "valid", "regions": {"x": {"rect": [0, 0, 1, 1], "z": 0}}}
pxl validate dot.pxl
# ✓ dot.pxl: valid
Validation Error Example
Files with errors show detailed diagnostic messages.
{"type": "sprite", "name": "broken", "size": [3, 1], "palette": "missing", "regions": {"x": {"rect": [0, 0, 1, 1], "z": 0}, "y": {"rect": [1, 0, 1, 1], "z": 0}, "z": {"rect": [2, 0, 1, 1], "z": 0}}}
pxl validate broken.pxl
# error: sprite 'broken' references undefined palette 'missing'
# error: undefined tokens in regions: x, y, z
Basic validation
# Validate a single file
pxl validate sprite.pxl
# Validate multiple files
pxl validate *.pxl
# Validate with glob pattern
pxl validate assets/**/*.pxl
Strict mode
# Fail on any warnings (useful in CI)
pxl validate --strict sprite.pxl
JSON output
# Get machine-readable output
pxl validate --json sprite.pxl
# Pipe to jq for filtering
pxl validate --json sprite.pxl | jq '.errors'
Stdin input
# Validate piped content
cat sprite.pxl | pxl validate --stdin
# Validate generated content
pxl sketch -n test < input.txt | pxl validate --stdin
Exit Codes
| Code | Meaning |
|---|---|
| 0 | All files valid (no errors) |
| 1 | Validation errors found |
| 2 | Validation warnings found (only with --strict) |
Common Errors
Undefined token
Error: undefined token 'xyz' in regions
The regions reference a token that isn't defined in the palette.
Invalid shape coordinates
Error: rect extends beyond sprite bounds at [10, 0, 5, 5]
Shape coordinates must fall within the sprite's defined size.
Missing palette
Error: sprite 'hero' references undefined palette 'colors'
The sprite uses a palette that doesn't exist in the file or includes.
See Also
- fmt - Format files for consistent style
- suggest - Get fix suggestions for errors
- explain - Get detailed explanations of objects
fmt
Format Pixelsrc files for consistent, readable style.
Usage
pxl fmt [OPTIONS] <FILES>...
Arguments
| Argument | Description |
|---|---|
<FILES>... | Input file(s) to format |
Options
| Option | Description |
|---|---|
--check | Check formatting without writing (exit 1 if changes needed) |
--stdout | Write to stdout instead of in-place |
Description
The fmt command standardizes the formatting of Pixelsrc files:
- Consistent indentation
- Aligned region definitions
- Normalized whitespace
- Ordered object fields
By default, files are modified in place. Use --check to verify formatting without making changes, or --stdout to preview the result.
Examples
Formatting Example
The formatter standardizes spacing and alignment for consistent, readable files.
# Before formatting (compact):
{"type":"sprite","name":"icon","size":[2,2],"palette":{"x":"#ff0000"},"regions":{"x":{"rect":[0,0,2,2],"z":0}}}
# After pxl fmt:
{"type": "sprite", "name": "icon", "size": [2, 2], "palette": {"x": "#ff0000"}, "regions": {"x": {"rect": [0, 0, 2, 2], "z": 0}}}
pxl fmt sprite.pxl
Format files in place
# Format a single file
pxl fmt sprite.pxl
# Format multiple files
pxl fmt *.pxl
# Format all files in a directory tree
pxl fmt assets/**/*.pxl
Check mode (CI integration)
Check Mode for CI
Verify formatting without modifying files—useful for CI pipelines.
# Check if files are formatted
pxl fmt --check sprite.pxl
# Exit code 0: already formatted
# Exit code 1: needs formatting
# Check if files are formatted (exit 1 if not)
pxl fmt --check sprite.pxl
# In a CI pipeline
pxl fmt --check *.pxl || echo "Run 'pxl fmt' to fix formatting"
Preview changes
# See formatted output without modifying file
pxl fmt --stdout sprite.pxl
# Diff against current file
pxl fmt --stdout sprite.pxl | diff sprite.pxl -
Formatting Rules
The formatter applies these conventions:
Region alignment
Shape arrays and union blocks are consistently formatted:
regions: {
body: { rect: [0, 0, 8, 8], z: 0 },
detail: {
union: [
{ rect: [1, 1, 2, 2] },
{ rect: [5, 1, 2, 2] },
],
z: 1,
},
}
Whitespace
- Single space between tokens
- No trailing whitespace
- Single newline at end of file
Field ordering
Object fields are ordered consistently:
- Type/kind fields first
- Name/identifier
- Content fields
- Metadata fields last
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success (files formatted or already formatted) |
| 1 | Files need formatting (with --check) |
See Also
explain
Explain sprites and other objects in human-readable format.
Usage
pxl explain [OPTIONS] <INPUT>
Arguments
| Argument | Description |
|---|---|
<INPUT> | Input file containing Pixelsrc objects |
Options
| Option | Description |
|---|---|
-n, --name <NAME> | Name of specific object to explain (sprite, palette, etc.) |
--json | Output as JSON |
Description
The explain command provides detailed, human-readable descriptions of Pixelsrc objects including:
- Dimensions and size information
- Color usage statistics
- Animation frame counts
- Composition structure
- And more
This is useful for understanding complex sprites or debugging issues.
Examples
Explain all objects
# Explain everything in a file
pxl explain character.pxl
Explain specific object
# Explain just the hero sprite
pxl explain character.pxl --name hero
# Explain a palette
pxl explain character.pxl --name colors
JSON output
# Get structured output
pxl explain character.pxl --json
# Filter with jq
pxl explain character.pxl --json | jq '.sprites[0].dimensions'
Sample Output
Sprite: hero
Dimensions: 16x16 pixels
Colors used: 5
- skin (tan): 42 pixels
- outline (black): 28 pixels
- hair (brown): 18 pixels
- shirt (blue): 32 pixels
- transparent: 136 pixels
Palette: colors
Colors: 8 defined
skin #E0A070
outline #000000
hair #8B4513
shirt #4169E1
...
Animation: walk
Frames: 4
Duration: 400ms total (100ms per frame)
Sprites: hero_walk_1, hero_walk_2, hero_walk_3, hero_walk_4
Use Cases
- Debugging: Understand why a sprite looks wrong
- Documentation: Generate sprite documentation automatically
- Analysis: Count colors, measure dimensions, audit animations
- Learning: Understand how existing sprites are structured
See Also
- analyze - Extract metrics from multiple files
- show - Visual preview in terminal
- diff - Compare two sprites
diff
Compare sprites semantically between two files.
Usage
pxl diff [OPTIONS] <FILE_A> <FILE_B>
Arguments
| Argument | Description |
|---|---|
<FILE_A> | First file to compare |
<FILE_B> | Second file to compare |
Options
| Option | Description |
|---|---|
--sprite <SPRITE> | Compare only a specific sprite by name |
--json | Output as JSON |
Description
The diff command performs a semantic comparison between Pixelsrc files, showing:
- Added, removed, or modified sprites
- Palette color changes
- Dimension changes
- Pixel-level differences within sprites
Unlike text diff, this command understands the structure of Pixelsrc files and provides meaningful comparisons.
Examples
Compare two files
# Compare all objects in two files
pxl diff before.pxl after.pxl
Compare specific sprite
# Compare only the hero sprite between versions
pxl diff v1/character.pxl v2/character.pxl --sprite hero
JSON output
# Get structured diff output
pxl diff before.pxl after.pxl --json
# Check if files are identical (empty diff)
pxl diff a.pxl b.pxl --json | jq '.changes | length'
Sample Output
Comparing before.pxl → after.pxl
Sprite 'hero':
Dimensions: unchanged (16x16)
Colors: 1 changed
- shirt: #4169E1 → #228B22 (blue → green)
Pixels: 32 modified (12.5%)
Sprite 'enemy':
Status: added in after.pxl
Dimensions: 16x16
Colors: 4
Palette 'colors':
Colors: 1 added
+ accent: #FFD700
Use Cases
- Version control: Understand what changed between commits
- Review: Check sprite modifications before merging
- Debugging: Find unexpected changes
- Testing: Verify that transformations produce expected results
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Comparison completed (even if differences found) |
| 1 | Error (file not found, invalid format, etc.) |
See Also
- validate - Check files for errors
- explain - Detailed explanation of single file
- analyze - Corpus-level metrics
suggest
Suggest fixes for Pixelsrc files (missing tokens, row completion).
Usage
pxl suggest [OPTIONS] [FILES]...
Arguments
| Argument | Description |
|---|---|
[FILES]... | Files to analyze (omit if using --stdin) |
Options
| Option | Description |
|---|---|
--stdin | Read input from stdin |
--json | Output as JSON |
--only <ONLY> | Only show a specific type of suggestion (token, row) |
Description
The suggest command analyzes Pixelsrc files and provides actionable suggestions for:
- Token suggestions: Likely intended tokens for typos or undefined references
- Row completion: Missing pixels to complete partial rows
This is particularly useful when working with AI-generated content that may have minor issues.
Examples
Get all suggestions
# Analyze a file and show suggestions
pxl suggest sprite.pxl
Filter by suggestion type
# Only show token suggestions (typos, undefined refs)
pxl suggest sprite.pxl --only token
# Only show row completion suggestions
pxl suggest sprite.pxl --only row
JSON output
# Get structured suggestions
pxl suggest sprite.pxl --json
# Apply suggestions programmatically
pxl suggest sprite.pxl --json | jq '.suggestions[] | .fix'
Stdin input
# Suggest fixes for piped content
cat sprite.pxl | pxl suggest --stdin
# Chain with AI generation
generate_sprite | pxl suggest --stdin
Sample Output
Analyzing sprite.pxl...
Token suggestions:
Line 5: 'blck' → did you mean 'black'?
Line 8: 'wht' → did you mean 'white'?
Row completion:
Line 12: row has 7 tokens, expected 8
Suggested: add 1x '_' (transparent) at end
Line 15: row has 6 tokens, expected 8
Suggested: add 2x 'black' at end (matches surrounding rows)
Use Cases
- AI output cleanup: Fix common AI generation errors
- Typo detection: Find and fix token typos
- Incomplete sprites: Complete partially-defined rows
- Learning: Understand common mistakes and fixes
See Also
- validate - Check files for errors
- fmt - Format files for consistency
- prime - Get AI context to prevent issues
show
Display sprites with colored terminal output using ANSI true-color.
Usage
pxl show [OPTIONS] <FILE>
Arguments
| Argument | Description |
|---|---|
<FILE> | Input file containing sprite definitions |
Options
| Option | Description |
|---|---|
--sprite <SPRITE> | Sprite name (if file contains multiple sprites) |
--animation <ANIMATION> | Animation name to show with onion skinning |
--frame <FRAME> | Frame index to display (for animations, default: 0) |
--onion <ONION> | Number of frames before/after to show as onion skin |
--onion-opacity <OPACITY> | Ghost frame opacity (0.0-1.0, default: 0.3) |
--onion-prev-color <COLOR> | Tint color for previous frames (default: #0000FF blue) |
--onion-next-color <COLOR> | Tint color for next frames (default: #00FF00 green) |
--onion-fade | Decrease opacity for frames farther from current |
-o, --output <OUTPUT> | Output file (PNG) for onion skin preview |
Description
The show command renders sprites directly in the terminal using ANSI true-color escape codes. This provides instant visual feedback without generating files.
For animations, onion skinning displays ghost frames before and after the current frame, helping visualize motion.
Examples
Basic preview
# Show a sprite in terminal
pxl show sprite.pxl
# Show a specific sprite from multi-sprite file
pxl show sprites.pxl --sprite hero
Animation preview
# Show first frame of animation
pxl show animation.pxl --animation walk
# Show specific frame
pxl show animation.pxl --animation walk --frame 2
Onion skinning
# Show with 2 frames of onion skin on each side
pxl show animation.pxl --animation walk --onion 2
# Custom onion skin colors
pxl show animation.pxl --animation walk --onion 2 \
--onion-prev-color "#FF0000" \
--onion-next-color "#00FF00"
# Fading onion skin (farther frames more transparent)
pxl show animation.pxl --animation walk --onion 3 --onion-fade
# Higher opacity ghost frames
pxl show animation.pxl --animation walk --onion 2 --onion-opacity 0.5
Export onion skin view
# Save onion skin preview as PNG
pxl show animation.pxl --animation walk --onion 2 -o preview.png
Terminal Requirements
This command requires a terminal that supports:
- ANSI true-color (24-bit color)
- Unicode block characters (▀, ▄)
Most modern terminals support this, including:
- iTerm2, Terminal.app (macOS)
- Windows Terminal
- GNOME Terminal, Konsole (Linux)
- VS Code integrated terminal
See Also
- grid - Show grid with coordinates
- render - Render to image files
- inline - Expand grid spacing for readability
analyze
Analyze Pixelsrc files and extract corpus metrics.
Usage
pxl analyze [OPTIONS] [FILES]...
Arguments
| Argument | Description |
|---|---|
[FILES]... | Files to analyze |
Options
| Option | Description |
|---|---|
--dir <DIR> | Directory to scan for .jsonl/.pxl files |
-r, --recursive | Include subdirectories when scanning a directory |
--format <FORMAT> | Output format: text or json (default: text) |
-o, --output <OUTPUT> | Write output to file instead of stdout |
Description
The analyze command extracts statistics and metrics from Pixelsrc files:
- Total file, sprite, and palette counts
- Dimension distributions
- Color usage patterns
- Token frequency analysis
- Animation statistics
This is useful for understanding a corpus of sprites, training data analysis, or project auditing.
Examples
Analyze single file
# Analyze one file
pxl analyze sprite.pxl
Analyze directory
# Analyze all files in a directory
pxl analyze --dir assets/sprites
# Include subdirectories
pxl analyze --dir assets --recursive
JSON output
# Get machine-readable output
pxl analyze --dir sprites --format json
# Save to file
pxl analyze --dir sprites --format json -o metrics.json
Analyze multiple files
# Analyze specific files
pxl analyze player.pxl enemy.pxl items.pxl
# Use glob patterns
pxl analyze *.pxl
Sample Output
Text format:
Corpus Analysis
===============
Files: 24
Sprites: 156
Palettes: 12
Animations: 28
Dimensions:
8x8: 42 (26.9%)
16x16: 89 (57.1%)
32x32: 18 (11.5%)
Other: 7 (4.5%)
Colors:
Average per sprite: 6.2
Most common:
black (156 sprites)
white (142 sprites)
skin (98 sprites)
Tokens:
Unique: 47
Most used: _ (transparent), black, white
JSON format:
{
"files": 24,
"sprites": 156,
"palettes": 12,
"animations": 28,
"dimensions": {
"8x8": 42,
"16x16": 89,
"32x32": 18,
"other": 7
},
"colors": {
"average_per_sprite": 6.2,
"most_common": ["black", "white", "skin"]
}
}
Use Cases
- Corpus analysis: Understand patterns in sprite collections
- Training data audit: Verify AI training data quality
- Project metrics: Track sprite counts and dimensions
- Documentation: Generate statistics for project READMEs
See Also
- explain - Detailed explanation of single file
- validate - Check files for errors
- diff - Compare two files
prime
Print Pixelsrc format guide for AI context injection.
Usage
pxl prime [OPTIONS]
Options
| Option | Description |
|---|---|
--brief | Print condensed version (~2000 tokens) |
--section <SECTION> | Print specific section: format, examples, tips, full |
Description
The prime command outputs documentation about the Pixelsrc format suitable for injecting into AI prompts. This helps AI models generate valid Pixelsrc content by providing format specifications, examples, and best practices.
The output is designed to fit within typical context windows while providing enough information for accurate generation.
Examples
Full context
# Print complete format guide
pxl prime
Brief version
# Condensed version (~2000 tokens)
pxl prime --brief
Specific sections
# Just the format specification
pxl prime --section format
# Just examples
pxl prime --section examples
# Just tips and best practices
pxl prime --section tips
Integration with AI tools
# Inject into prompt file
pxl prime --brief > context.txt
# Pipe to clipboard (macOS)
pxl prime --brief | pbcopy
# Use in a prompt template
echo "$(pxl prime --brief)
Create a 16x16 warrior sprite:" | ai-tool
Output Sections
Format Section
Documents the Pixelsrc syntax:
- Object types (palette, sprite, animation)
- Grid syntax and tokens
- Color definitions
- Special tokens (
_for transparency)
Examples Section
Provides complete, working examples:
- Simple sprite with inline palette
- Animation with multiple frames
- Composition combining sprites
Tips Section
Best practices for AI generation:
- Token naming conventions
- Grid consistency rules
- Common mistakes to avoid
- Validation recommendations
Token Estimates
| Version | Approximate Tokens |
|---|---|
| Full | ~5000 |
| Brief | ~2000 |
| Format only | ~1500 |
| Examples only | ~1000 |
| Tips only | ~500 |
Use Cases
- AI prompt engineering: Provide context for sprite generation
- Documentation: Generate format reference docs
- Training: Create training data documentation
- Integration: Build AI-powered sprite tools
See Also
- prompts - Template prompts for sprite generation
- suggest - Fix AI-generated content issues
- validate - Validate generated content
prompts
Show GenAI prompt templates for sprite generation.
Usage
pxl prompts [TEMPLATE]
Arguments
| Argument | Description |
|---|---|
[TEMPLATE] | Template name to show. If omitted, lists all available templates |
Available Templates
| Template | Description |
|---|---|
character | Character sprite generation prompt |
item | Item and object sprite generation |
tileset | Tileset and terrain generation |
animation | Animation sequence generation |
Description
The prompts command provides ready-to-use prompt templates for generating Pixelsrc content with AI models. Each template includes:
- Clear instructions for the AI
- Format specifications
- Example output structure
- Common parameters to customize
Examples
List available templates
# Show all available templates
pxl prompts
Get specific template
# Get character sprite template
pxl prompts character
# Get animation template
pxl prompts animation
Use with AI tools
# Copy template to clipboard (macOS)
pxl prompts character | pbcopy
# Pipe to AI tool
pxl prompts character | ai-chat --model gpt-4
# Save template for editing
pxl prompts tileset > tileset-prompt.txt
Preview AI output quickly
# After generating sprite, preview in terminal
pxl show sprite.pxl -s character
# View with coordinates for debugging
pxl grid sprite.pxl -s character
# Get structural breakdown
pxl explain sprite.pxl -s character
Sample Template Output
# Character Sprite Generation
Generate a pixel art character sprite in Pixelsrc format.
## Requirements
- Size: 16x16 pixels
- Style: Retro game aesthetic
- Include: Front-facing idle pose
## Output Format
Create a palette with 4-8 colors, then a sprite referencing those colors.
Example structure:
palette: name: character_colors colors: outline: #000000 skin: #E0A070 ...
sprite: name: character palette: character_colors grid: _ _ _ _ ...
## Guidelines
- Use `_` for transparent pixels
- Keep outlines consistent (usually 1px black)
- Center the character in the grid
...
Use Cases
- Quick start: Get working prompts without writing from scratch
- Consistency: Ensure AI outputs match expected format
- Training: Understand effective prompt structure
- Integration: Build into AI-powered workflows
See Also
- prime - Format specification for context injection
- validate - Validate AI-generated output
- suggest - Fix common AI generation errors
palettes
List and inspect built-in palettes.
Usage
pxl palettes <COMMAND>
Subcommands
| Command | Description |
|---|---|
list | List all available built-in palettes |
show | Show details of a specific palette |
pxl palettes list
List all available built-in palettes.
pxl palettes list
Output:
Built-in palettes:
@gameboy
@nes
@pico8
@grayscale
@1bit
@dracula
pxl palettes show
Show details of a specific palette.
pxl palettes show <NAME>
Arguments
| Argument | Description |
|---|---|
<NAME> | Name of the palette to show (e.g., gameboy, pico8) |
Examples
# Show Game Boy palette
pxl palettes show gameboy
# Show PICO-8 palette
pxl palettes show pico8
Sample Output
Palette: gameboy
Colors: 4
darkest #0f380f
dark #306230
light #8bac0f
lightest #9bbc0f
Usage in sprites:
palette: @gameboy
Built-in Palettes
@gameboy
The classic Game Boy 4-color green palette.
| Token | Color | Hex |
|---|---|---|
| darkest | Dark green | #0f380f |
| dark | Medium green | #306230 |
| light | Light green | #8bac0f |
| lightest | Pale green | #9bbc0f |
@nes
NES-inspired color palette with 54 colors.
@pico8
The PICO-8 fantasy console 16-color palette.
@grayscale
8-level grayscale from black to white.
@1bit
Simple 2-color black and white palette.
@dracula
The popular Dracula dark theme colors.
Using Built-in Palettes
Reference built-in palettes with the @ prefix:
sprite:
name: player
palette: @gameboy
grid:
_ _ darkest darkest _ _
_ darkest light light darkest _
darkest light lightest lightest light darkest
See Also
- Format: Palette - Creating custom palettes
- Reference: Palettes - Complete palette reference
- new - Create sprites with built-in palettes
build
Build all assets according to pxl.toml configuration.
Synopsis
pxl build [OPTIONS]
Description
The build command processes all .pxl source files and generates output assets (PNG sprites, GIF animations, sprite atlases, and game engine exports). It reads configuration from pxl.toml to determine source locations, output directories, and export settings.
The build system supports:
- Incremental builds: Only rebuilds files that have changed
- Parallel builds: Uses multiple workers for faster processing
- Progress reporting: Shows build status in console or JSON format
- Watch mode: Monitors files and rebuilds on changes
- Game engine exports: Generates assets for Godot, Unity, and libGDX
Options
| Option | Description |
|---|---|
-o, --out <DIR> | Override output directory (default: from pxl.toml or build/) |
--src <DIR> | Override source directory (default: from pxl.toml or src/pxl/) |
-w, --watch | Watch for changes and rebuild automatically |
--dry-run | Show what would be built without building |
-v, --verbose | Show detailed output including config path and file processing |
Examples
Basic Build Example
Build all assets according to pxl.toml configuration.
# pxl.toml
[project]
name = "my-game"
src = "src/pxl"
out = "build"
[defaults]
scale = 2
pxl build
# Building 5 targets...
# sprite:player ... ok (150ms)
# sprite:enemy ... ok (120ms)
# Build succeeded: 5 built in 270ms
Basic Build
Build all assets using defaults from pxl.toml:
pxl build
Custom Directories
Override source and output directories:
pxl build --src assets/sprites --out dist/images
Watch Mode
Watch Mode
Automatically rebuild when source files change—ideal for development workflows.
pxl build --watch
# Watching src/pxl for changes...
# [12:34:56] sprite:player rebuilt (45ms)
# [12:35:01] sprite:enemy rebuilt (38ms)
# Press Ctrl+C to stop
Automatically rebuild when source files change:
pxl build --watch
Watch mode:
- Monitors source files for changes
- Re-renders only changed files (incremental)
- Shows errors inline without stopping
- Recovers automatically when errors are fixed
- Press
Ctrl+Cto stop
Dry Run
Preview what would be built without writing files:
pxl build --dry-run
Output shows source/output directories and file counts:
Dry run - would build:
Source: src/pxl
Output: build
Files: 24
Sprites: 18
Verbose Output
Show detailed build information:
pxl build --verbose
Displays config file path, individual file processing, and timing information.
Build Pipeline
The build system processes files through a multi-stage pipeline:
- Discovery: Scan source directories for
.pxlfiles using glob patterns - Planning: Create a build plan with targets and dependencies
- Incremental Check: Skip targets that haven't changed since last build
- Parallel Execution: Process independent targets concurrently
- Export Generation: Create game engine-specific output formats
Incremental Builds
The build system tracks file hashes and timestamps in a manifest file. On subsequent builds, it skips targets where:
- Source files haven't changed
- Output files still exist
- No dependencies have been rebuilt
Use --force flag (when available) to bypass incremental checking.
Parallel Execution
By default, the build uses multiple worker threads to process independent targets simultaneously. Targets with dependencies are scheduled to build after their dependencies complete.
Configuration
The build command reads settings from pxl.toml. Key sections:
[project]
name = "my-game"
src = "src/pxl" # Source directory
out = "build" # Output directory
[defaults]
scale = 2 # Default scale factor
padding = 1 # Default atlas padding
[atlases.characters]
sources = ["sprites/player/**", "sprites/enemies/**"]
max_size = [2048, 2048]
[animations]
sources = ["animations/**"]
preview = true # Generate GIF previews
sheet_layout = "horizontal"
[export.godot]
enabled = true
resource_path = "res://assets/sprites"
[export.unity]
enabled = true
pixels_per_unit = 16
[watch]
debounce_ms = 100
clear_screen = true
See Configuration Reference for complete options.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Build succeeded |
| 1 | Build failed (errors in source files or I/O) |
| 2 | Invalid arguments or missing config |
Progress Output
Console Output
Standard build progress shows target status:
Building 5 targets...
sprite:player ... ok (150ms)
sprite:enemy ... ok (120ms)
atlas:characters ... ok (45ms)
export:godot:characters ... ok (12ms)
export:unity:characters ... ok (8ms)
Build succeeded: 5 built in 335ms
Skipped targets (incremental builds) show:
sprite:player ... skipped (up to date)
Verbose Mode
With --verbose, shows additional details:
Using config: /project/pxl.toml
Building 5 targets (4 workers)...
[1/5] sprite:player src/pxl/player.pxl -> build/player.png
...
Related
- Configuration - Full
pxl.tomlreference - Build System Integration - CI/CD and Makefile integration
- render - Render individual files
- validate - Validate without rendering
new
Create a new asset from template.
Usage
pxl new [OPTIONS] <ASSET_TYPE> <NAME>
Arguments
| Argument | Description |
|---|---|
<ASSET_TYPE> | Asset type: sprite, animation, palette |
<NAME> | Asset name |
Options
| Option | Description |
|---|---|
--palette <PALETTE> | Palette to use (for sprites and animations) |
Description
The new command creates new Pixelsrc assets from templates. It generates properly structured files with placeholder content that you can customize.
Examples
Create a sprite
# Create a new sprite with default palette
pxl new sprite hero
# Create with a specific built-in palette
pxl new sprite player --palette @pico8
# Create with a custom palette name
pxl new sprite enemy --palette monster_colors
Create an animation
# Create a new animation
pxl new animation walk
# With palette
pxl new animation run --palette @gameboy
Create a palette
# Create a new palette definition
pxl new palette fantasy_colors
Generated Content
Sprite Template
palette:
name: hero_palette
colors:
outline: #000000
fill: #4a90d9
highlight: #7cb5eb
_: transparent
sprite:
name: hero
palette: hero_palette
grid:
_ _ outline outline outline outline _ _
_ outline fill fill fill fill outline _
outline fill highlight fill fill fill fill outline
outline fill fill fill fill fill fill outline
outline fill fill fill fill fill fill outline
outline fill fill fill fill fill fill outline
_ outline fill fill fill fill outline _
_ _ outline outline outline outline _ _
Animation Template
animation:
name: walk
frames:
- sprite: walk_1
duration: 100
- sprite: walk_2
duration: 100
- sprite: walk_3
duration: 100
- sprite: walk_4
duration: 100
Palette Template
palette:
name: fantasy_colors
colors:
black: #000000
dark: #3a3a3a
mid: #7a7a7a
light: #bababa
white: #ffffff
_: transparent
Output Location
By default, new assets are created in the current directory:
hero.pxlfor spriteswalk.pxlfor animationsfantasy_colors.pxlfor palettes
In a project with pxl.toml, assets are created in the configured source directory.
See Also
init
Initialize a new Pixelsrc project.
Usage
pxl init [OPTIONS] [PATH]
Arguments
| Argument | Description |
|---|---|
[PATH] | Project directory (default: current directory) |
Options
| Option | Description |
|---|---|
--name <NAME> | Project name (default: directory name) |
--preset <PRESET> | Preset template: minimal, artist, animator, game (default: minimal) |
Description
The init command creates a new Pixelsrc project with:
- A
pxl.tomlconfiguration file - Directory structure based on the chosen preset
- Sample assets to get started
Examples
Initialize in current directory
# Minimal setup in current directory
pxl init
# With a custom project name
pxl init --name "my-game-sprites"
Initialize in new directory
# Create and initialize a new directory
pxl init my-project
# With preset
pxl init my-game --preset game
Presets
# Minimal: just config and one sample sprite
pxl init --preset minimal
# Artist: includes palette examples and common templates
pxl init --preset artist
# Animator: includes animation examples and frame templates
pxl init --preset animator
# Game: full setup with atlases, exports, and CI integration
pxl init --preset game
Presets
minimal
Basic setup for getting started:
project/
├── pxl.toml
└── src/
└── sample.pxl
artist
For sprite artists and designers:
project/
├── pxl.toml
├── src/
│ ├── characters/
│ ├── items/
│ └── palettes/
│ └── custom.pxl
└── build/
animator
For animation-focused projects:
project/
├── pxl.toml
├── src/
│ ├── sprites/
│ ├── animations/
│ │ └── sample_walk.pxl
│ └── palettes/
└── build/
game
Full game asset pipeline:
project/
├── pxl.toml
├── src/
│ ├── characters/
│ ├── enemies/
│ ├── items/
│ ├── tiles/
│ ├── ui/
│ └── palettes/
├── build/
└── .github/
└── workflows/
└── build.yml
Generated pxl.toml
[project]
name = "my-project"
src = "src"
out = "build"
[defaults]
scale = 1
format = "png"
# Game preset includes:
[export.godot]
enabled = false
resource_path = "res://assets/sprites"
[export.unity]
enabled = false
pixels_per_unit = 16
See Also
- build - Build project assets
- new - Create individual assets
- Configuration - Full config reference
PNG Export
PNG is the default output format for Pixelsrc. Each sprite renders to a transparent PNG file with optional scaling.
Basic Usage
Render a single sprite to PNG:
pxl render sprite.pxl
This creates sprite_<name>.png for each sprite in the file.
Output Options
Specifying Output Path
# Single file output
pxl render sprite.pxl -o hero.png
# Output to directory (must end with /)
pxl render sprite.pxl -o output/
# Specific sprite only
pxl render sprite.pxl --sprite hero -o hero.png
Scaling
Scale output by integer factors (1-16):
# 2x scale (each pixel becomes 2x2)
pxl render sprite.pxl --scale 2
# 4x scale for high-res preview
pxl render sprite.pxl --scale 4 -o preview.png
Scaling uses nearest-neighbor interpolation to preserve pixel art crispness.
Output Naming
Without -o, output follows this pattern:
| Input | Sprites | Output |
|---|---|---|
hero.pxl | 1 sprite | hero_<sprite_name>.png |
hero.pxl | Multiple | hero_<sprite_name>.png per sprite |
hero.jsonl | 1 sprite | hero_<sprite_name>.png |
Composition Rendering
Render compositions (layered sprites):
# Render all compositions
pxl render scene.pxl
# Render specific composition
pxl render scene.pxl --composition battle_scene
Compositions flatten all layers into a single PNG, respecting:
- Layer order (first layer = background)
- Layer positions (x, y offsets)
- Transparency blending
Error Handling
Lenient Mode (Default)
By default, Pixelsrc is lenient:
- Unknown tokens render as magenta (
#FF00FF) - Row length mismatches are padded/truncated
- Warnings are printed but rendering continues
# Normal render (lenient)
pxl render sprite.pxl
# Warning: Unknown token {foo} in sprite "hero"
# Rendered hero.png (with magenta pixels for unknown tokens)
Strict Mode
For CI/CD validation, use strict mode:
pxl render sprite.pxl --strict
# Error: Unknown token {foo} in sprite "hero"
# Exit code: 1
File Format Details
PNG files are saved with:
- RGBA color (32-bit with alpha channel)
- True transparency (alpha = 0 for
{_}tokens) - No compression artifacts (lossless)
- No embedded metadata
Examples
Basic Sprite Export
Basic PNG Export
Render a simple sprite to PNG with transparency.
{"type": "palette", "name": "coin", "colors": {"_": "#00000000", "y": "#ffd700", "o": "#daa520"}}
{"type": "sprite", "name": "coin", "size": [4, 4], "palette": "coin", "regions": {"y": {"union": [{"rect": [1, 0, 2, 1]}, {"points": [[0, 1], [3, 1], [0, 2], [3, 2]]}, {"rect": [1, 3, 2, 1]}], "z": 0}, "o": {"rect": [1, 1, 2, 2], "z": 1}}}
pxl render coin.pxl -o coin.png
High-Resolution Preview
Scaled PNG Export
Scale output for high-resolution previews while preserving pixel art crispness.
{"type": "palette", "name": "gem", "colors": {"_": "#00000000", "b": "#4169e1", "l": "#87ceeb", "d": "#191970"}}
{"type": "sprite", "name": "gem", "size": [4, 4], "palette": "gem", "regions": {"l": {"rect": [1, 0, 2, 2], "z": 0}, "b": {"union": [{"points": [[0, 1], [3, 1]]}, {"rect": [0, 2, 4, 1]}], "z": 0}, "d": {"rect": [1, 3, 2, 1], "z": 0}}}
pxl render gem.pxl --scale 4 -o gem_4x.png
Batch Processing
# Render all .pxl files in a directory
for f in assets/*.pxl; do
pxl render "$f" -o output/
done
Related
- GIF Animation - Export animated sprites
- Spritesheet - Combine frames into a single image
- Atlas Formats - Game engine integration
GIF Animation
Export animated sprites as GIF files for previews, social media, or web use.
Basic Usage
Render an animation to GIF:
pxl render sprite.pxl --gif
This requires an animation object in your input file:
{
type: "animation",
name: "walk",
frames: ["walk_1", "walk_2", "walk_3"],
duration: 100,
}
Animation Definition
Animations reference sprite names in sequence:
{
type: "sprite",
name: "walk_1",
size: [8, 12],
palette: "hero",
regions: {
body: { rect: [2, 4, 4, 8], z: 0 },
leg_left: { rect: [2, 10, 2, 2], z: 1 },
leg_right: { rect: [4, 10, 2, 2], z: 1 },
},
}
{
type: "sprite",
name: "walk_2",
size: [8, 12],
palette: "hero",
regions: {
body: { rect: [2, 4, 4, 8], z: 0 },
leg_left: { rect: [1, 10, 2, 2], z: 1 },
leg_right: { rect: [5, 10, 2, 2], z: 1 },
},
}
{
type: "animation",
name: "walk",
frames: ["walk_1", "walk_2"],
duration: 100,
}
Animation Options
| Field | Type | Default | Description |
|---|---|---|---|
frames | array | required | Sprite names in sequence |
duration | number | 100 | Milliseconds per frame |
loop | boolean | true | Whether animation loops |
Command Options
Select Animation
When a file contains multiple animations:
# Use specific animation
pxl render sprites.pxl --gif --animation walk
# Without --animation, uses first animation found
pxl render sprites.pxl --gif
Scaling
# 4x scale for better visibility
pxl render sprites.pxl --gif --scale 4 -o preview.gif
Output Path
pxl render sprites.pxl --gif -o hero_walk.gif
Frame Timing
GIF uses centiseconds (1/100 second) for frame delays. Pixelsrc converts duration from milliseconds:
| duration (ms) | GIF delay | Effective FPS |
|---|---|---|
| 16 | 2cs | ~50 fps |
| 33 | 3cs | ~33 fps |
| 100 | 10cs | 10 fps |
| 200 | 20cs | 5 fps |
Minimum duration is 10ms (1 centisecond). Shorter values are clamped.
Palette Cycling
Create animated effects without multiple frames using palette cycling:
{
type: "sprite",
name: "water",
size: [6, 3],
palette: "ocean",
regions: {
w1: { points: [[0, 0], [3, 1], [0, 2]], z: 0 },
w2: { points: [[1, 0], [4, 1], [1, 2]], z: 0 },
w3: { points: [[2, 0], [5, 1], [2, 2]], z: 0 },
},
}
{
type: "animation",
name: "shimmer",
frames: ["water"],
duration: 150,
palette_cycle: [{ tokens: ["w1", "w2", "w3"] }],
}
Palette cycling rotates colors through the specified tokens, creating animated effects like:
- Shimmering water
- Flickering fire
- Pulsing energy
Looping Behavior
Control whether animations loop:
{
type: "animation",
name: "death",
frames: ["die_1", "die_2", "die_3"],
loop: false,
}
"loop": true(default): Animation repeats infinitely"loop": false: Animation plays once and stops on final frame
Examples
Basic Walk Cycle
Animated GIF Export
Export a simple animation as an animated GIF.
{
type: "palette",
name: "blink",
colors: {
_: "transparent",
w: "#FFFFFF",
b: "#000000",
},
}
{
type: "sprite",
name: "eye_open",
size: [4, 3],
palette: "blink",
regions: {
w: {
union: [
{ rect: [1, 0, 2, 1] },
{ rect: [0, 1, 4, 1] },
{ rect: [1, 2, 2, 1] },
],
z: 0,
},
b: { rect: [1, 1, 2, 1], z: 1 },
},
}
{
type: "sprite",
name: "eye_closed",
size: [4, 3],
palette: "blink",
regions: {
w: { rect: [0, 1, 4, 1], z: 0 },
},
}
{
type: "animation",
name: "blink",
frames: ["eye_open", "eye_open", "eye_open", "eye_closed"],
duration: 200,
}
pxl render blink.pxl --gif -o blink.gif
High-Res Social Media Preview
Scaled GIF Export
Scale up animations for better visibility in previews.
{
type: "palette",
name: "spinner",
colors: {
_: "transparent",
a: "#FF6B6B",
b: "#4ECDC4",
},
}
{
type: "sprite",
name: "spin_1",
size: [2, 2],
palette: "spinner",
regions: {
a: { points: [[0, 0]], z: 0 },
b: { points: [[1, 1]], z: 0 },
},
}
{
type: "sprite",
name: "spin_2",
size: [2, 2],
palette: "spinner",
regions: {
a: { points: [[1, 0]], z: 0 },
b: { points: [[0, 1]], z: 0 },
},
}
{
type: "sprite",
name: "spin_3",
size: [2, 2],
palette: "spinner",
regions: {
b: { points: [[0, 0]], z: 0 },
a: { points: [[1, 1]], z: 0 },
},
}
{
type: "sprite",
name: "spin_4",
size: [2, 2],
palette: "spinner",
regions: {
b: { points: [[1, 0]], z: 0 },
a: { points: [[0, 1]], z: 0 },
},
}
{
type: "animation",
name: "spin",
frames: ["spin_1", "spin_2", "spin_3", "spin_4"],
duration: 100,
}
pxl render spinner.pxl --gif --scale 4 -o spinner_4x.gif
Batch Animation Export
# Export all animations from a file
for anim in idle walk run attack; do
pxl render character.pxl --gif --animation "$anim" -o "output/${anim}.gif"
done
Limitations
GIF format has inherent limitations:
- 256 color limit: Complex sprites may have color banding
- Binary transparency: Pixels are fully transparent or opaque (no semi-transparency)
- Large file size: Uncompressed frame data can be large
For better quality and smaller files, consider Spritesheet export for game use.
Related
- PNG Export - Static sprite export
- Spritesheet - Combine frames into a single image
- Atlas Formats - Game engine integration with metadata
Spritesheet
Export animation frames as a single spritesheet image. Spritesheets are ideal for game engines that expect all frames in one file.
Basic Usage
Render an animation as a spritesheet:
pxl render sprites.pxl --spritesheet
This creates a horizontal strip with all frames side by side.
Layout
By default, spritesheets use a horizontal layout (single row):
┌────┬────┬────┬────┐
│ F1 │ F2 │ F3 │ F4 │
└────┴────┴────┴────┘
For a 16x16 sprite with 4 frames, the output is 64x16 pixels.
Frame Dimensions
All frames are padded to match the largest frame:
┌────────┬────────┬────────┐
│ │ ████ │ │
│ ██ │ ██████ │ ██ │
│ │ ████ │ │
└────────┴────────┴────────┘
8x12 12x12 8x12
→ Output: 36x12 (each cell 12x12)
This ensures consistent frame spacing for game engine playback.
Command Options
Select Animation
# Specific animation
pxl render sprites.pxl --spritesheet --animation walk
# First animation found (default)
pxl render sprites.pxl --spritesheet
Scaling
# 4x scale for high-res assets
pxl render sprites.pxl --spritesheet --scale 4 -o walk_4x.png
Output Path
pxl render sprites.pxl --spritesheet -o assets/walk_sheet.png
Animation Definition
The spritesheet is generated from an animation object:
{
type: "sprite",
name: "walk_1",
size: [16, 16],
palette: "hero",
regions: {
body: { rect: [4, 0, 8, 12], z: 0 },
legs: { rect: [4, 12, 8, 4], z: 0 },
},
}
{
type: "sprite",
name: "walk_2",
size: [16, 16],
palette: "hero",
regions: {
body: { rect: [4, 0, 8, 12], z: 0 },
legs: { rect: [2, 12, 12, 4], z: 0 },
},
}
{
type: "animation",
name: "walk",
frames: ["walk_1", "walk_2"],
}
Game Engine Integration
Frame Coordinates
For a 16x16 sprite with 4 frames in a horizontal strip:
| Frame | X | Y | Width | Height |
|---|---|---|---|---|
| 0 | 0 | 0 | 16 | 16 |
| 1 | 16 | 0 | 16 | 16 |
| 2 | 32 | 0 | 16 | 16 |
| 3 | 48 | 0 | 16 | 16 |
Example: Godot AnimatedSprite2D
var texture = load("res://sprites/walk.png")
var frames = SpriteFrames.new()
var frame_width = 16
var num_frames = 4
for i in range(num_frames):
var region = Rect2(i * frame_width, 0, frame_width, texture.get_height())
var atlas_tex = AtlasTexture.new()
atlas_tex.atlas = texture
atlas_tex.region = region
frames.add_frame("walk", atlas_tex)
Example: Unity
Texture2D sheet = Resources.Load<Texture2D>("walk");
int frameWidth = 16;
int frameCount = 4;
Sprite[] frames = new Sprite[frameCount];
for (int i = 0; i < frameCount; i++) {
Rect rect = new Rect(i * frameWidth, 0, frameWidth, sheet.height);
frames[i] = Sprite.Create(sheet, rect, new Vector2(0.5f, 0.5f), 16);
}
Spritesheet vs Atlas
| Feature | Spritesheet | Atlas |
|---|---|---|
| Layout | Horizontal strip | Bin-packed |
| Metadata | None (manual calc) | JSON with coordinates |
| Space efficiency | Lower (fixed grid) | Higher (tight packing) |
| Simplicity | Higher | Lower |
| Multiple sprites | No | Yes |
Use spritesheet for quick exports and simple animation playback. Use atlas for production game assets with multiple sprites and animations.
Examples
Basic Export
Horizontal Spritesheet
Export animation frames as a horizontal strip.
{
type: "palette",
name: "flag",
colors: {
_: "transparent",
r: "#FF0000",
w: "#FFFFFF",
p: "#8B4513",
},
}
{
type: "sprite",
name: "flag_1",
size: [3, 3],
palette: "flag",
regions: {
r: { rect: [0, 0, 2, 1], z: 1 },
w: { rect: [0, 1, 2, 1], z: 1 },
p: { points: [[0, 2]], z: 0 },
},
}
{
type: "sprite",
name: "flag_2",
size: [3, 3],
palette: "flag",
regions: {
r: { rect: [1, 0, 2, 1], z: 1 },
w: { rect: [1, 1, 2, 1], z: 1 },
p: { points: [[0, 2]], z: 0 },
},
}
{
type: "sprite",
name: "flag_3",
size: [3, 3],
palette: "flag",
regions: {
r: { rect: [0, 0, 2, 1], z: 1 },
w: { rect: [0, 1, 2, 1], z: 1 },
p: { points: [[0, 2]], z: 0 },
},
}
{
type: "animation",
name: "wave",
frames: ["flag_1", "flag_2", "flag_3"],
duration: 150,
}
pxl render flag.pxl --spritesheet -o flag_sheet.png
Scaled for Preview
Scaled Spritesheet
Scale up spritesheets for better visibility.
{
type: "palette",
name: "bounce",
colors: {
_: "transparent",
b: "#3498DB",
s: "#2980B9",
},
}
{
type: "sprite",
name: "ball_1",
size: [3, 3],
palette: "bounce",
regions: {
b: {
union: [
{ points: [[1, 0], [0, 1], [2, 1], [1, 2]] },
],
z: 0,
},
s: { points: [[1, 1]], z: 1 },
},
}
{
type: "sprite",
name: "ball_2",
size: [3, 3],
palette: "bounce",
regions: {
b: { rect: [0, 0, 3, 3], z: 0 },
s: { points: [[1, 1]], z: 1 },
},
}
{
type: "sprite",
name: "ball_3",
size: [3, 3],
palette: "bounce",
regions: {
b: {
union: [
{ points: [[1, 0], [0, 1], [2, 1], [1, 2]] },
],
z: 0,
},
s: { points: [[1, 1]], z: 1 },
},
}
{
type: "animation",
name: "bounce",
frames: ["ball_1", "ball_2", "ball_3", "ball_2"],
duration: 100,
}
pxl render bounce.pxl --spritesheet --scale 4 -o bounce_4x.png
Multiple Animations
# Export each animation as separate spritesheet
for anim in idle walk run attack; do
pxl render character.pxl --spritesheet --animation "$anim" -o "sheets/${anim}.png"
done
Related
- PNG Export - Single sprite export
- GIF Animation - Animated preview
- Atlas Formats - Full game asset pipeline with metadata
Atlas Formats
Texture atlases combine multiple sprites into a single image with metadata describing frame locations. Pixelsrc supports several atlas formats for different game engines.
Basic Usage
Export an atlas with generic JSON metadata:
pxl render sprites.pxl --format atlas
This creates:
sprites.png- The packed texture atlas imagesprites.json- Metadata with frame coordinates
Supported Formats
| Format | Flag | Output | Description |
|---|---|---|---|
| JSON | --format atlas | .json | Generic JSON, works with any engine |
| Godot | --format atlas-godot | .tres | Godot resource files |
| Unity | --format atlas-unity | .json | Unity sprite import format |
| libGDX | --format atlas-libgdx | .atlas | TextureAtlas format |
Atlas Configuration
Maximum Size
Limit atlas dimensions (useful for platform constraints):
pxl render sprites.pxl --format atlas --max-size 512x512
If sprites don't fit, multiple atlas pages are generated.
Padding
Add space between sprites to prevent texture bleeding:
pxl render sprites.pxl --format atlas --padding 2
Power of Two
Force dimensions to power-of-two (required by some platforms):
pxl render sprites.pxl --format atlas --power-of-two
Generic JSON Format
The default JSON format includes all metadata for custom integration:
{
"image": "sprites.png",
"size": [256, 256],
"frames": {
"hero_idle": {
"x": 0,
"y": 0,
"w": 32,
"h": 32,
"origin": [16, 32]
},
"hero_walk_1": {
"x": 32,
"y": 0,
"w": 32,
"h": 32,
"origin": [16, 32]
}
},
"animations": {
"walk": {
"frames": ["hero_walk_1", "hero_walk_2", "hero_walk_3"],
"fps": 10
}
}
}
Frame Metadata
Sprites with metadata in the source file include:
origin- Pivot point[x, y]boxes- Collision boxes (hit, hurt, collide, trigger)
{
type: "sprite",
name: "hero",
size: [32, 32],
palette: "colors",
regions: {
body: { rect: [8, 0, 16, 32], z: 0 },
},
metadata: {
origin: [16, 32],
boxes: {
hit: { x: 4, y: 0, w: 24, h: 32 },
},
},
}
Godot Format
pxl render sprites.pxl --format atlas-godot
Generates Godot .tres resource files:
- Individual
AtlasTextureresources for each frame SpriteFramesresource for animations
Example AtlasTexture:
[gd_resource type="AtlasTexture" load_steps=2 format=3]
[ext_resource type="Texture2D" path="res://assets/atlas.png" id="1"]
[resource]
atlas = ExtResource("1")
region = Rect2(0, 0, 32, 32)
Unity Format
pxl render sprites.pxl --format atlas-unity
Generates Unity-compatible JSON:
{
"texture": "atlas.png",
"textureSize": { "w": 256, "h": 256 },
"pixelsPerUnit": 16,
"filterMode": "Point",
"sprites": [
{
"name": "hero_idle",
"rect": { "x": 0, "y": 0, "w": 32, "h": 32 },
"pivot": { "x": 0.5, "y": 0.0 },
"border": { "x": 0, "y": 0, "z": 0, "w": 0 }
}
],
"animations": [
{
"name": "walk",
"frameRate": 10,
"sprites": ["walk_1", "walk_2", "walk_3"]
}
]
}
Import using Unity's TextureImporter API or custom editor scripts.
libGDX Format
pxl render sprites.pxl --format atlas-libgdx
Generates libGDX TextureAtlas format:
atlas.png
size: 256, 256
format: RGBA8888
filter: Nearest, Nearest
repeat: none
hero_idle
rotate: false
xy: 0, 0
size: 32, 32
orig: 32, 32
offset: 0, 0
index: -1
hero_walk
rotate: false
xy: 32, 0
size: 32, 32
orig: 32, 32
offset: 0, 0
index: 0
Load directly with libGDX's TextureAtlas class:
TextureAtlas atlas = new TextureAtlas(Gdx.files.internal("sprites.atlas"));
TextureRegion idle = atlas.findRegion("hero_idle");
Animation<TextureRegion> walk = new Animation<>(0.1f, atlas.findRegions("hero_walk"));
Animation Tags
Define named sub-ranges within animations for game logic:
{
type: "animation",
name: "attack",
frames: ["attack_1", "attack_2", "attack_3", "attack_4", "attack_5"],
duration: 100,
tags: {
windup: { start: 0, end: 1 },
active: { start: 2, end: 3, loop: false },
recovery: { start: 4, end: 4 },
},
}
Tags are included in atlas metadata for runtime use.
Bin Packing
Pixelsrc uses shelf bin packing for efficient atlas layout:
┌─────────────────────────────┐
│ ████ │ ██████ │ ████████ │
├──────┴────────┴──────────┤
│ ██████████ │ ██████ │ │
├────────────┴────────┴────┤
│ ████████████████████████ │
└─────────────────────────────┘
Sprites are sorted by height and packed into horizontal shelves for efficient space usage.
Examples
Basic Atlas Export
JSON Atlas Export
Pack multiple sprites into a single atlas with JSON metadata.
{
type: "palette",
name: "items",
colors: {
_: "transparent",
g: "#FFD700",
s: "#C0C0C0",
r: "#FF4444",
},
}
{
type: "sprite",
name: "coin",
size: [3, 3],
palette: "items",
regions: {
g: {
union: [
{ points: [[1, 0]] },
{ rect: [0, 1, 3, 1] },
{ points: [[1, 2]] },
],
z: 0,
},
},
}
{
type: "sprite",
name: "key",
size: [3, 3],
palette: "items",
regions: {
s: {
union: [
{ rect: [0, 0, 2, 1] },
{ rect: [1, 1, 1, 2] },
{ points: [[2, 2]] },
],
z: 0,
},
},
}
{
type: "sprite",
name: "heart",
size: [3, 3],
palette: "items",
regions: {
r: {
union: [
{ points: [[0, 0], [2, 0]] },
{ rect: [0, 1, 3, 1] },
{ points: [[1, 2]] },
],
z: 0,
},
},
}
pxl render items.pxl --format atlas -o items
Godot Export
Godot Atlas Export
Generate Godot-compatible atlas resources.
{
type: "palette",
name: "ui",
colors: {
_: "transparent",
w: "#FFFFFF",
g: "#888888",
b: "#333333",
},
}
{
type: "sprite",
name: "btn_normal",
size: [4, 3],
palette: "ui",
regions: {
b: { points: [[0, 0], [3, 0], [0, 2], [3, 2]], z: 0 },
g: {
union: [
{ rect: [1, 0, 2, 1] },
{ points: [[0, 1], [3, 1]] },
{ rect: [1, 2, 2, 1] },
],
z: 0,
},
w: { rect: [1, 1, 2, 1], z: 1 },
},
}
{
type: "sprite",
name: "btn_hover",
size: [4, 3],
palette: "ui",
regions: {
g: { points: [[0, 0], [3, 0], [0, 2], [3, 2]], z: 0 },
w: { rect: [0, 0, 4, 3], z: 1 },
},
}
{
type: "sprite",
name: "btn_pressed",
size: [4, 3],
palette: "ui",
regions: {
b: { rect: [0, 0, 4, 3], z: 0 },
g: { rect: [1, 1, 2, 1], z: 1 },
},
}
pxl render ui.pxl --format atlas-godot --padding 1 -o ui_atlas
Production Game Assets
pxl render characters.pxl \
--format atlas-godot \
--max-size 2048x2048 \
--padding 2 \
--power-of-two \
-o assets/characters
Multi-Format Export
# Generate for all supported engines
pxl render sprites.pxl --format atlas -o assets/sprites
pxl render sprites.pxl --format atlas-godot -o assets/godot/sprites
pxl render sprites.pxl --format atlas-unity -o assets/unity/sprites
pxl render sprites.pxl --format atlas-libgdx -o assets/libgdx/sprites
Related
- PNG Export - Individual sprite export
- GIF Animation - Animated preview
- Spritesheet - Simple horizontal strip format
Terminal Output
Display sprites directly in the terminal using ANSI true-color escape sequences. Ideal for quick previews without generating image files.
Basic Usage
Display a sprite in the terminal:
pxl show sprite.pxl
This renders the sprite using colored backgrounds, with each pixel shown as a 3-character cell:
_ _ _ _
_ r r _
r r r r
_ r r _
Legend:
_ = transparent (#00000000)
r = red (#FF0000)
Requirements
Terminal output requires:
- True-color support: 24-bit color (most modern terminals)
- Unicode support: For box-drawing characters in grid mode
Supported terminals: iTerm2, Alacritty, kitty, Windows Terminal, VS Code terminal, and most modern terminal emulators.
Commands
pxl show
Display sprite with colored backgrounds:
# Show first sprite
pxl show sprite.pxl
# Show specific sprite
pxl show sprite.pxl --sprite hero
# Show animation frame
pxl show sprite.pxl --animation walk --frame 2
pxl render --emoji
Quick ASCII art preview:
pxl render sprite.pxl --emoji
Sprite Selection
When a file contains multiple sprites:
# Show specific sprite by name
pxl show characters.pxl --sprite hero
# Without --sprite, shows first sprite found
pxl show characters.pxl
Animation Preview
View individual animation frames:
# Show frame 0 (default)
pxl show sprite.pxl --animation walk
# Show specific frame
pxl show sprite.pxl --animation walk --frame 3
Onion Skinning
Overlay previous/next frames for animation review:
# Show 2 frames before and after current
pxl show sprite.pxl --animation walk --frame 3 --onion 2
Onion Skin Options
| Option | Default | Description |
|---|---|---|
--onion N | - | Show N frames before/after |
--onion-opacity | 0.3 | Ghost frame transparency (0.0-1.0) |
--onion-prev-color | #0000FF | Tint for previous frames (blue) |
--onion-next-color | #00FF00 | Tint for next frames (green) |
--onion-fade | false | Fade opacity for distant frames |
Onion Skin to PNG
Save onion skin preview to file:
pxl show sprite.pxl --animation walk --frame 3 --onion 2 -o preview.png
Coordinate Grid
Use pxl grid to display with row/column coordinates:
pxl grid sprite.pxl --sprite hero
Output:
0 1 2 3
┌────────────
0 │ _ _ _ _
1 │ _ r r _
2 │ r r r r
3 │ _ r r _
Useful for:
- Identifying pixel positions
- Debugging sprite alignment
- Documenting coordinates for code
Color Display
Transparent Pixels
Transparent pixels ({_} or alpha=0) display with a dark gray background to distinguish them from black:
┌──────────┬─────────────────────┐
│ Actual │ Terminal Display │
├──────────┼─────────────────────┤
│ #00000000│ Dark gray (visible) │
│ #000000FF│ Black │
└──────────┴─────────────────────┘
Color Legend
A legend is displayed after the sprite showing:
- Token alias (single character)
- Semantic name
- Hex color value
Legend:
_ = transparent (#00000000)
s = skin (#FFCC99)
h = hair (#8B4513)
o = outline (#000000)
Inline View
Expand tokens with aligned spacing:
pxl inline sprite.pxl --sprite hero
Output:
{_} {_} {skin} {skin} {_} {_}
{_} {skin} {skin} {skin} {skin} {_}
{skin} {skin} {hair} {hair} {skin} {skin}
{_} {skin} {skin} {skin} {skin} {_}
Useful for copy-pasting and editing grid rows.
Examples
Quick Preview During Development
# Fast visual check without creating files
pxl show hero.pxl
Animation Review
# Review walk cycle frame by frame
for i in {0..3}; do
echo "Frame $i:"
pxl show character.pxl --animation walk --frame $i
echo
done
Onion Skin for Animation Polish
# See previous/next frames overlaid for smooth animation
pxl show character.pxl \
--animation walk \
--frame 3 \
--onion 2 \
--onion-fade \
-o onion_preview.png
Debug Pixel Positions
# Get coordinates for a specific pixel
pxl grid sprite.pxl --sprite hero
# Then reference in code: pixel at row 2, column 3
Limitations
- Cell size: Each pixel is 3 characters wide, limiting resolution
- Color accuracy: Terminal color rendering varies by emulator
- No animation playback: Shows static frames only (use GIF for animation)
For accurate color review, export to PNG and view in an image editor.
Related
- PNG Export - Export to image files
- GIF Animation - Animated export
WASM Module
The @stiwi/pixelsrc-wasm package provides a WebAssembly build of the Pixelsrc renderer for use in browsers and Node.js.
Installation
npm install @stiwi/pixelsrc-wasm
Or with other package managers:
yarn add @stiwi/pixelsrc-wasm
pnpm add @stiwi/pixelsrc-wasm
Browser Usage
import init, { render_to_png, render_to_rgba, list_sprites } from '@stiwi/pixelsrc-wasm';
// Initialize WASM module (required before first use)
await init();
// Define a sprite in pixelsrc format
const jsonl = `{
type: "sprite",
name: "heart",
size: [7, 5],
palette: { _: "transparent", r: "#FF0000" },
regions: {
r: {
union: [
{ rect: [1, 0, 2, 1] },
{ rect: [4, 0, 2, 1] },
{ rect: [0, 1, 7, 1] },
{ rect: [1, 2, 5, 1] },
{ rect: [2, 3, 3, 1] },
{ rect: [3, 4, 1, 1] },
],
z: 0,
},
},
}`;
// Render to PNG bytes
const pngBytes = render_to_png(jsonl);
// Display in an <img> element
const blob = new Blob([pngBytes], { type: 'image/png' });
const url = URL.createObjectURL(blob);
document.getElementById('preview').src = url;
Canvas Rendering
For direct canvas rendering, use render_to_rgba:
const result = render_to_rgba(jsonl);
const imageData = new ImageData(
new Uint8ClampedArray(result.pixels),
result.width,
result.height
);
ctx.putImageData(imageData, 0, 0);
Node.js Usage
import { readFileSync, writeFileSync } from 'fs';
import init, { render_to_png } from '@stiwi/pixelsrc-wasm';
await init();
const jsonl = readFileSync('sprite.pxl', 'utf8');
const pngBytes = render_to_png(jsonl);
writeFileSync('sprite.png', pngBytes);
API Reference
init(): Promise<void>
Initialize the WASM module. Must be called once before using any other function.
render_to_png(jsonl: string, spriteName?: string): Uint8Array
Render JSONL input to PNG bytes.
| Parameter | Type | Description |
|---|---|---|
jsonl | string | Pixelsrc JSONL content |
spriteName | string? | Optional: render specific sprite |
Returns: PNG image as Uint8Array
render_to_rgba(jsonl: string, spriteName?: string): RenderResult
Render to raw RGBA pixel data for canvas manipulation.
Returns:
| Property | Type | Description |
|---|---|---|
width | number | Image width in pixels |
height | number | Image height in pixels |
pixels | Uint8Array | Raw RGBA pixel data |
warnings | string[] | Any rendering warnings |
list_sprites(jsonl: string): string[]
Get list of sprite and composition names defined in the input.
validate(jsonl: string): string[]
Validate JSONL without rendering. Returns array of error/warning messages. Empty array means valid input.
Build Targets
The WASM module supports multiple bundler targets:
| Target | Use Case | Build Command |
|---|---|---|
web | Browser ES modules | npm run build |
nodejs | Node.js | npm run build:nodejs |
bundler | Webpack/Rollup | npm run build:bundler |
Error Handling
try {
const pngBytes = render_to_png(jsonl);
// Success
} catch (err) {
// Handle parsing or rendering errors
console.error('Render failed:', err.message);
}
Common errors:
- Invalid JSON syntax
- Undefined palette tokens
- Missing required fields
Performance Tips
- Initialize once: Call
init()once at app startup, not per render - Reuse results: Cache PNG blobs when displaying the same sprite multiple times
- Use RGBA for animations:
render_to_rgbais faster for frequent canvas updates - Batch validation: Use
validate()for quick syntax checks before rendering
Framework Integration
React
import { useEffect, useState, useRef } from 'react';
import init, { render_to_png } from '@stiwi/pixelsrc-wasm';
function PixelSprite({ jsonl }) {
const [imgUrl, setImgUrl] = useState(null);
const initRef = useRef(false);
useEffect(() => {
async function render() {
if (!initRef.current) {
await init();
initRef.current = true;
}
const png = render_to_png(jsonl);
const blob = new Blob([png], { type: 'image/png' });
setImgUrl(URL.createObjectURL(blob));
}
render();
}, [jsonl]);
return imgUrl ? <img src={imgUrl} alt="Pixel sprite" /> : null;
}
Vue
<template>
<img v-if="imgUrl" :src="imgUrl" alt="Pixel sprite" />
</template>
<script setup>
import { ref, watch, onMounted } from 'vue';
import init, { render_to_png } from '@stiwi/pixelsrc-wasm';
const props = defineProps(['jsonl']);
const imgUrl = ref(null);
let wasmReady = false;
onMounted(async () => {
await init();
wasmReady = true;
render();
});
watch(() => props.jsonl, render);
function render() {
if (!wasmReady) return;
const png = render_to_png(props.jsonl);
const blob = new Blob([png], { type: 'image/png' });
imgUrl.value = URL.createObjectURL(blob);
}
</script>
Browser Compatibility
The WASM module requires a modern browser with WebAssembly support.
Supported Browsers
| Browser | Minimum Version | Notes |
|---|---|---|
| Chrome | 90+ | Full support |
| Firefox | 88+ | Full support |
| Safari | 14+ | Full support |
| Edge | 90+ | Full support (Chromium-based) |
Required Features
The WASM module relies on the following browser APIs:
- WebAssembly - Core requirement for running the compiled Rust code
- ES6 Modules - Dynamic import for loading the WASM module
- Blob API - For creating image URLs from PNG bytes
- URL.createObjectURL - For displaying rendered images
- Promises - For async initialization
Feature Detection
The documentation demos include built-in feature detection:
// Check if WASM is supported
if (typeof WebAssembly === 'object' &&
typeof WebAssembly.instantiate === 'function') {
// WASM is supported
}
// Using the pixelsrcDemo API (in documentation pages)
const support = pixelsrcDemo.getBrowserSupport();
if (support.isCompatible) {
// All required features available
} else {
console.log('Missing:', support);
}
Fallback Behavior
When running in an unsupported browser:
- Demo containers display a helpful error message
- The page remains functional (text content, navigation)
- Static images are used where possible as fallbacks
Cross-Browser CSS
The documentation uses cross-browser compatible CSS for pixel art rendering:
/* Pixel-perfect image scaling */
img {
image-rendering: -moz-crisp-edges; /* Firefox */
image-rendering: -webkit-optimize-contrast; /* Safari */
image-rendering: pixelated; /* Chrome, Edge */
image-rendering: crisp-edges; /* Standard */
}
Testing Checklist
When deploying WASM-based features, verify:
- Chrome (latest and 2 versions back)
- Firefox (latest and 2 versions back)
- Safari (latest)
- Edge (latest)
- Mobile Safari (iOS 14+)
- Chrome for Android (latest)
Known Issues
- Safari 13 and earlier: No WebAssembly SIMD support (module loads but may be slower)
- IE11: Not supported (no WebAssembly)
- Some corporate proxies: May block
.wasmfile downloads
Chronicle Integration
Chronicle is a narrative engine for games and graphic novels that uses pixelsrc as its asset rendering layer.
How Chronicle Uses Pixelsrc
Chronicle embeds pixelsrc WASM internally to:
- Pre-render character sprites (all emotions/variants)
- Pre-render location backgrounds
- Generate animation frames for portraits
- Compose scenes (background + positioned characters)
- Render comic panels
Game clients interact with Chronicle, not pixelsrc directly:
Game Client → Chronicle (WASM) → Pixelsrc (embedded)
│
└─► Pre-rendered PNGs
Asset Flow
- Game generates pixelsrc JSONL (via AI or authoring)
- Chronicle receives:
createCharacter(def, pixelsrc_jsonl) - Chronicle stores the JSONL
- Chronicle calls pixelsrc to render → caches PNG
- Later: Chronicle returns cached PNG on request
Why Embed vs. Direct Use
Chronicle embeds pixelsrc rather than exposing it to clients because:
- Pre-rendering: Assets rendered once at creation, not on demand
- Caching: Chronicle manages the render cache
- Simplicity: Clients only deal with PNGs, not JSONL
- Consistency: All rendering goes through one path
Generating Assets for Chronicle
When generating assets for use with Chronicle, follow the standard pixelsrc format:
{
type: "palette",
name: "hero_colors",
colors: {
skin: "#FFDAB9",
hair: "#8B4513",
armor: "#4682B4",
},
}
{
type: "sprite",
name: "hero_neutral",
size: [32, 48],
palette: "hero_colors",
regions: { ... },
}
{
type: "sprite",
name: "hero_happy",
size: [32, 48],
palette: "hero_colors",
regions: { ... },
}
{
type: "animation",
name: "hero_talk",
frames: ["hero_talk_1", "hero_talk_2"],
duration: 100,
}
Chronicle will parse this and render each sprite/animation to its cache.
Related
- Web Editor - Browser-based editor using WASM
- Obsidian Plugin - Uses WASM for rendering
Obsidian Plugin
The Pixelsrc plugin for Obsidian lets you render pixel art sprites directly in your notes.
Features
- Inline rendering - View sprites as images in your notes
- Live preview - See sprites render as you type
- Copy to clipboard - Right-click any sprite to copy as PNG
- Configurable display - Adjust scale, transparency background, and more
Installation
From Community Plugins (Recommended)
- Open Settings > Community Plugins
- Disable Safe Mode if enabled
- Click Browse and search for "PixelSrc"
- Click Install, then Enable
Manual Installation
- Download the latest release from GitHub Releases
- Extract files to your vault's
.obsidian/plugins/pixelsrc/directory - Reload Obsidian
- Enable the plugin in Settings > Community Plugins
Usage
Create a code block with language pixelsrc or pxl:
```pixelsrc
{
type: "sprite",
name: "heart",
size: [7, 5],
palette: { _: "transparent", r: "#FF0000" },
regions: {
r: {
union: [
{ rect: [1, 0, 2, 1] },
{ rect: [4, 0, 2, 1] },
{ rect: [0, 1, 7, 1] },
{ rect: [1, 2, 5, 1] },
{ rect: [2, 3, 3, 1] },
{ rect: [3, 4, 1, 1] },
],
z: 0,
},
},
}
```
The sprite renders in both Reading mode and Live Preview.
Multi-Line Format
For better readability, use the multi-line format:
```pxl
{
type: "palette",
name: "skin",
colors: {
_: "transparent",
s: "#FFD5B8",
h: "#8B4513",
e: "#000000",
},
}
{
type: "sprite",
name: "face",
size: [8, 8],
palette: "skin",
regions: {
h: {
union: [
{ rect: [1, 0, 6, 1] },
{ points: [[0, 1], [7, 1], [0, 6], [7, 6]] },
{ rect: [2, 7, 4, 1] },
],
z: 0,
},
s: {
union: [
{ rect: [1, 1, 6, 5] },
{ rect: [2, 6, 4, 1] },
],
z: 1,
},
e: { points: [[2, 2], [5, 2]], z: 2 },
},
}
```
Settings
Access settings via Settings > Community Plugins > PixelSrc:
| Setting | Description | Default |
|---|---|---|
| Default Scale | Scale factor for rendered sprites (1-16x) | 4 |
| Show Warnings | Display rendering warnings below sprites | Off |
| Transparency Background | Show checkered background for transparent pixels | On |
| Live Preview | Show sprite preview while editing | On |
Keyboard Shortcuts
| Action | Shortcut |
|---|---|
| Re-render current block | Ctrl/Cmd + Shift + R |
| Toggle Live Preview | Ctrl/Cmd + Shift + P |
Tips for Note-Taking
Game Design Documentation
Document game assets with their specifications:
## Player Character
Scale: 16x16 pixels
Animation: 4-frame walk cycle
```pxl
{
type: "sprite",
name: "player",
size: [4, 4],
palette: { _: "transparent", body: "#4169E1", skin: "#FFCC99" },
regions: {
skin: { rect: [1, 0, 2, 1], z: 0 },
body: { rect: [0, 1, 4, 2], z: 0 },
},
}
```
States: idle, walk, jump, attack
Mood Boards
Create visual reference collections:
# Color Palette Exploration
## Warm Sunset
```pxl
{
type: "sprite",
name: "sunset",
size: [3, 3],
palette: { a: "#FF6B35", b: "#F7C59F", c: "#EFEFEF" },
regions: {
a: { rect: [0, 0, 3, 1], z: 0 },
b: { rect: [0, 1, 3, 1], z: 0 },
c: { rect: [0, 2, 3, 1], z: 0 },
},
}
```
## Cool Ocean
```pxl
{
type: "sprite",
name: "ocean",
size: [3, 3],
palette: { a: "#1A535C", b: "#4ECDC4", c: "#F7FFF7" },
regions: {
a: { rect: [0, 0, 3, 1], z: 0 },
b: { rect: [0, 1, 3, 1], z: 0 },
c: { rect: [0, 2, 3, 1], z: 0 },
},
}
```
Tutorials and Guides
Embed sprites inline with explanations:
# Creating a Simple Tree
Start with the trunk:
```pxl
{
type: "sprite",
name: "trunk",
size: [4, 3],
palette: { _: "transparent", t: "#8B4513" },
regions: {
t: { rect: [1, 0, 2, 3], z: 0 },
},
}
```
Then add the foliage...
Troubleshooting
Sprite Not Rendering
- Check that the code block language is
pixelsrcorpxl - Verify JSON5 syntax (look for missing quotes or commas)
- Ensure the plugin is enabled
- Try toggling Live Preview off and on
Blurry Sprites
Increase the scale factor in settings. The default 4x works well for most sprites, but very small sprites (4x4 or 8x8) may benefit from 8x or higher.
Performance Issues
For notes with many sprites:
- Consider using PNG images for finalized sprites
- Disable Live Preview for large documents
- Split sprite collections across multiple notes
Building from Source
git clone https://github.com/pixelsrc/obsidian-pixelsrc
cd obsidian-pixelsrc
npm install
npm run build
Copy main.js, manifest.json, and styles.css to your vault's plugin directory.
Related
- WASM Module - The rendering engine used by this plugin
- Format Specification - Complete format reference
Web Editor
The Pixelsrc web editor provides a browser-based environment for creating and previewing pixel art without installing anything.
Try it: pixelsrc.dev
Features
- Live preview - See sprites render as you type
- Example gallery - Start from pre-built examples
- Export options - Download PNG or copy to clipboard
- Shareable URLs - Share your creations via URL hash
- Keyboard shortcuts - Fast editing workflow
Interface Overview
The editor has three main panels:
| Panel | Purpose |
|---|---|
| Editor | Write and edit Pixelsrc JSONL |
| Preview | Real-time rendered output |
| Gallery | Example sprites to load and modify |
Getting Started
- Open the web editor
- Click an example from the Gallery to load it
- Modify the JSONL in the Editor panel
- Watch the Preview update in real-time
Or paste your own Pixelsrc JSONL directly into the editor.
Keyboard Shortcuts
| Action | Shortcut |
|---|---|
| Render immediately | Ctrl/Cmd + Enter |
| Select all | Ctrl/Cmd + A |
The editor auto-renders as you type with a slight debounce delay. Use Ctrl/Cmd + Enter to force an immediate render.
Export Options
Download PNG
- Select a scale factor (1x, 2x, 4x, or 8x)
- Click Download PNG
- File saves as
pixelsrc-{scale}x.png
Copy to Clipboard
- Select a scale factor
- Click Copy
- Paste directly into other applications
The clipboard copy produces a PNG image, compatible with design tools, chat apps, and document editors.
Sharing Sprites
The editor stores your JSONL in the URL hash. To share:
- Create or load a sprite
- Copy the browser URL
- Share the link
Recipients see your exact sprite when they open the link.
Example URL structure:
https://pixelsrc.dev/#eJyLVkrOz0nVUcpMUbJRKs9ILUpVslKKBQAkDgWV
The hash contains compressed JSONL, keeping URLs reasonably short even for complex sprites.
Error Handling
When your JSONL contains errors, the editor shows helpful messages:
| Error | Meaning | Fix |
|---|---|---|
| Invalid JSON syntax | Missing quotes, commas, or brackets | Check JSON formatting |
| Palette error | Undefined color token in grid | Define all tokens in palette |
| Grid error | Inconsistent row lengths | Ensure all rows have same token count |
| Missing "type" field | Object lacks required type | Add "type": "sprite" or similar |
Offline Usage
The editor works offline after initial load. It uses:
- Service worker caching for assets
- WASM module stored in browser
- No server-side rendering required
Bookmark the editor for quick access even without internet.
Mobile Support
The web editor works on mobile devices with some considerations:
- Touch editing - Standard text input in editor panel
- Pinch zoom - Zoom preview with two fingers
- Responsive layout - Panels stack vertically on narrow screens
For the best mobile experience, use landscape orientation.
Embedding
Embed the editor in your own site using an iframe:
<iframe
src="https://pixelsrc.dev/#YOUR_COMPRESSED_HASH"
width="100%"
height="600"
style="border: 1px solid #ccc;"
></iframe>
To embed a specific sprite, include the compressed JSONL hash in the URL.
Local Development
Run the editor locally:
cd website
npm install
npm run dev
This starts a Vite development server with hot module replacement.
Building for Production
npm run build
Output goes to website/dist/ ready for static hosting.
Technical Details
The web editor is built with:
- TypeScript - Type-safe codebase
- Vite - Fast development and bundling
- WASM -
@stiwi/pixelsrc-wasmfor rendering - LZ-String - URL hash compression
No framework dependencies. The editor uses vanilla DOM manipulation for minimal bundle size.
Related
- WASM Module - The rendering library
- Format Specification - JSONL format reference
- AI Generation - Generate sprites with AI
Build System Integration
Integrate Pixelsrc into your build pipeline to automatically render sprites during development and deployment.
Command-Line Interface
The pxl CLI is designed for build system integration:
# Render a sprite
pxl render sprite.pxl -o output.png
# Validate without rendering (fast)
pxl validate sprite.pxl
# Strict mode for CI (warnings become errors)
pxl render sprite.pxl --strict -o output.png
# Full project build
pxl build
Exit Codes
Use exit codes for conditional build logic:
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Error (or warning in strict mode) |
| 2 | Invalid arguments |
Incremental Builds
The build system tracks file changes and only rebuilds what's necessary:
# First build - processes all files
pxl build
# Build complete - Files: 24 | Sprites: 18
# Second build (no changes) - skips everything
pxl build
# Build complete - Files: 24 | Sprites: 0 (all skipped)
# After modifying player.pxl - only rebuilds affected files
pxl build
# Build complete - Files: 24 | Sprites: 1
The manifest file (.pxl-manifest.json) tracks:
- Source file hashes
- Output file paths
- Build timestamps
- Dependency relationships
Forcing Full Rebuild
To bypass incremental checking:
# Delete the manifest
rm .pxl-manifest.json && pxl build
# Or use clean build in your tooling
Parallel Builds
The build system automatically parallelizes independent targets:
Building 12 targets (4 workers)...
sprite:player ... ok (150ms)
sprite:enemy ... ok (120ms)
sprite:coin ... ok (80ms)
atlas:characters ... ok (45ms) # waits for sprites
export:godot ... ok (12ms) # waits for atlas
Dependencies are respected: atlas builds wait for their source sprites, exports wait for atlases.
Progress Reporting
Console Output
Standard progress shows build status:
Building...
Build complete - Files: 24 | Sprites: 18
Verbose mode (-v) shows per-target details:
pxl build -v
# Using config: /project/pxl.toml
# Building 5 targets...
# sprite:player ... ok (150ms)
# sprite:enemy ... ok (120ms)
# ...
JSON Output (CI Integration)
For programmatic parsing, the build system can output JSON progress events:
{"event":"build_started","total_targets":5}
{"event":"target_completed","target_id":"sprite:player","status":"success","duration_ms":150}
{"event":"target_completed","target_id":"sprite:enemy","status":"success","duration_ms":120}
{"event":"build_completed","success":true,"duration_ms":335,"succeeded":5,"skipped":0,"failed":0}
Game Engine Exports
Configure exports in pxl.toml to generate engine-specific files alongside your sprites.
Godot Export
[export.godot]
enabled = true
resource_path = "res://assets/sprites"
animation_player = true
sprite_frames = true
Generates:
.tresresource files for atlasesAnimationPlayerresources for animationsSpriteFramesresources for animated sprites
Unity Export
[export.unity]
enabled = true
pixels_per_unit = 16
filter_mode = "point" # or "bilinear"
Generates:
.assetmeta files for sprites- Sprite slicing data for atlases
- Animation clips for animated sprites
libGDX Export
[export.libgdx]
enabled = true
Generates:
.atlasfiles in libGDX TexturePacker format- Region definitions for sprite lookup
Make / Justfile
Basic Makefile integration:
SPRITES := $(wildcard assets/*.pxl)
PNGS := $(SPRITES:.pxl=.png)
all: $(PNGS)
%.png: %.pxl
pxl render $< -o $@
clean:
rm -f $(PNGS)
With Just, the project includes a ready-to-use justfile:
# Render an example
just render coin
# Run all checks
just check
# List available commands
just --list
npm Scripts
Add to package.json:
{
"scripts": {
"sprites:build": "pxl build",
"sprites:watch": "pxl build --watch",
"sprites:validate": "pxl validate assets/sprites/*.pxl --strict"
}
}
Watch Mode
The CLI supports watch mode for development:
pxl build --watch
Watch mode:
- Monitors source files for changes
- Re-renders only changed files (incremental)
- Shows errors inline without stopping
- Recovers automatically when errors are fixed
- Configurable debounce delay
Configure watch behavior in pxl.toml:
[watch]
debounce_ms = 100 # Wait before rebuilding
clear_screen = true # Clear terminal between builds
Error Recovery
Watch mode continues running after errors. When you fix the error, the build automatically recovers:
Watching for changes (Ctrl+C to stop)...
[15:23:01] player.pxl changed
ERROR: Invalid color 'gren' at line 5
[15:23:15] player.pxl changed
ok - player.png
CI/CD Integration
GitHub Actions
name: Build Sprites
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Pixelsrc
run: cargo install pixelsrc
- name: Validate sprites
run: pxl validate assets/*.pxl --strict
- name: Build sprites
run: pxl build
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: sprites
path: build/
GitLab CI
build-sprites:
image: rust:latest
script:
- cargo install pixelsrc
- pxl validate assets/*.pxl --strict
- pxl build
artifacts:
paths:
- build/
Caching
Cache the Cargo build to speed up CI:
# GitHub Actions
- uses: actions/cache@v4
with:
path: ~/.cargo
key: ${{ runner.os }}-cargo-pixelsrc
# GitLab CI
cache:
paths:
- ~/.cargo
Configuration File
Create pxl.toml in your project root for persistent settings:
[project]
name = "my-game"
src = "assets/sprites"
out = "dist/sprites"
[defaults]
scale = 4
padding = 1
[atlases.characters]
sources = ["player/**", "enemies/**"]
max_size = [2048, 2048]
power_of_two = true
[animations]
sources = ["animations/**"]
preview = true
sheet_layout = "horizontal"
[export.godot]
enabled = true
resource_path = "res://sprites"
[validate]
strict = true
unused_palettes = "warn"
missing_refs = "error"
[watch]
debounce_ms = 100
clear_screen = true
Project Structure
Recommended layout for projects with sprites:
project/
├── assets/
│ └── sprites/
│ ├── characters/
│ │ ├── hero.pxl
│ │ └── enemy.pxl
│ ├── items/
│ │ ├── sword.pxl
│ │ └── potion.pxl
│ └── palettes/
│ └── shared.pxl
├── build/
│ ├── sprites/
│ │ └── ... (generated PNGs)
│ ├── godot/
│ │ └── ... (generated .tres files)
│ └── .pxl-manifest.json
├── pxl.toml
└── package.json
Shared Palettes
Use the --include flag to share palettes across sprites:
pxl render sprite.pxl --include palettes/shared.pxl -o output.png
Or reference palettes by path in your source files using the includes field.
Batch Processing
Render multiple sprites with glob patterns:
# Render all sprites in a directory
pxl render assets/sprites/**/*.pxl -o dist/
# Render with specific output names
pxl render character.pxl enemy.pxl -o dist/{name}.png
The {name} placeholder uses the sprite name for the output filename.
Format Conversion
Convert between output formats:
# PNG (default)
pxl render sprite.pxl -o output.png
# GIF animation
pxl render animation.pxl -o output.gif
# Spritesheet
pxl render sprites.pxl -o sheet.png --spritesheet
# Terminal preview
pxl render sprite.pxl --terminal
Error Handling in Scripts
Example bash script with proper error handling:
#!/bin/bash
set -e
echo "Validating sprites..."
if ! pxl validate assets/*.pxl --strict; then
echo "Validation failed!"
exit 1
fi
echo "Building sprites..."
pxl build
echo "Done! Rendered $(ls build/*.png 2>/dev/null | wc -l) sprites."
Performance Tips
- Use incremental builds - Let the manifest track changes
- Validate before building - Faster than full renders
- Batch similar sprites - Reduces CLI startup overhead
- Cache in CI - Cache Cargo builds and manifest files
- Watch mode for development - Avoids manual re-running
- Parallel builds - Enabled by default for multi-core systems
Troubleshooting
Build is slow
- Check if incremental builds are working (look for "skipped" in output)
- Ensure manifest file isn't being deleted between builds
- Use
--verboseto identify slow targets
Watch mode missing changes
- Check
debounce_mssetting (increase if changes are batched) - Verify source directory path in
pxl.toml - Some editors use atomic saves that may need different handling
Exports not generating
- Verify export is enabled in
pxl.toml(e.g.,[export.godot] enabled = true) - Check that atlas sources are correctly specified
- Run with
--verboseto see export target status
Related
- CLI Reference - Complete command documentation
- build command - Full build command reference
- Configuration - pxl.toml reference
- Exit Codes - All exit code meanings
System Prompts
Use these system prompts to generate Pixelsrc sprites with LLMs like Claude, GPT, or local models.
Base System Prompt
Copy this prompt to your AI assistant's system message:
You are a pixel art generator that outputs sprites in pixelsrc format.
## Format Rules
1. Output is JSON5 objects with a "type" field
2. Output types: "palette" (color definitions) or "sprite" (structured regions)
3. Sprites use structured regions that define shapes, not pixel grids
4. Common tokens: `_` for transparency, descriptive names for colors
5. Palette colors use hex format: `#RRGGBB` or `#RRGGBBAA` (with alpha)
## Sprite Structure
{
type: "sprite",
name: "sprite_name",
size: [width, height],
palette: "palette_name",
regions: {
token: { shape: [params], z: order },
},
}
- name: unique identifier (lowercase_snake_case)
- size: [width, height] in pixels
- palette: name of defined palette
- regions: map of token names to shape definitions
## Shape Primitives
- rect: [x, y, width, height] - Filled rectangle
- stroke: [x, y, width, height] - Rectangle outline
- points: [[x, y], ...] - Individual pixels
- circle: [cx, cy, radius] - Filled circle
- ellipse: [cx, cy, rx, ry] - Filled ellipse
- polygon: [[x, y], ...] - Filled polygon
- line: [[x1, y1], [x2, y2], ...] - Connected line
## Compound Shapes
Combine shapes with union, subtract, or intersect:
{
union: [
{ rect: [2, 0, 4, 1] },
{ rect: [0, 2, 8, 4] },
],
z: 0,
}
## Example Output
{
type: "palette",
name: "heart",
colors: {
_: "transparent",
r: "#FF0000",
p: "#FF6B6B",
dark: "#B80000",
},
}
{
type: "sprite",
name: "heart",
size: [7, 6],
palette: "heart",
regions: {
r: {
union: [
{ rect: [1, 0, 2, 1] }, // Left lobe
{ rect: [4, 0, 2, 1] }, // Right lobe
{ rect: [0, 1, 7, 2] }, // Wide middle
{ rect: [1, 3, 5, 1] }, // Narrowing
{ rect: [2, 4, 3, 1] }, // Tip area
],
z: 0,
},
p: { points: [[1, 1], [4, 1]], z: 1 },
dark: { points: [[3, 5]], z: 2 },
},
}
{
type: "animation",
name: "heart_beat",
keyframes: {
"0%": { sprite: "heart", transform: "scale(1)" },
"15%": { sprite: "heart", transform: "scale(1.1)" },
"30%": { sprite: "heart", transform: "scale(1)" },
},
duration: "1s",
timing_function: "ease-out",
loop: true,
}
## Best Practices
- Use semantic token names: skin, hair, outline, shadow, highlight
- Keep sprites small: 8x8, 16x16, or 32x32 pixels
- Use _ with "transparent" for transparent pixels
- Limit palette to 4-16 colors for retro aesthetic
- Use shapes (rect, circle) for large areas, points for details
- Higher z values draw on top
- Add highlights and shadows for depth
- Include an outline color for definition
## Variants
Create color variations of existing sprites:
{ type: "variant", name: "hero_red", base: "hero", palette: { hair: "#FF0000" } }
## Compositions (Layering)
Compose multiple sprites into larger images:
{
type: "composition",
name: "scene",
size: [32, 32],
layers: [
{ sprite: "background", x: 0, y: 0 },
{ sprite: "hero", x: 12, y: 12 },
],
}
Specialized Templates
Character Template
Use this for generating character sprites:
Create a [SIZE]x[SIZE] pixel art character sprite in pixelsrc format.
Character description:
- [DESCRIBE CHARACTER: hero, villain, NPC, monster, etc.]
- [DESCRIBE APPEARANCE: hair color, clothing, accessories]
- [DESCRIBE STYLE: cute, realistic, retro, etc.]
Requirements:
- Use semantic token names: skin, hair, eye, shirt, pants, outline
- Define outline first with stroke, then fill with rect shapes
- Include _ mapped to "transparent" for transparent background
- Limit palette to 8-12 colors
- Use z-ordering: outline z:0, fills z:1, details z:2
Output format:
1. First: palette with colors
2. Second: sprite with regions using shapes
Example palette structure:
{
type: "palette",
name: "char",
colors: {
_: "transparent",
outline: "#2C1810",
skin: "#FFCC99",
skin_shadow: "#D4A574",
hair: "#8B4513",
shirt: "#4169E1",
},
}
Item Template
Use this for objects and collectibles:
Create a [SIZE]x[SIZE] pixel art item/object sprite in pixelsrc format.
Item description:
- [DESCRIBE ITEM: sword, potion, key, chest, etc.]
- [DESCRIBE MATERIALS: wood, metal, glass, magical, etc.]
- [DESCRIBE STYLE: fantasy RPG, sci-fi, cute, realistic]
Requirements:
- Use semantic token names: blade, hilt, gem, wood, metal
- Use stroke for outlines, rect/ellipse for fills
- Include _ mapped to "transparent" for transparent background
- Keep palette under 8 colors
Visual guidelines:
- Use lighter colors on top-left for highlight
- Use darker colors on bottom-right for shadow
- Leave 1-2 pixel margin for breathing room
Example palette:
{
type: "palette",
name: "sword",
colors: {
_: "transparent",
blade: "#C0C0C0",
blade_shine: "#E8E8E8",
blade_shadow: "#808080",
guard: "#FFD700",
hilt: "#8B4513",
},
}
Animation Template
Use this for animated sprites:
Create a [SIZE]x[SIZE] pixel art animation in pixelsrc format.
Animation description:
- [DESCRIBE SUBJECT: character, object, effect]
- [DESCRIBE ACTION: walking, attacking, idle, spinning]
- [DESCRIBE FRAME COUNT: 2, 4, 6, 8 frames]
Requirements:
- All frames must use the SAME shared palette
- Keep subject centered and consistent across frames
- Only animate the parts that move
- Use meaningful frame names: [name]_1, [name]_2, etc.
- Use CSS keyframes format for animations
Output format:
1. First: shared palette
2. Following: sprite frames in order
3. Last: CSS keyframes animation
CSS Keyframes format:
{
type: "animation",
name: "anim",
keyframes: {
"0%": { sprite: "frame_1" },
"50%": { sprite: "frame_2", transform: "translate(0, -2)" },
"100%": { sprite: "frame_1" },
},
duration: "500ms",
timing_function: "ease-in-out",
loop: true,
}
Frame timing:
- Fast action: "50ms" to "100ms"
- Normal movement: "100ms" to "150ms"
- Slow/relaxed: "200ms" to "300ms"
- Idle breathing: "400ms" to "2s"
Usage with Different Models
Claude
Claude excels at following complex format rules. Paste the system prompt, then request:
"Create a 16x16 pixel art sword with a silver blade and golden hilt"
GPT-4
Use the system prompt as a "System" message in the API or ChatGPT custom instructions.
Local Models
For smaller local models:
- Keep prompts simpler
- Provide more examples inline
- May need multiple generation attempts
- Consider fine-tuning for pixelsrc format
Verification
Always verify generated output:
# Quick preview in terminal
pxl show generated.pxl
# Render to PNG
pxl render generated.pxl -o output.png
# Catch format issues
pxl validate generated.pxl --strict
# Format for consistency
pxl fmt generated.pxl
Best Practices
Tips and techniques for getting reliable, high-quality pixel art from LLMs.
Prompt Structure
Be Explicit About Format
LLMs work best with clear, specific instructions. Always include:
- Dimensions: "16x16 pixels" not just "small"
- Color count: "using 4-8 colors"
- Style reference: "SNES-era" or "Game Boy style"
- Output format: "in pixelsrc JSONL format"
Good Prompt Example
Create a 16x16 pixel art treasure chest in pixelsrc format. Use a warm brown wood color, gold trim, and a dark shadow. Include a keyhole detail. Use semantic token names like {wood}, {gold}, {shadow}. Keep the palette under 8 colors.
Bad Prompt Example
Make me a chest
Token Naming
Use Semantic Names
Semantic token names produce better, more editable output:
| Good | Bad |
|---|---|
{skin} | {c1} |
{outline} | {black} |
{highlight} | {light} |
{shadow} | {dark} |
{hair_dark} | {brown2} |
Consistent Naming Patterns
For related colors, use suffixes:
{skin} - base color
{skin_light} - highlight
{skin_dark} - shadow
For body parts:
{hair}
{hair_highlight}
{eye}
{eye_pupil}
Color Palettes
Limit Colors
Different color counts suit different styles:
- 4 colors: Game Boy, minimalist
- 8 colors: NES, clean retro
- 16 colors: SNES, detailed retro
- 32+ colors: Modern pixel art
Ask for specific counts: "use exactly 6 colors"
Request Harmony
"Use a complementary color scheme with blue and orange"
"Use an analogous palette of warm earth tones"
"Match the NES palette limitations"
Include Alpha
Always request transparency handling:
"Use {_} with #00000000 for transparent pixels"
Multi-Sprite Workflows
Shared Palettes
When generating multiple related sprites, define the palette first:
"First, create a palette called 'hero_colors' with skin, hair, shirt, pants, and outline colors. Then create 4 sprite frames using that palette."
Animation Frames
For animations, be specific about frame differences:
"Create a 4-frame walk cycle:
- Frame 1: left foot forward
- Frame 2: feet together
- Frame 3: right foot forward
- Frame 4: feet together Keep the body consistent across frames, only animate the legs."
Tileset Consistency
For tilesets, establish rules:
"Create a 4-tile grass tileset that tiles seamlessly. Use the same 3 shades of green across all tiles. Ensure edges match when tiles are placed adjacent."
Large Images with Tiling
For images larger than 32x32, use composition with cell_size:
"Create a 64x64 landscape scene. First generate four 32x32 tiles: sky_left, sky_right, ground_left, ground_right. Then compose them with cell_size [32, 32]."
Example composition:
{"type": "composition", "name": "landscape", "size": [64, 64], "cell_size": [32, 32],
"sprites": {"A": "sky_left", "B": "sky_right", "C": "ground_left", "D": "ground_right"},
"layers": [{"map": ["AB", "CD"]}]}
Color Variants
Use variants to recolor existing sprites without regenerating:
"Create a hero sprite, then create a variant called 'hero_fire' with red hair instead of brown"
{"type": "variant", "name": "hero_fire", "base": "hero", "palette": {"{hair}": "#FF4400"}}
Iteration Strategies
Silhouette First
Before adding detail, nail the outline. A good silhouette is recognizable when filled solid.
The Mushroom Principle: Most organic forms can be built from 3 basic shapes:
- Circle - head/cranium
- Rectangle - torso/mid-section
- Triangle/Polygon - tapers (jaw, legs)
Workflow:
- Define outline with 2-3 basic shapes
- Adjust proportions until shape reads correctly
- Add asymmetry for 3/4 view (shift shapes, angle triangles)
- Only then add internal detail, shading, features
If you can't make the silhouette work with 3 shapes, the problem is proportions, not detail.
Start Simple, Add Detail
- Generate basic shape
- Add colors/shading
- Refine details
- Create variations
Ask for Variations
"Create 3 color variations of this sprite:
- Original colors
- Night version (darker, bluer)
- Autumn version (warmer oranges)"
Refine Incrementally
"The sword looks good, but make the blade 2 pixels longer and add a shine highlight on the edge"
Quality Checklist
Before using generated sprites, verify:
- Valid JSON (parseable)
- Correct dimensions match request
- All rows have same token count
- Transparency token defined
- Colors are valid hex format
- Token names are semantic
-
Renders correctly with
pxl render - No magenta (error color) in output
# Quick validation
pxl render generated.pxl --strict
# Format for consistency
pxl fmt generated.pxl
# If it renders without errors, you're good
Model-Specific Tips
Claude
- Excellent at following complex format rules
- Include the full system prompt for best results
- Can generate and explain in same response
- Handles multi-step workflows well
GPT-4
- Strong at visual concepts
- May need reminding about JSON syntax
- Use "System" message for format instructions
- Good at creative variations
Local Models
- Keep prompts simpler
- Provide more examples
- May need multiple attempts
- Consider fine-tuning for pixelsrc format
Known Rendering Limitations
Some shape combinations can cause rendering artifacts. Keep these in mind:
| Issue | Trigger | Workaround |
|---|---|---|
| Stripe artifacts | 5+ shapes with same color | Keep to 4 shapes max per color |
| Polygon fill gaps | 6+ vertices in polygon | Break into simpler shapes, use union |
| Overlap artifacts | Multiple regions overlapping at same z | Use different z-levels |
Safe patterns:
- 3-4 simple shapes (circle, rect, polygon with 3-5 vertices)
unionof simple shapes
Risky patterns:
- 5+ overlapping regions with same color
- Single polygon with 6+ vertices
When in doubt, keep it simple. If you hit artifacts, reduce complexity.
Example Gallery
Example prompts and their expected outputs for generating pixel art with AI.
Simple Item: Heart
Prompt:
Create a 7x6 pixel art heart icon with a red base color and pink highlight
Output:
{
type: "palette",
name: "heart",
colors: {
_: "transparent",
r: "#FF0000",
p: "#FF6B6B",
},
}
{
type: "sprite",
name: "heart",
size: [7, 6],
palette: "heart",
regions: {
r: {
union: [
{ rect: [1, 0, 2, 1] }, // Left lobe top
{ rect: [4, 0, 2, 1] }, // Right lobe top
{ rect: [0, 1, 7, 2] }, // Wide middle
{ rect: [1, 3, 5, 1] }, // Narrowing
{ rect: [2, 4, 3, 1] }, // Near tip
{ rect: [3, 5, 1, 1] }, // Tip
],
z: 0,
},
p: { points: [[1, 1], [4, 1]], z: 1 },
},
}
Collectible: Animated Coin
Prompt:
Create an 8x8 animated spinning coin with 4 frames showing rotation
Output:
{
type: "palette",
name: "coin",
colors: {
_: "transparent",
g: "#FFD700",
y: "#FFEC8B",
o: "#B8860B",
d: "#8B6914",
},
}
// Frame 1: Full face
{
type: "sprite",
name: "coin_1",
size: [8, 8],
palette: "coin",
regions: {
g: {
union: [
{ rect: [2, 0, 4, 1] },
{ rect: [1, 1, 6, 1] },
{ rect: [0, 2, 8, 4] },
{ rect: [1, 6, 6, 1] },
],
z: 0,
},
y: {
union: [
{ rect: [2, 1, 4, 1] },
{ rect: [1, 2, 2, 4] },
],
z: 1,
},
o: { rect: [2, 6, 4, 1], z: 1 },
d: { rect: [2, 7, 4, 1], z: 2 },
},
}
// Frame 2: Slight rotation
{
type: "sprite",
name: "coin_2",
size: [8, 8],
palette: "coin",
regions: {
g: {
union: [
{ rect: [3, 0, 2, 1] },
{ rect: [2, 1, 4, 1] },
{ rect: [1, 2, 6, 4] },
{ rect: [2, 6, 4, 1] },
],
z: 0,
},
y: { rect: [2, 2, 2, 4], z: 1 },
o: { rect: [3, 6, 2, 1], z: 1 },
d: { rect: [3, 7, 2, 1], z: 2 },
},
}
// Frame 3: Edge on
{
type: "sprite",
name: "coin_3",
size: [8, 8],
palette: "coin",
regions: {
g: { rect: [3, 0, 2, 6], z: 0 },
y: { rect: [3, 2, 1, 3], z: 1 },
o: { rect: [3, 6, 2, 1], z: 1 },
d: { rect: [3, 7, 2, 1], z: 2 },
},
}
// Frame 4: Returning
{
type: "sprite",
name: "coin_4",
size: [8, 8],
palette: "coin",
regions: {
g: {
union: [
{ rect: [3, 0, 2, 1] },
{ rect: [2, 1, 4, 1] },
{ rect: [1, 2, 6, 4] },
{ rect: [2, 6, 4, 1] },
],
z: 0,
},
y: { rect: [4, 2, 2, 4], z: 1 },
o: { rect: [3, 6, 2, 1], z: 1 },
d: { rect: [3, 7, 2, 1], z: 2 },
},
}
{
type: "animation",
name: "coin_spin",
frames: ["coin_1", "coin_2", "coin_3", "coin_4"],
duration: 150,
loop: true,
}
Character: Hero Sprite
Prompt:
Create a 16x16 pixel art character with brown hair, skin tone face, blue shirt, and dark pants. Include an outline for definition.
Output:
{
type: "palette",
name: "hero",
colors: {
_: "transparent",
outline: "#2C1810",
skin: "#FFCC99",
hair: "#8B4513",
eye: "#000000",
shirt: "#4169E1",
pants: "#1E3A5F",
shoes: "#000000",
},
}
{
type: "sprite",
name: "hero_idle",
size: [16, 16],
palette: "hero",
regions: {
// Outline drawn first (lowest z)
outline: {
union: [
{ stroke: [5, 1, 6, 10] }, // Head/body outline
{ stroke: [6, 11, 4, 4] }, // Legs outline
],
z: 0,
},
// Hair
hair: { rect: [6, 2, 4, 3], z: 1 },
// Face
skin: {
union: [
{ rect: [6, 5, 4, 3] }, // Face
{ points: [[4, 10], [11, 10]] }, // Hands
],
z: 1,
},
// Eyes
eye: { points: [[7, 6], [9, 6]], z: 2 },
// Shirt
shirt: { rect: [5, 9, 6, 3], z: 1 },
// Pants
pants: { rect: [6, 12, 4, 2], z: 1 },
// Shoes
shoes: {
union: [
{ rect: [5, 14, 2, 2] },
{ rect: [9, 14, 2, 2] },
],
z: 1,
},
},
}
Weapon: Sword
Prompt:
Create a 16x16 pixel art sword with a silver blade, golden crossguard, and brown leather handle
Output:
{
type: "sprite",
name: "sword",
size: [16, 16],
palette: {
_: "transparent",
blade: "#C0C0C0",
edge: "#E8E8E8",
guard: "#FFD700",
handle: "#8B4513",
pommel: "#DAA520",
},
regions: {
// Blade body (diagonal via line)
blade: {
union: [
{ line: [[14, 0], [5, 9]] },
{ line: [[13, 1], [6, 8]] },
],
z: 0,
},
// Blade edge highlight
edge: { line: [[14, 1], [6, 9]], z: 1 },
// Crossguard
guard: { rect: [3, 10, 6, 1], z: 2 },
// Handle
handle: { rect: [5, 11, 2, 4], z: 0 },
// Pommel
pommel: { rect: [5, 15, 2, 1], z: 1 },
},
}
Tile: Grass with Variation
Prompt:
Create a 16x16 grass tile for a top-down RPG with varied green shades
Output:
{
type: "sprite",
name: "grass",
size: [16, 16],
palette: {
g1: "#228B22",
g2: "#32CD32",
g3: "#3CB371",
d: "#1E5F1E",
},
regions: {
// Base grass (fills entire tile)
g1: { rect: [0, 0, 16, 16], z: 0 },
// Light grass highlights scattered
g2: {
points: [
[1, 0], [6, 1], [11, 0], [14, 2],
[2, 4], [8, 3], [13, 5], [0, 7],
[5, 8], [10, 7], [15, 9], [3, 11],
[7, 12], [12, 10], [1, 14], [9, 15],
],
z: 1,
},
// Medium grass accents
g3: {
points: [
[4, 0], [9, 2], [2, 5], [14, 4],
[0, 9], [6, 10], [11, 8], [3, 13],
[8, 14], [13, 12], [5, 15], [15, 15],
],
z: 1,
},
// Dark grass shadows
d: {
points: [
[8, 4], [2, 8], [12, 6], [6, 13],
],
z: 1,
},
},
}
Scene: Tiled Composition
Prompt:
Create a 32x32 scene using 8x8 tiles. Make sky and grass tiles, then compose them into a simple landscape.
Output:
{
type: "palette",
name: "nature",
colors: {
sky: "#87CEEB",
cloud: "#FFFFFF",
grass: "#228B22",
grass_dark: "#1E5F1E",
},
}
{
type: "sprite",
name: "sky_tile",
size: [8, 8],
palette: "nature",
regions: {
sky: { rect: [0, 0, 8, 8], z: 0 },
cloud: {
union: [
{ rect: [2, 1, 2, 1] },
{ rect: [1, 2, 4, 2] },
],
z: 1,
},
},
}
{
type: "sprite",
name: "grass_tile",
size: [8, 8],
palette: "nature",
regions: {
grass: { rect: [0, 0, 8, 8], z: 0 },
grass_dark: {
points: [
[1, 0], [5, 1], [0, 2], [3, 3],
[6, 4], [1, 5], [4, 6], [7, 7],
],
z: 1,
},
},
}
{
type: "composition",
name: "landscape",
size: [32, 32],
layers: [
// Top rows: sky
{ sprite: "sky_tile", x: 0, y: 0 },
{ sprite: "sky_tile", x: 8, y: 0 },
{ sprite: "sky_tile", x: 16, y: 0 },
{ sprite: "sky_tile", x: 24, y: 0 },
{ sprite: "sky_tile", x: 0, y: 8 },
{ sprite: "sky_tile", x: 8, y: 8 },
{ sprite: "sky_tile", x: 16, y: 8 },
{ sprite: "sky_tile", x: 24, y: 8 },
// Bottom rows: grass
{ sprite: "grass_tile", x: 0, y: 16 },
{ sprite: "grass_tile", x: 8, y: 16 },
{ sprite: "grass_tile", x: 16, y: 16 },
{ sprite: "grass_tile", x: 24, y: 16 },
{ sprite: "grass_tile", x: 0, y: 24 },
{ sprite: "grass_tile", x: 8, y: 24 },
{ sprite: "grass_tile", x: 16, y: 24 },
{ sprite: "grass_tile", x: 24, y: 24 },
],
}
Variant: Color Swap
Prompt:
Create a hero sprite, then make a variant with red hair for an "enemy" version
Output:
{
type: "sprite",
name: "hero",
size: [8, 8],
palette: {
_: "transparent",
skin: "#FFCC99",
hair: "#8B4513",
shirt: "#4169E1",
},
regions: {
hair: { rect: [2, 0, 4, 2], z: 0 },
skin: { rect: [2, 2, 4, 2], z: 0 },
shirt: {
union: [
{ rect: [1, 4, 6, 3] },
{ rect: [0, 4, 1, 2] }, // Left arm
{ rect: [7, 4, 1, 2] }, // Right arm
],
z: 0,
},
},
}
{
type: "variant",
name: "enemy",
base: "hero",
palette: {
hair: "#FF0000",
shirt: "#8B0000",
},
}
Tips for Writing Prompts
- Be specific about size: "16x16", "32x32", "8x8"
- Name colors explicitly: "silver blade", "golden hilt", "brown handle"
- Reference real games: "like Zelda items", "SNES-era style"
- Request semantic tokens: "use descriptive token names like skin and hair"
- Ask for palettes separately: "first create a palette, then the sprite"
- Request structured regions: "use rect, circle, and points shapes"
Formatting Output
Use pxl fmt to clean up generated output:
# Format a generated file
pxl fmt generated.pxl
# Check if formatting is needed
pxl fmt --check generated.pxl
Troubleshooting
Common issues when generating Pixelsrc sprites with AI and how to fix them.
Format Issues
Invalid JSON
Problem: Output has syntax errors and won't parse.
Symptoms:
Error: expected ',' or '}' at line 1, column 42
Solutions:
-
Ask the AI to regenerate with explicit validation:
"Output valid JSON, one complete object per line. Verify each line is valid JSON before outputting."
-
Check for common mistakes:
- Missing commas between key-value pairs
- Trailing commas after last element
- Unquoted string values
- Single quotes instead of double quotes
-
Use
pxl fmtto attempt auto-repair:pxl fmt generated.pxl
Wrong Token Format
Problem: Using [skin] or skin instead of {skin}.
Solutions:
-
Include example in prompt:
"Use curly brace token format: {skin}, {hair}, {_}"
-
Show the exact format needed:
"Tokens must be wrapped in curly braces. Example:
{outline}{skin}{skin}{outline}"
Inconsistent Grid Width
Problem: Rows have different numbers of tokens.
Symptoms:
Warning: Row 3 has 15 tokens but expected 16
Solutions:
-
Ask for explicit size validation:
"Ensure each row has exactly 16 tokens"
-
Use strict mode to catch issues:
pxl render output.pxl --strict -
Request the AI double-check:
"After generating, verify that every grid row has exactly the same number of tokens"
Visual Issues
Missing Transparency
Problem: Background is solid instead of transparent.
Solutions:
-
Explicitly request transparency:
"Use {_} mapped to #00000000 for all background pixels"
-
Verify the hex code has alpha:
"{_}": "#00000000" // Correct: 8 digits with 00 alpha "{_}": "#000000" // Wrong: 6 digits, fully opaque black
Magenta Pixels in Output
Problem: Rendered image has bright magenta pixels.
Cause: Magenta (#FF00FF) is the error color for undefined tokens.
Solutions:
-
Check for typos in token names:
// In palette "{skin}": "#FFCC99" // In grid - typo! "{skn}{skn}{skn}" -
Ensure all tokens used in grid are defined in palette
-
Use
pxl explainto see which tokens are missing:pxl explain generated.pxl
Colors Look Wrong
Problem: Colors don't match expectations.
Solutions:
-
Verify hex format:
#RGB- 3 digits, expanded to 6#RGBA- 4 digits, expanded to 8#RRGGBB- 6 digits, standard#RRGGBBAA- 8 digits, with alpha
-
Check for RGB vs BGR confusion (rare)
-
Request specific hex values:
"Use #FF0000 for red, not #F00"
Generation Quality Issues
Sprite Looks Stretched/Wrong Proportions
Problem: 16x16 sprite has 15 or 17 rows.
Solutions:
-
Be explicit about both dimensions:
"Create a 16 pixels wide by 16 pixels tall sprite with exactly 16 rows and 16 tokens per row"
-
Verify after generation:
pxl render generated.pxl --strict
Animation Frames Don't Match
Problem: Frames have different sizes or character shifts position.
Solutions:
-
Define constraints clearly:
"All 4 frames must be exactly 16x16. Keep the character centered. Only animate the legs - head and torso stay in the same position."
-
Request shared palette first:
"First create a palette called 'walk_colors'. Then create 4 frames all using that palette with identical dimensions."
Details Too Small/Large
Problem: Requested 16x16 but details are too fine or too coarse.
Solutions:
-
Reference pixel art style:
"NES-style with chunky 2-3 pixel wide lines" "Detailed SNES-style with 1-pixel outlines"
-
Specify detail level:
"Simple silhouette with minimal detail" "Detailed with visible features like eyes and clothing seams"
Workflow Issues
Inconsistent Palettes Across Sprites
Problem: Related sprites have different colors.
Solutions:
-
Define palette first:
"Create a palette named 'character_colors' with {skin}: #FFCC99, {hair}: #8B4513, etc. Then create all frames using this palette by name."
-
Use variants for color swaps instead of regenerating:
{"type": "variant", "name": "hero_red", "base": "hero", "palette": {"{hair}": "#FF0000"}}
Tiles Don't Seamlessly Connect
Problem: Edges don't match when tiles are placed adjacent.
Solutions:
-
Request seamless design:
"The left edge must match the right edge. The top edge must match the bottom edge. Test by mentally placing 4 copies in a 2x2 grid."
-
Generate multiple tiles with shared edge constraints:
"Create grass_1 and grass_2 that can be placed adjacent in any order"
Large Image Generation Fails
Problem: AI produces inconsistent or truncated output for large sprites.
Solutions:
-
Use composition with smaller tiles:
"Create four 16x16 tiles, then compose them into a 32x32 image"
-
Generate in stages:
Step 1: Create the palette Step 2: Create top-left 16x16 quadrant Step 3: Create top-right 16x16 quadrant ... Final: Compose into 32x32
Verification Commands
# Basic render test
pxl render sprite.pxl -o test.png
# Strict validation (fails on any issue)
pxl render sprite.pxl --strict
# Format check
pxl fmt --check sprite.pxl
# Explain structure
pxl explain sprite.pxl
# Validate without rendering
pxl validate sprite.pxl
Recovery Strategies
When All Else Fails
- Start fresh: Ask for a complete regeneration with all constraints restated
- Simplify: Request a smaller sprite first, then scale up
- Manual fix: Edit the JSONL directly - it's just text
- Use lenient mode: Let Pixelsrc auto-fix minor issues (default behavior)
Common Fix Patterns
# Auto-format fixes many issues
pxl fmt broken.pxl -o fixed.pxl
# Lenient render shows warnings but produces output
pxl render broken.pxl -o output.png
# Strict mode for CI/validation only
pxl render sprite.pxl --strict
Antialiasing
Semantic-aware antialiasing for pixel art upscaling and edge smoothing.
Overview
Pixelsrc provides optional antialiasing that uses semantic metadata to make intelligent smoothing decisions. Unlike traditional algorithms that treat all pixels equally, semantic-aware antialiasing respects the meaning of your pixel art - preserving crisp details like eyes while smoothing large fills.
By default, antialiasing is disabled to preserve the authentic pixel art aesthetic.
// Enable antialiasing in palette
{
type: "palette",
name: "hero",
antialias: {
enabled: true,
algorithm: "hq2x",
strength: 0.5
}
}
Algorithms
| Algorithm | Scale | Quality | Speed | Best For |
|---|---|---|---|---|
nearest | 1x | - | Fastest | No antialiasing (default) |
scale2x | 2x | Good | Fast | Simple upscaling, retro look |
hq2x | 2x | Better | Medium | Balanced quality/performance |
hq4x | 4x | Better | Medium | Higher resolution output |
xbr2x | 2x | Best | Slower | Maximum quality at 2x |
xbr4x | 4x | Best | Slower | Maximum quality at 4x |
aa-blur | 1x | Subtle | Fast | Edge softening without scaling |
nearest
No antialiasing applied. Preserves crisp pixel edges exactly as authored. This is the default behavior.
scale2x
The Scale2x (EPX) algorithm performs 2x upscaling with edge-aware interpolation. It detects edges and curves to produce smoother diagonals while preserving sharp edges.
Input pixel P with neighbors:
A
B P C
D
Output 2x2 block:
E0 E1
E2 E3
The algorithm applies corner smoothing based on neighbor similarity patterns.
hq2x / hq4x
HQ2x and HQ4x use pattern-based interpolation with YUV color comparison. They examine a 3x3 neighborhood around each pixel and apply interpolation rules based on which neighbors are "similar" in perceptual color space.
Key features:
- YUV color comparison for perceptually accurate edge detection
- Configurable similarity threshold
- Smooth diagonal handling
xbr2x / xbr4x
xBR (Scale By Rules) algorithms provide the highest quality upscaling through edge direction and curvature analysis. They examine a 5x5 neighborhood to detect complex edge patterns.
Key features:
- Best visual quality for diagonal lines and curves
- Weighted edge direction detection
- Superior handling of complex pixel art patterns
aa-blur
Gaussian blur with semantic masking. Unlike scaling algorithms, aa-blur operates at 1x resolution and selectively smooths edges based on semantic roles:
| Role | Blur Weight | Effect |
|---|---|---|
| Anchor | 0% | No blur (crisp details) |
| Boundary | 25% | Light blur (soft edges) |
| Fill | 100% | Full blur (smooth fills) |
| Shadow/Highlight | 100% | Full blur (smooth gradients) |
Configuration
Basic Options
{
antialias: {
// Enable antialiasing (default: false)
enabled: true,
// Algorithm selection (default: "nearest")
algorithm: "hq2x",
// Smoothing strength 0.0-1.0 (default: 0.5)
strength: 0.5
}
}
Anchor Mode
Controls how anchor pixels (important details like eyes) are handled:
{
antialias: {
enabled: true,
algorithm: "xbr2x",
// anchor_mode options:
// - "preserve": No AA on anchors (default, keeps details crisp)
// - "reduce": 25% AA strength on anchors
// - "normal": Full AA on anchors (treat like any other region)
anchor_mode: "preserve"
}
}
Semantic Options
{
antialias: {
enabled: true,
algorithm: "hq4x",
// Smooth transitions at DerivesFrom boundaries (default: true)
// Creates gradual color blending between shadow/highlight and base
gradient_shadows: true,
// Respect ContainedWithin boundaries as hard edges (default: true)
// Prevents color bleeding between regions like eye and skin
respect_containment: true,
// Use semantic role information for AA decisions (default: false)
// When enabled, roles from palette guide smoothing choices
semantic_aware: true
}
}
Per-Region Overrides
Fine-grained control for specific regions:
{
antialias: {
enabled: true,
algorithm: "hq2x",
regions: {
// Preserve eyes completely (no AA)
eye: { preserve: true },
// Reduce AA on outline
outline: { mode: "reduce" },
// Disable gradient smoothing for hair shadow
"hair-shadow": { gradient: false }
}
}
}
Configuration Hierarchy
Antialiasing can be configured at multiple levels, with increasing precedence:
- pxl.toml defaults - Project-wide defaults
- Atlas-level - Per-atlas configuration
- Per-sprite - Individual sprite settings
- CLI flags - Highest priority (overrides everything)
In pxl.toml
[defaults]
antialias_algorithm = "scale2x"
antialias_strength = 0.5
[atlases.characters]
sources = ["sprites/characters/**"]
antialias = { algorithm = "hq4x", strength = 0.7 }
Per-Sprite in Palette
{
type: "palette",
name: "hero",
antialias: {
enabled: true,
algorithm: "xbr2x",
semantic_aware: true
}
}
CLI Usage
Basic Rendering
# Render with antialiasing
pxl render sprite.pxl --antialias hq2x
# Render with strength control
pxl render sprite.pxl --antialias xbr4x --aa-strength 0.8
# Disable semantic awareness
pxl render sprite.pxl --antialias hq2x --no-semantic-aa
Available Flags
| Flag | Description |
|---|---|
--antialias <algo> | Select algorithm (scale2x, hq2x, hq4x, xbr2x, xbr4x, aa-blur) |
--aa-strength <0.0-1.0> | Smoothing intensity |
--anchor-mode <mode> | preserve, reduce, or normal |
--no-semantic-aa | Disable semantic-aware processing |
--no-gradient-shadows | Disable gradient smoothing |
Build Command
# Build all with default AA settings from pxl.toml
pxl build
# Override AA for this build
pxl build --antialias hq4x
Semantic Integration
Antialiasing integrates with semantic metadata to make intelligent smoothing decisions.
Roles Affect Smoothing
{
type: "palette",
colors: {
outline: "#000000",
skin: "#FFD5B4",
eye: "#4169E1"
},
roles: {
outline: "boundary", // Light smoothing (25%)
skin: "fill", // Full smoothing
eye: "anchor" // Preserved (0% by default)
},
antialias: {
enabled: true,
algorithm: "hq2x",
semantic_aware: true
}
}
Relationships Guide Blending
{
relationships: {
// DerivesFrom: Creates smooth gradient at boundary
"skin-shadow": {
type: "derives-from",
target: "skin"
},
// ContainedWithin: Hard edge, no blending across boundary
pupil: {
type: "contained-within",
target: "eye"
}
}
}
DerivesFrom relationships enable gradient smoothing - the transition between skin-shadow and skin will be gradual rather than a hard edge.
ContainedWithin relationships create hard boundaries - the pupil will never blend into surrounding eye pixels, maintaining crisp definition.
Examples
Retro Upscaling
For a classic scaled-up look with minimal smoothing:
{
antialias: {
enabled: true,
algorithm: "scale2x",
strength: 0.3,
anchor_mode: "preserve"
}
}
High-Quality Character Sprites
For smooth character art at high resolution:
{
antialias: {
enabled: true,
algorithm: "xbr4x",
strength: 0.7,
semantic_aware: true,
gradient_shadows: true
},
roles: {
outline: "boundary",
eye: "anchor",
skin: "fill",
"skin-shadow": "shadow"
},
relationships: {
"skin-shadow": { type: "derives-from", target: "skin" }
}
}
Subtle Edge Softening
For slight smoothing without scaling:
{
antialias: {
enabled: true,
algorithm: "aa-blur",
strength: 0.4,
semantic_aware: true,
anchor_mode: "preserve"
}
}
Pure Algorithm (No Semantic Awareness)
For traditional upscaling behavior:
{
antialias: {
enabled: true,
algorithm: "hq4x",
semantic_aware: false,
gradient_shadows: false,
respect_containment: false
}
}
Technical Notes
Scale Factors
Algorithms produce fixed output scales:
nearest,aa-blur: 1x (no scaling)scale2x,hq2x,xbr2x: 2xhq4x,xbr4x: 4x
When combining with manual scaling (--scale), antialiasing is applied first, then additional scaling uses nearest-neighbor.
Color Comparison
HQ and xBR algorithms use YUV color space for similarity comparisons:
- More perceptually accurate than RGB
- Green channel weighted most heavily (human eye sensitivity)
- Configurable threshold affects edge detection sensitivity
Performance Considerations
| Algorithm | Relative Speed |
|---|---|
| nearest | 1x (baseline) |
| aa-blur | ~2x |
| scale2x | ~3x |
| hq2x | ~5x |
| hq4x | ~8x |
| xbr2x | ~10x |
| xbr4x | ~20x |
For large atlases, consider using scale2x or hq2x for faster builds, reserving xbr4x for final production renders.
Related
- Semantic Metadata - Roles and relationships
- render command - Rendering options
- Configuration - pxl.toml reference
Built-in Palettes
Pixelsrc includes several built-in palettes that can be referenced by name using the @name syntax. These palettes provide curated color sets for common pixel art styles.
Using Built-in Palettes
Reference a built-in palette with the @ prefix:
{"type": "sprite", "name": "retro_heart", "size": [3, 3], "palette": "@gameboy", "regions": {"dark": {"union": [{"points": [[1, 0]]}, {"points": [[0, 1], [2, 1]]}, {"points": [[1, 2]]}], "z": 0}, "light": {"points": [[1, 1]], "z": 1}}}
List available palettes:
pxl palettes
Show palette colors:
pxl palettes --show gameboy
Available Palettes
gameboy
Classic Game Boy 4-color green palette.
| Token | Color | Hex |
|---|---|---|
{_} | Transparent | #00000000 |
{lightest} | ![]() | #9BBC0F |
{light} | ![]() | #8BAC0F |
{dark} | ![]() | #306230 |
{darkest} | ![]() | #0F380F |
Reference: Nintendo Game Boy (BGB) on Lospec
nes
NES-inspired palette with key representative colors.
| Token | Color | Hex |
|---|---|---|
{_} | Transparent | #00000000 |
{black} | ![]() | #000000 |
{white} | ![]() | #FCFCFC |
{red} | ![]() | #A80020 |
{green} | ![]() | #00A800 |
{blue} | ![]() | #0058F8 |
{cyan} | ![]() | #00B8D8 |
{yellow} | ![]() | #F8D800 |
{orange} | ![]() | #F83800 |
{pink} | ![]() | #F878F8 |
{brown} | ![]() | #503000 |
{gray} | ![]() | #7C7C7C |
{skin} | ![]() | #FCB8B8 |
Reference: Nintendo Entertainment System on Lospec
pico8
PICO-8 fantasy console 16-color palette.
| Token | Color | Hex |
|---|---|---|
{_} | Transparent | #00000000 |
{black} | ![]() | #000000 |
{dark_blue} | ![]() | #1D2B53 |
{dark_purple} | ![]() | #7E2553 |
{dark_green} | ![]() | #008751 |
{brown} | ![]() | #AB5236 |
{dark_gray} | ![]() | #5F574F |
{light_gray} | ![]() | #C2C3C7 |
{white} | ![]() | #FFF1E8 |
{red} | ![]() | #FF004D |
{orange} | ![]() | #FFA300 |
{yellow} | ![]() | #FFEC27 |
{green} | ![]() | #00E436 |
{blue} | ![]() | #29ADFF |
{indigo} | ![]() | #83769C |
{pink} | ![]() | #FF77A8 |
{peach} | ![]() | #FFCCAA |
Reference: PICO-8 on Lospec
grayscale
8-shade grayscale palette from white to black.
| Token | Color | Hex |
|---|---|---|
{_} | Transparent | #00000000 |
{white} | ![]() | #FFFFFF |
{gray1} | ![]() | #DFDFDF |
{gray2} | ![]() | #BFBFBF |
{gray3} | ![]() | #9F9F9F |
{gray4} | ![]() | #7F7F7F |
{gray5} | ![]() | #5F5F5F |
{gray6} | ![]() | #3F3F3F |
{black} | ![]() | #000000 |
1bit
Minimal 1-bit black and white palette.
| Token | Color | Hex |
|---|---|---|
{_} | Transparent | #00000000 |
{black} | ![]() | #000000 |
{white} | ![]() | #FFFFFF |
dracula
Dracula theme palette for code editor aesthetics.
| Token | Color | Hex |
|---|---|---|
{_} | Transparent | #00000000 |
{background} | ![]() | #282A36 |
{current} | ![]() | #44475A |
{foreground} | ![]() | #F8F8F2 |
{comment} | ![]() | #6272A4 |
{cyan} | ![]() | #8BE9FD |
{green} | ![]() | #50FA7B |
{orange} | ![]() | #FFB86C |
{pink} | ![]() | #FF79C6 |
{purple} | ![]() | #BD93F9 |
{red} | ![]() | #FF5555 |
{yellow} | ![]() | #F1FA8C |
Reference: Dracula Theme
Transparent Color
All built-in palettes include the special transparent token {_} mapped to #00000000. This is the conventional token for transparency in Pixelsrc sprites.
Extending Built-in Palettes
You can use a built-in palette and add custom colors:
{"type": "palette", "name": "custom_gb", "extends": "@gameboy", "colors": {"{highlight}": "#FFFF00"}}
Or override existing colors:
{"type": "palette", "name": "warm_gb", "extends": "@gameboy", "colors": {"{lightest}": "#E8D8A0"}}
Related
- Palette Format - Custom palette definition
- palettes command - Palette management CLI
- Color Formats - Supported color notation
Color Formats
Pixelsrc supports a variety of color formats for defining palette colors. All formats are parsed using a combination of optimized hex parsing and the lightningcss library for CSS color notation.
Quick Reference
| Format | Example | Notes |
|---|---|---|
| Hex (short) | #RGB, #RGBA | Each digit doubled |
| Hex (long) | #RRGGBB, #RRGGBBAA | Full precision |
| RGB | rgb(255, 0, 0) | Integer or percentage |
| HSL | hsl(0, 100%, 50%) | Hue, saturation, lightness |
| HWB | hwb(0 0% 0%) | Hue, whiteness, blackness |
| OKLCH | oklch(0.628 0.258 29.23) | Perceptually uniform |
| color-mix | color-mix(in oklch, red 70%, black) | Blend two colors |
| Named | red, blue, transparent | CSS named colors |
Hex Colors
The most common format for pixel art. Fast to parse and easy to read.
Short Form (#RGB, #RGBA)
Each digit is doubled to get the full value:
| Short | Expands To | Result |
|---|---|---|
#F00 | #FF0000 | Red (255, 0, 0) |
#0F0 | #00FF00 | Green (0, 255, 0) |
#00F | #0000FF | Blue (0, 0, 255) |
#ABC | #AABBCC | (170, 187, 204) |
#F008 | #FF000088 | Red with ~53% alpha |
Full Form (#RRGGBB, #RRGGBBAA)
Full precision hex values:
#RRGGBB
│ │ └── Blue (00-FF)
│ └──── Green (00-FF)
└────── Red (00-FF)
Without an alpha channel, colors default to fully opaque (alpha = 255).
CSS Functional Notation
RGB/RGBA
{"colors": {
"{red}": "rgb(255, 0, 0)",
"{green}": "rgb(0, 255, 0)",
"{semi}": "rgba(255, 0, 0, 0.5)",
"{modern}": "rgb(255 0 0 / 50%)"
}}
HSL/HSLA
Useful for creating color variations by adjusting lightness:
{"colors": {
"{red}": "hsl(0, 100%, 50%)",
"{dark_red}": "hsl(0, 100%, 25%)",
"{light_red}": "hsl(0, 100%, 75%)"
}}
OKLCH
Perceptually uniform color space. Colors with the same lightness value appear equally bright:
{"colors": {
"{red}": "oklch(0.628 0.258 29.23)",
"{green}": "oklch(0.866 0.295 142.5)"
}}
color-mix() Function
Blend two colors in a specified color space. Ideal for generating shadow and highlight variants.
Syntax
color-mix(in <color-space>, <color1> [<percentage>], <color2> [<percentage>])
Shadow Generation
Use oklch for perceptually uniform darkening:
{"colors": {
"--skin": "#FFCC99",
"{skin}": "var(--skin)",
"{skin_shadow}": "color-mix(in oklch, var(--skin) 70%, black)",
"{skin_deep}": "color-mix(in oklch, var(--skin) 50%, black)"
}}
| Pattern | Effect |
|---|---|
color-mix(in oklch, <color> 70%, black) | Light shadow (30% darker) |
color-mix(in oklch, <color> 50%, black) | Medium shadow |
color-mix(in oklch, <color> 30%, black) | Deep shadow |
Highlight Generation
{"colors": {
"--primary": "#3366CC",
"{primary}": "var(--primary)",
"{primary_light}": "color-mix(in srgb, var(--primary) 70%, white)"
}}
Complete Character Palette
{"type": "palette", "name": "character", "colors": {
"--skin-base": "#FFCC99",
"--hair-base": "#5D3A29",
"{_}": "transparent",
"{outline}": "#222034",
"{skin}": "var(--skin-base)",
"{skin_hi}": "color-mix(in srgb, var(--skin-base) 60%, white)",
"{skin_sh}": "color-mix(in oklch, var(--skin-base) 70%, black)",
"{hair}": "var(--hair-base)",
"{hair_hi}": "color-mix(in srgb, var(--hair-base) 60%, white)",
"{hair_sh}": "color-mix(in oklch, var(--hair-base) 70%, black)"
}}
Why OKLCH for shadows? OKLCH provides perceptually uniform blending - a 30% darkening looks equally dark across all hues. With sRGB, some colors darken more dramatically than others.
Named Colors
All 147 CSS named colors are supported:
| Name | Hex Equivalent |
|---|---|
transparent | #00000000 |
black | #000000 |
white | #FFFFFF |
red | #FF0000 |
green | #008000 (not #00FF00!) |
blue | #0000FF |
Warning: CSS green is #008000. Use lime for pure green (#00FF00).
Transparency
Fully Transparent
Convention: use {_} token:
{"colors": {
"{_}": "transparent",
"{_}": "#00000000",
"{_}": "rgba(0, 0, 0, 0)"
}}
Semi-Transparent
{"colors": {
"{glass}": "#FFFFFF80",
"{shadow}": "rgba(0, 0, 0, 0.3)"
}}
Error Messages
| Error | Cause | Example |
|---|---|---|
empty color string | Empty value | "" |
color must start with '#' | Missing hash (for hex) | "FF0000" |
invalid color length N | Wrong digit count | "#FF00" |
invalid hex character 'X' | Non-hex character | "#GG0000" |
CSS parse error: ... | Invalid CSS syntax | "notacolor" |
Invalid colors in lenient mode render as magenta (#FF00FF) to make them visible.
Best Practices
- Use hex for simple colors -
#F00is clearer thanrgb(255, 0, 0) - Use color-mix for shadows/highlights - Consistent shading from base colors
- Use oklch for shadows - Perceptually uniform darkening across all hues
- Use CSS variables with color-mix - Define base colors once, derive variants
- Use
transparentover#00000000- More readable - Test at 1x scale - Ensure colors are distinguishable at native size
Related
- CSS Variables - Custom properties and var()
- Built-in Palettes - Pre-defined color sets
- Palette Format - Defining custom palettes
Exit Codes
Pixelsrc CLI commands return standard exit codes to indicate success or failure. These codes are useful for scripting and CI/CD integration.
Exit Code Summary
| Code | Name | Description |
|---|---|---|
| 0 | Success | Command completed successfully |
| 1 | Error | Runtime error during execution |
| 2 | Invalid Arguments | Invalid command-line arguments |
Exit Code Details
0 - Success
The command completed without errors.
pxl render sprite.pxl -o sprite.png
echo $? # 0
1 - Error
A runtime error occurred during execution. Common causes:
- File not found or unreadable
- Invalid JSON/JSONL syntax
- Missing required fields in definitions
- Color parsing errors
- Render failures
- I/O errors writing output files
pxl render nonexistent.pxl -o out.png
echo $? # 1
# Error: failed to read input file: No such file or directory
2 - Invalid Arguments
Command-line arguments are invalid or missing required values. Common causes:
- Missing required arguments
- Invalid option values
- Mutually exclusive options used together
- Unknown subcommand or option
pxl render
echo $? # 2
# Error: the following required arguments were not provided: <INPUT>
Using Exit Codes in Scripts
Bash
#!/bin/bash
if pxl validate sprites/*.pxl; then
echo "All sprites valid"
pxl build
else
echo "Validation failed"
exit 1
fi
Make
sprites: $(wildcard src/*.pxl)
pxl build || exit 1
validate:
pxl validate src/*.pxl
CI/CD (GitHub Actions)
- name: Validate sprites
run: pxl validate src/**/*.pxl
- name: Build assets
run: pxl build
# Fails the job if exit code != 0
Command-Specific Behavior
validate
Returns exit code 1 if any validation errors are found:
pxl validate sprite.pxl
# Exit 0: No errors
# Exit 1: Validation errors found
With --strict, warnings also cause exit code 1.
build
Returns exit code 1 if any file fails to build:
pxl build
# Exit 0: All files built successfully
# Exit 1: One or more files failed
diff
Returns exit code 0 even when differences are found (differences are not errors):
pxl diff a.pxl b.pxl
# Exit 0: Comparison completed (may have differences)
# Exit 1: Error reading files
render
Returns exit code 1 for any render failure:
pxl render sprite.pxl -o out.png
# Exit 0: Rendered successfully
# Exit 1: Render failed
Error Output
Error messages are written to stderr, allowing you to separate them from normal output:
# Capture errors separately
pxl validate sprites/*.pxl 2>errors.txt
# Suppress errors
pxl validate sprites/*.pxl 2>/dev/null
Related
- validate command - Validation options
- build command - Build system
- Configuration - Build configuration
Configuration (pxl.toml)
Project configuration file for Pixelsrc builds and exports.
Overview
Create a pxl.toml file in your project root to configure the build system. The only required field is project.name.
[project]
name = "my-game"
Full Example
[project]
name = "my-game"
version = "1.0.0"
src = "assets/pxl"
out = "dist"
[defaults]
scale = 2
padding = 4
[atlases.characters]
sources = ["sprites/player/**", "sprites/enemies/**"]
max_size = [2048, 2048]
padding = 2
power_of_two = true
[atlases.ui]
sources = ["sprites/ui/**"]
max_size = [1024, 1024]
nine_slice = true
[animations]
sources = ["anims/**"]
preview = true
preview_scale = 4
sheet_layout = "vertical"
[export.generic]
enabled = true
atlas_format = "json"
[export.godot]
enabled = true
resource_path = "res://sprites"
animation_player = true
sprite_frames = true
[export.unity]
enabled = true
pixels_per_unit = 32
filter_mode = "point"
[export.libgdx]
enabled = true
[validate]
strict = true
unused_palettes = "warn"
missing_refs = "error"
[watch]
debounce_ms = 200
clear_screen = false
Sections
[project]
Project metadata and directory configuration.
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Project name |
version | string | "0.1.0" | Project version |
src | path | "src/pxl" | Source directory for .pxl files |
out | path | "build" | Output directory for rendered assets |
[project]
name = "my-game"
version = "1.0.0"
src = "assets/sprites"
out = "dist/sprites"
[defaults]
Default settings applied to all outputs unless overridden.
| Field | Type | Default | Description |
|---|---|---|---|
scale | integer | 1 | Default scale factor for rendering |
padding | integer | 1 | Default padding between sprites in atlases |
[defaults]
scale = 2
padding = 4
[atlases.<name>]
Define named atlas configurations. Multiple atlases can be defined.
| Field | Type | Default | Description |
|---|---|---|---|
sources | array | required | Glob patterns for sprite sources |
max_size | [w, h] | [1024, 1024] | Maximum atlas dimensions |
padding | integer | from defaults | Padding between sprites |
power_of_two | boolean | false | Constrain to power-of-two dimensions |
nine_slice | boolean | false | Preserve nine-slice metadata |
[atlases.characters]
sources = ["sprites/player/**", "sprites/enemies/**"]
max_size = [2048, 2048]
padding = 2
power_of_two = true
[atlases.ui]
sources = ["sprites/ui/**"]
max_size = [1024, 1024]
nine_slice = true
[animations]
Animation output configuration.
| Field | Type | Default | Description |
|---|---|---|---|
sources | array | ["animations/**"] | Glob patterns for animation files |
preview | boolean | false | Generate preview GIFs |
preview_scale | integer | 1 | Scale factor for previews |
sheet_layout | string | "horizontal" | Layout: horizontal, vertical, or grid |
[animations]
sources = ["anims/**", "characters/*/walk.pxl"]
preview = true
preview_scale = 4
sheet_layout = "vertical"
[export.generic]
Generic JSON export configuration.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable generic JSON export |
atlas_format | string | "json" | Output format identifier |
[export.generic]
enabled = true
atlas_format = "json"
[export.godot]
Godot game engine export configuration.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable Godot export |
atlas_format | string | "godot" | Output format identifier |
resource_path | string | "res://assets/sprites" | Godot resource path prefix |
animation_player | boolean | true | Generate AnimationPlayer resources |
sprite_frames | boolean | true | Generate SpriteFrames resources |
[export.godot]
enabled = true
resource_path = "res://sprites"
animation_player = true
sprite_frames = true
Generated files:
.tresresource files for atlasesAnimationPlayerresources for animationsSpriteFramesresources for animated sprites
[export.unity]
Unity game engine export configuration.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable Unity export |
atlas_format | string | "unity" | Output format identifier |
pixels_per_unit | integer | 16 | Pixels per Unity unit |
filter_mode | string | "point" | Texture filter: point or bilinear |
[export.unity]
enabled = true
pixels_per_unit = 32
filter_mode = "point"
Generated files:
.assetmeta files for sprites- Sprite slicing data for atlases
- Animation clips for animated sprites
[export.libgdx]
libGDX game framework export configuration.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable libGDX export |
atlas_format | string | "libgdx" | Output format identifier |
[export.libgdx]
enabled = true
Generated files:
.atlasfiles in libGDX TexturePacker format- Region definitions for sprite lookup
[validate]
Validation settings for the build process.
| Field | Type | Default | Description |
|---|---|---|---|
strict | boolean | false | Treat warnings as errors |
unused_palettes | level | "warn" | How to handle unused palettes |
missing_refs | level | "error" | How to handle missing references |
Validation levels: error, warn, ignore
[validate]
strict = true
unused_palettes = "error"
missing_refs = "warn"
[watch]
Watch mode configuration.
| Field | Type | Default | Description |
|---|---|---|---|
debounce_ms | integer | 100 | Debounce delay in milliseconds |
clear_screen | boolean | true | Clear terminal between rebuilds |
[watch]
debounce_ms = 200
clear_screen = false
Minimal Configuration
The simplest valid configuration:
[project]
name = "my-project"
This uses all default values:
- Source:
src/pxl/ - Output:
build/ - Scale: 1
- No exports enabled (except generic JSON)
Environment Variables
Some settings can be overridden via environment variables or command-line flags:
| Setting | CLI Flag | Environment Variable |
|---|---|---|
| Source directory | --src | - |
| Output directory | --out or -o | - |
| Verbose output | --verbose or -v | - |
Validation
The configuration is validated on load. Common validation errors:
| Error | Cause |
|---|---|
project.name must be non-empty | Missing or empty project name |
defaults.scale must be positive | Scale set to 0 |
atlases.\<name\>.sources must contain at least one glob pattern | Empty sources array |
atlases.\<name\>.max_size dimensions must be positive | Zero dimension in max_size |
export.unity.pixels_per_unit must be positive | Zero pixels_per_unit with Unity enabled |
Related
- build command - Build system usage
- Build System Integration - CI/CD integration
- validate command - Validation options
















































