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?

Tips

  • Use semantic token names like skin, outline, shadow instead of generic names
  • The _ token is the conventional name for transparent pixels
  • Use shapes (rect, circle, ellipse) for efficiency over individual points
  • Higher z values 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:

TypePurposeRequired Fields
paletteDefine named colorsname, colors
spriteStructured regionsname, size, palette, regions
animationFrame sequencename, frames
compositionLayer spritesname, size, layers
variantModify existing spritename, 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 shadow more 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 definitions
  • z: Controls draw order (higher draws on top)
  • palette: Name of a defined palette or inline colors object

Shape Primitives

ShapeSyntaxDescription
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 order
  • duration: 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 .pxl extension

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:

FieldTypeDefaultDescription
namestringrequiredAnimation identifier
framesarrayrequiredSprite names in sequence
durationnumber100Milliseconds per frame
loopbooleantrueWhether to loop

Tips

  • Reuse frames - Repeat sprite names in the frames array for timing control
  • Frame duration - Lower values = faster animation
  • Scale - Always scale up for previews; pixel art is small!
  • Validate first - Run pxl validate before 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

  1. Create a .pxl file with minimal setup
  2. Preview in terminal with pxl show
  3. Iterate rapidly until the concept feels right
  4. 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:

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

  1. Design a thoughtful palette with semantic tokens
  2. Build sprites using meaningful color names
  3. Create variants for different states or colors
  4. Compose complex scenes from layered sprites
  5. 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_shadow is clearer than #E8B4A0
  • Maintainability: Change a color once, update everywhere
  • Reusability: Share palettes across multiple sprites
  • AI-friendly: LLMs can reason about outline reliably

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

  1. Create keyframe sprites
  2. Define animation sequences
  3. Preview and adjust timing
  4. 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 TypeTypical Frames
Idle breathing2-4 frames
Walk cycle4-8 frames
Run cycle6-8 frames
Attack3-6 frames
Jump4-6 frames
Death4-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

The Tool Builder

You build pipelines and automation. You want reliable validation, reproducible builds, and seamless CI/CD integration.

Your Workflow

  1. Set up a project with pxl init
  2. Configure validation rules
  3. Integrate with build systems
  4. 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

  1. Create sprites and animations in Pixelsrc
  2. Export to spritesheets or atlas formats
  3. Import into your game engine
  4. 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 image
  • characters.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

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: skin is 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

  1. Write or refine a system prompt
  2. Give the AI examples and constraints
  3. Generate sprites iteratively
  4. 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 generation
  • animation - Animation sequences
  • character - Full character with variants
  • tileset - 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

  1. Generate: Ask AI for a sprite
  2. Save: Copy output to sprite.pxl
  3. Validate: pxl validate sprite.pxl
  4. Preview: pxl show sprite.pxl
  5. 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

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:

TypePurposeLearn More
paletteDefine named color tokens with roles and relationshipsPalette
spriteDefine a pixel image using regionsSprite
state_rulesDefine visual states and effectsState Rules
animationSequence sprites over timeAnimation
variantCreate color variationsVariant
compositionLayer sprites togetherComposition

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)

ErrorBehavior
Unknown tokenRender as magenta #FF00FF
Region outside canvasClipped with warning
Duplicate nameLast definition wins
Invalid colorUse magenta placeholder
Missing paletteAll 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:

FormatExampleDescription
#RGB#F00Expands to #RRGGBB (red)
#RGBA#F00FExpands to #RRGGBBAA (red, opaque)
#RRGGBB#FF0000Fully opaque color
#RRGGBBAA#FF000080With alpha channel
rgb()rgb(255, 0, 0)CSS RGB notation
hsl()hsl(0, 100%, 50%)CSS HSL notation
Namedred, transparentCSS 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:

  1. Objects are parsed as complete JSON5 values
  2. Objects are processed in order of appearance
  3. Palettes must be defined before sprites that reference them
  4. Regions within a sprite must define dependencies before dependents

Exit Codes

CodeMeaning
0Success (lenient: may have warnings)
1Error (strict: any warning; lenient: fatal error)
2Invalid 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

FieldRequiredDescription
typeYesMust be "palette"
nameYesUnique identifier, referenced by sprites
colorsYesMap of token names to color values
rolesNoSemantic roles for tokens
relationshipsNoToken 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:

FormatExampleDescription
#RGB#F00Short hex (expands to #FF0000)
#RGBA#F008Short hex with alpha
#RRGGBB#FF0000Full hex
#RRGGBBAA#FF000080Full 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 outlines
  • anchor - Critical details (eyes, etc.)
  • fill - Large interior areas
  • shadow - Shading regions
  • highlight - 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 another
  • contained-within - Region contained in another
  • adjacent-to - Regions share boundary
  • paired-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 --:

SyntaxDescription
"--name": "value"Define a variable
"--kebab-case": "#hex"Convention: use kebab-case

Variables are resolved in a two-pass process:

  1. Collection pass: All --name entries are collected
  2. 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):

SyntaxDescription
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

  1. Use descriptive names: --primary-bg over --bg1
  2. Group related variables: Keep theme colors together
  3. Provide fallbacks for optional variables: Enables safe customization
  4. Use kebab-case: Matches CSS convention (--my-color)
  5. 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:

FormatExampleDescription
#RGB#F00Short form (expands to #RRGGBB)
#RRGGBB#FF0000Full form
#RRGGBBAA#FF0000FFWith 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)"
  }
}
FormatExampleDescription
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%)"
  }
}
ParameterRangeDescription
Hue0-360Color wheel angle (0=red, 120=green, 240=blue)
Saturation0-100%Color intensity (0=gray, 100=vivid)
Lightness0-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)"
  }
}
ParameterRangeDescription
Lightness0-100%Perceived brightness
Chroma0-0.4Color intensity
Hue0-360Color 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%)"
  }
}
ParameterRangeDescription
Hue0-360Color wheel angle
Whiteness0-100%Amount of white mixed in
Blackness0-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)"
  }
}
SyntaxDescription
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

  1. Use hex for exact colors: When you know the exact RGB values
  2. Use HSL for variations: Easy to create lighter/darker/muted versions
  3. Use OKLCH for palettes: Consistent perceived brightness across colors
  4. Use color-mix for blends: Create intermediate shades programmatically
  5. 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:

FunctionDescriptionUse Case
linearConstant speedMechanical motion, progress bars
easeSlow start/end, fast middleGeneral-purpose, natural feel
ease-inSlow start, fast endObjects accelerating
ease-outFast start, slow endObjects decelerating
ease-in-outSlow start and endSmooth 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)"
}
ParameterRangeDescription
x10-1First control point X
y1anyFirst control point Y
x20-1Second control point X
y2anySecond control point Y

Common Custom Curves

Namecubic-bezierEffect
Bouncecubic-bezier(0.68, -0.55, 0.27, 1.55)Overshoots then settles
Snapcubic-bezier(0.5, 0, 0.5, 1.5)Quick with overshoot
Smoothcubic-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)"
}
SyntaxDescription
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

AliasEquivalent
step-startsteps(1, jump-start)
step-endsteps(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:

  1. Use steps() for sprite swaps: Prevents interpolation between discrete sprites
  2. Use linear for mechanical motion: Conveyor belts, rotating gears
  3. Use ease-out for impacts: Objects hitting the ground
  4. Use ease-in-out for 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)" }
  }
}
PercentageMeaning
0%Animation start
50%Animation midpoint
100%Animation end
Any valueIntermediate 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 }
  }
}
KeywordEquivalent
from0%
to100%

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

PropertyExampleDescription
translatetranslate(10, 5)Move position
rotaterotate(90deg)Rotate clockwise
scalescale(2)Scale size
flipflip(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 }
  }
}
ValueDescription
1.0Fully opaque
0.550% transparent
0.0Fully 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

  1. Use steps() for sprite swaps: Prevents blurry transitions
  2. Keep transform chains consistent: Same transforms in same order across keyframes
  3. Use from/to for simple animations: More readable than 0%/100%
  4. Add intermediate keyframes for complex motion: Control the path, not just start/end
  5. Match loop points: For looping animations, ensure 100% matches 0%

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:

  1. Add complexity without pixel art benefit - Features designed for responsive web layouts
  2. Require browser/runtime context - Features that depend on DOM state or inheritance
  3. Have poor GenAI reliability - Syntax that LLMs frequently generate incorrectly
  4. Exceed scope - Full CSS engine features beyond color/transform/timing

Colors

Not Supported

FeatureSyntaxWhy 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
currentColorcurrentColorNo CSS cascade; colors are explicit per-palette
System colorsCanvas, CanvasTextNo browser context; pixel art needs explicit colors
Relative color syntaxhsl(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 than lch()
  • 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

FeatureSyntaxWhy 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

FeatureSyntaxWhy Excluded
linear() with stopslinear(0, 0.25 75%, 1)Complex piecewise timing rarely needed
spring()spring(1 100 10 0)Proposed CSS; not standardized

Supported

FeatureSyntaxNotes
linearlinearConstant speed
easeeaseSmooth acceleration/deceleration
ease-inease-inSlow start
ease-outease-outSlow end
ease-in-outease-in-outSlow 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

FeatureSyntaxWhy Excluded
skew()skew(10deg)Distorts pixel grid; produces blurry output
matrix()matrix(1, 0, 0, 1, 0, 0)Low-level; use individual transforms
3D transformsrotateX(), translateZ()2D only; pixel art is flat
transform-origintransform-origin: top leftAdds complexity; anchor points handled differently

Supported

FeatureSyntaxNotes
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 increments
  • scale() works best with integer factors
  • translate() 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

FeatureWhy Excluded
hueRequires HSL color space conversion per-pixel
saturationRequires HSL color space conversion per-pixel
colorRequires HSL color space conversion per-pixel
luminosityRequires HSL color space conversion per-pixel
plus-lighterNon-standard; limited support
plus-darkerNon-standard; limited support

Supported

ModeUse Case
normalStandard alpha compositing
multiplyShadows, color tinting
screenGlows, highlights
overlayContrast enhancement
addAdditive glow effects
subtractSpecial effects
differenceMasks, inversions
darkenShadow overlays
lightenHighlight 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

FeatureSyntaxWhy Excluded
Multiple animationsanimation: a 1s, b 2sOne animation per sprite; use compositions
animation-delayanimation-delay: 500msUse keyframe percentages instead
animation-directionanimation-direction: reverseUse explicit keyframe ordering
animation-fill-modeanimation-fill-mode: forwardsAnimations loop or stop; no fill state
animation-play-stateanimation-play-state: pausedRuntime control not applicable

Supported

FeatureDescription
Keyframes0%, 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 cyclingColor rotation animations
Frame tagsNamed 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:

  1. Reliability - Features GenAI can generate correctly on first attempt
  2. Simplicity - Flat palettes, explicit colors, 2D transforms
  3. 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 syntaxcolor-mix()
calc()Pre-computed values
skew()Source art
3D transforms2D transforms
animation-delayKeyframe percentages
Multiple animationsCompositions

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

FieldRequiredDescription
typeYesMust be "sprite"
nameYesUnique identifier
sizeYes[width, height] in pixels
paletteYesPalette name to use for colors
regionsYesMap of token names to region definitions

Optional Fields

FieldDescription
backgroundToken to fill empty pixels (default: _)
originAnchor point [x, y] for transforms
metadataCustom data passthrough for game engines
state-rulesName 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

FieldPurpose
originSprite anchor point [x, y]
boxes.hurtDamage-receiving region
boxes.hitDamage-dealing region
boxes.collidePhysics collision boundary
boxes.triggerInteraction 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)

ErrorBehavior
Unknown tokenRender as magenta #FF00FF
Region outside canvasClip to canvas with warning
Forward reference in fillError (must define dependencies first)
Missing paletteAll 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 union of 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 to
  • L x,y - Line to
  • H x - Horizontal line to
  • V y - Vertical line to
  • Z - 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) - supports deg or rad
  • scale(x, y) or scale(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:

  1. 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.

  2. 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

RoleMeaningTransform Behavior
boundaryEdge-defining (outlines)High priority, preserve connectivity
anchorCritical details (eyes, buttons)Must survive transforms (min 1px)
fillInterior mass (skin, clothes)Can shrink, low priority
shadowDepth indicatorsDerives from parent
highlightLight indicatorsDerives 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

RelationshipMeaning
derives-fromColor derived from another token
contained-withinSpatially inside another region
adjacent-toMust touch specified region
paired-withSymmetric 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-within checked against actual pixel positions
  • adjacent-to verified that regions share at least one edge
  • paired-with validated 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-from shadows

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

FieldRequiredDescription
typeYesMust be "state_rules"
nameYesUnique identifier for this rule set
rulesYesMap of CSS selectors to state effects

Selectors

State rules use a CSS-like selector syntax to target tokens.

Token Selectors

SelectorExampleMeaning
[token=name][token=eye]Exact token match
[token*=str][token*=skin]Token contains substring
[token][token]Any token (wildcard)

Role Selectors

SelectorExampleMeaning
[role=type][role=boundary]Match by role

State Classes

SelectorExampleMeaning
.state.damagedMatch 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 contrast
  • saturate(n) - Adjust saturation
  • hue-rotate(deg) - Shift hue
  • invert(n) - Invert colors
  • grayscale(n) - Convert to grayscale
  • sepia(n) - Apply sepia tone
  • drop-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

FieldRequiredDefaultDescription
typeYes-Must be "animation"
nameYes-Unique identifier
keyframesYes-Map of percentage keys to keyframe objects
durationNo100Total animation duration (ms or CSS time string)
timing_functionNo"linear"CSS timing function for easing
loopNotrueWhether animation loops

Keyframe Object Fields

Each keyframe can specify any combination of these properties:

FieldDescription
spriteSprite name to display at this keyframe
opacityOpacity from 0.0 (transparent) to 1.0 (opaque)
offsetPosition offset [x, y] in pixels
transformCSS 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:

FunctionDescription
linearConstant speed (default)
easeSmooth acceleration and deceleration
ease-inSlow start, fast end
ease-outFast start, slow end
ease-in-outSlow 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

FieldRequiredDefaultDescription
typeYes-Must be "animation"
nameYes-Unique identifier
framesYes-Array of sprite names in order
durationNo100Milliseconds per frame
loopNotrueWhether 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

FieldRequiredDefaultDescription
spriteYes*-Single sprite to cycle (*required if no frames)
palette_cycleYes-Cycle definition object or array
palette_cycle.tokensYes-Ordered list of tokens to rotate
palette_cycle.fpsNo10Frames per second for cycling
palette_cycle.directionNo"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

FieldRequiredDefaultDescription
tagsNo-Map of tag name to tag definition
tags.{name}.startYes-Starting frame index (0-based)
tags.{name}.endYes-Ending frame index (inclusive)
tags.{name}.loopNotrueWhether this segment loops
tags.{name}.fpsNoinheritOverride 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

FieldRequiredDefaultDescription
attachmentsNo-Array of attachment definitions
attachments[].nameYes-Identifier for this attachment
attachments[].anchorYes-Attachment point [x, y] on parent sprite
attachments[].chainYes-Array of sprite names forming the chain
attachments[].delayNo1Frame delay between chain segments
attachments[].followNo"position""position", "velocity", or "rotation"
attachments[].dampingNo0.8Oscillation damping (0.0-1.0)
attachments[].stiffnessNo0.5Spring stiffness (0.0-1.0)
attachments[].z_indexNo0Render order (negative = behind parent)

Follow Modes

ModeBehavior
positionChain follows parent position directly
velocityChain responds to movement velocity
rotationChain 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

AspectFrames FormatKeyframes Format
Timingduration is per-frameduration is total animation time
StructureFlat sprite arrayPercentage-keyed objects
PropertiesSprite onlySprite, opacity, offset, transform
EasingN/Atiming_function for interpolation

Migration Steps

  1. Calculate total duration: Multiply per-frame duration by frame count

    • 4 frames × 100ms = 400ms total
  2. 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)
  3. Wrap sprites in keyframe objects: "walk_1" becomes { sprite: "walk_1" }

  4. 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

FieldRequiredDescription
typeYesMust be "variant"
nameYesUnique identifier for this variant
baseYesName of the sprite to derive from
paletteYesColor 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

FieldRequiredDefaultDescription
typeYes-Must be "composition"
nameYes-Unique identifier
sizeYes-Canvas size [width, height] in pixels
layersYes-Array of layers, rendered bottom-to-top

Layer Fields

FieldRequiredDescription
spriteYesSprite name to place
xNoX position (default: 0)
yNoY position (default: 0)
blendNoBlend mode (default: "normal")
opacityNoLayer 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

ModeDescription
normalStandard alpha compositing
multiplyDarkens - good for shadows
screenLightens - good for glows
overlayEnhances contrast
addAdditive blending - fire, particles
subtractSubtractive blending
differenceAbsolute difference
darkenKeeps darker color
lightenKeeps 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 CaseSystemExample
Keyframe animationsCSS Transforms"transform": "rotate(90deg) scale(2)"
Derived spritesOp-style Transforms"transform": [{"op": "mirror-h"}]
Geometric only (rotate, flip, scale)EitherContext determines choice
Effects (dither, outline, shadow)Op-style only"transform": [{"op": "sel-out"}]

Rule of thumb:

  • In an animation keyframe → use CSS transform strings
  • In a sprite with source → 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

FunctionSyntaxDescription
translatetranslate(x, y)Move by x, y pixels
rotaterotate(deg)Rotate clockwise
scalescale(n) or scale(x, y)Scale uniformly or non-uniformly
flipflip(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
ParameterTypeDescription
xintegerHorizontal offset (positive = right)
yintegerVertical 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:

AngleResult
0, 90, 180, 270Pixel-perfect rotation
45, 135, 225, 315Diagonal, some blurring
Other valuesArbitrary 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
ParameterTypeDescription
nfloatUniform scale factor (must be positive)
x, yfloatNon-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:

ScaleResult
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}"]}
OperationString SyntaxDescription
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

FieldRequiredDefaultDescription
opYes-Must be "dither"
patternYes-Dither pattern name
tokensYes-Two-element array [dark, light]
thresholdNo0.5Blend threshold (0.0-1.0)
seedNoautoRandom seed for noise pattern

Built-in Patterns

PatternDescription
checker2x2 checkerboard
ordered-2x22x2 Bayer matrix (4 levels)
ordered-4x44x4 Bayer matrix (16 levels)
ordered-8x88x8 Bayer matrix (64 levels)
diagonalDiagonal line pattern
horizontalHorizontal line pattern
verticalVertical line pattern
noiseRandom dither (seeded)

Gradient Dither

Create smooth gradients across the sprite:

{
  "op": "dither-gradient",
  "direction": "vertical",
  "from": "{sky_light}",
  "to": "{sky_dark}",
  "pattern": "ordered-4x4"
}
FieldRequiredDefaultDescription
opYes-Must be "dither-gradient"
directionYes-"vertical", "horizontal", or "radial"
fromYes-Starting color token
toYes-Ending color token
patternNo"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

FieldRequiredDefaultDescription
opYes-Must be "sel-out"
fallbackNo"{_}"Default outline color
auto_darkenNo0.3Auto-darken factor (0.0-1.0)
mappingNoautoExplicit 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

FieldRequiredDefaultDescription
opYes-"squash" or "stretch"
amountYes-Deformation amount (0.0-1.0)
anchorNo"center"Transform anchor point
preserve_areaNotrueMaintain sprite area

Anchor Points

ValueDescription
"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:

  1. Resolves the path relative to the current file's directory
  2. Opens and parses the included file
  3. Extracts the first palette object from that file
  4. 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:

  1. Exact path as specified
  2. Path with .pxl extension
  3. Path with .jsonl extension
// 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

MethodBest For
Inline paletteSimple, self-contained sprites
Named paletteMultiple 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

CommandDescription
renderRender sprites to PNG, GIF, or atlas formats
importConvert PNG images to Pixelsrc format
validateCheck files for errors and common mistakes
fmtFormat files for consistent style
buildBuild all assets according to pxl.toml

Authoring Tools

CommandDescription
newCreate new assets from templates
initInitialize a new Pixelsrc project
sketchCreate sprites from simple text grids

Inspection & Debugging

CommandDescription
showDisplay sprites with colored terminal output
gridDisplay grid with row/column coordinates
inlineExpand grid with column-aligned spacing
explainExplain objects in human-readable format
diffCompare sprites semantically
analyzeExtract corpus metrics from files

AI Integration

CommandDescription
primePrint format guide for AI context injection
promptsShow GenAI prompt templates
suggestSuggest fixes for common issues
aliasExtract repeated patterns into aliases

Reference Data

CommandDescription
palettesList 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

CodeMeaning
0Success
1General error (invalid input, missing file, etc.)
2Validation 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

ArgumentDescription
<INPUT>Input file containing palette and sprite definitions (.pxl or .jsonl)

Options

OptionDescription
-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)
--strictTreat warnings as errors
--gifOutput as animated GIF (requires animation in input)
--spritesheetOutput as spritesheet (horizontal strip of all frames)
--emojiOutput 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-twoForce 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}.png for each

If --output is a file:

  • Single sprite: uses the exact filename
  • Multiple sprites: {output}_{sprite}.png for each

If --output ends with /:

  • Each sprite is written as {dir}/{sprite}.png

Atlas Formats

The --format option supports:

FormatDescription
atlasGeneric JSON atlas
atlas-asepriteAseprite-compatible JSON
atlas-godotGodot engine format
atlas-unityUnity sprite atlas
atlas-libgdxLibGDX 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

ArgumentDescription
<INPUT>Input PNG file to convert

Options

OptionDescription
-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 show after 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

ArgumentDescription
[FILES]...Files to validate (omit if using --stdin)

Options

OptionDescription
--stdinRead input from stdin
--strictTreat warnings as errors
--jsonOutput 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

CodeMeaning
0All files valid (no errors)
1Validation errors found
2Validation 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

ArgumentDescription
<FILES>...Input file(s) to format

Options

OptionDescription
--checkCheck formatting without writing (exit 1 if changes needed)
--stdoutWrite 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:

  1. Type/kind fields first
  2. Name/identifier
  3. Content fields
  4. Metadata fields last

Exit Codes

CodeMeaning
0Success (files formatted or already formatted)
1Files need formatting (with --check)

See Also

  • validate - Check files for errors
  • inline - Expand grid spacing for readability

explain

Explain sprites and other objects in human-readable format.

Usage

pxl explain [OPTIONS] <INPUT>

Arguments

ArgumentDescription
<INPUT>Input file containing Pixelsrc objects

Options

OptionDescription
-n, --name <NAME>Name of specific object to explain (sprite, palette, etc.)
--jsonOutput 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

ArgumentDescription
<FILE_A>First file to compare
<FILE_B>Second file to compare

Options

OptionDescription
--sprite <SPRITE>Compare only a specific sprite by name
--jsonOutput 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

CodeMeaning
0Comparison completed (even if differences found)
1Error (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

ArgumentDescription
[FILES]...Files to analyze (omit if using --stdin)

Options

OptionDescription
--stdinRead input from stdin
--jsonOutput 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

ArgumentDescription
<FILE>Input file containing sprite definitions

Options

OptionDescription
--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-fadeDecrease 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

ArgumentDescription
[FILES]...Files to analyze

Options

OptionDescription
--dir <DIR>Directory to scan for .jsonl/.pxl files
-r, --recursiveInclude 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

OptionDescription
--briefPrint 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

VersionApproximate 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

ArgumentDescription
[TEMPLATE]Template name to show. If omitted, lists all available templates

Available Templates

TemplateDescription
characterCharacter sprite generation prompt
itemItem and object sprite generation
tilesetTileset and terrain generation
animationAnimation 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

CommandDescription
listList all available built-in palettes
showShow 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

ArgumentDescription
<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.

TokenColorHex
darkestDark green#0f380f
darkMedium green#306230
lightLight green#8bac0f
lightestPale 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

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

OptionDescription
-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, --watchWatch for changes and rebuild automatically
--dry-runShow what would be built without building
-v, --verboseShow 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+C to 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:

  1. Discovery: Scan source directories for .pxl files using glob patterns
  2. Planning: Create a build plan with targets and dependencies
  3. Incremental Check: Skip targets that haven't changed since last build
  4. Parallel Execution: Process independent targets concurrently
  5. 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

CodeMeaning
0Build succeeded
1Build failed (errors in source files or I/O)
2Invalid 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
  ...

new

Create a new asset from template.

Usage

pxl new [OPTIONS] <ASSET_TYPE> <NAME>

Arguments

ArgumentDescription
<ASSET_TYPE>Asset type: sprite, animation, palette
<NAME>Asset name

Options

OptionDescription
--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.pxl for sprites
  • walk.pxl for animations
  • fantasy_colors.pxl for palettes

In a project with pxl.toml, assets are created in the configured source directory.

See Also

  • init - Initialize a new project
  • palettes - List built-in palettes
  • render - Render created sprites

init

Initialize a new Pixelsrc project.

Usage

pxl init [OPTIONS] [PATH]

Arguments

ArgumentDescription
[PATH]Project directory (default: current directory)

Options

OptionDescription
--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.toml configuration 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

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:

InputSpritesOutput
hero.pxl1 spritehero_<sprite_name>.png
hero.pxlMultiplehero_<sprite_name>.png per sprite
hero.jsonl1 spritehero_<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

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

FieldTypeDefaultDescription
framesarrayrequiredSprite names in sequence
durationnumber100Milliseconds per frame
loopbooleantrueWhether 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 delayEffective FPS
162cs~50 fps
333cs~33 fps
10010cs10 fps
20020cs5 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.

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:

FrameXYWidthHeight
0001616
11601616
23201616
34801616

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

FeatureSpritesheetAtlas
LayoutHorizontal stripBin-packed
MetadataNone (manual calc)JSON with coordinates
Space efficiencyLower (fixed grid)Higher (tight packing)
SimplicityHigherLower
Multiple spritesNoYes

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

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 image
  • sprites.json - Metadata with frame coordinates

Supported Formats

FormatFlagOutputDescription
JSON--format atlas.jsonGeneric JSON, works with any engine
Godot--format atlas-godot.tresGodot resource files
Unity--format atlas-unity.jsonUnity sprite import format
libGDX--format atlas-libgdx.atlasTextureAtlas 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 AtlasTexture resources for each frame
  • SpriteFrames resource 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

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

OptionDefaultDescription
--onion N-Show N frames before/after
--onion-opacity0.3Ghost frame transparency (0.0-1.0)
--onion-prev-color#0000FFTint for previous frames (blue)
--onion-next-color#00FF00Tint for next frames (green)
--onion-fadefalseFade 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.

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.

ParameterTypeDescription
jsonlstringPixelsrc JSONL content
spriteNamestring?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:

PropertyTypeDescription
widthnumberImage width in pixels
heightnumberImage height in pixels
pixelsUint8ArrayRaw RGBA pixel data
warningsstring[]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:

TargetUse CaseBuild Command
webBrowser ES modulesnpm run build
nodejsNode.jsnpm run build:nodejs
bundlerWebpack/Rollupnpm 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

  1. Initialize once: Call init() once at app startup, not per render
  2. Reuse results: Cache PNG blobs when displaying the same sprite multiple times
  3. Use RGBA for animations: render_to_rgba is faster for frequent canvas updates
  4. 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

BrowserMinimum VersionNotes
Chrome90+Full support
Firefox88+Full support
Safari14+Full support
Edge90+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 .wasm file 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

  1. Game generates pixelsrc JSONL (via AI or authoring)
  2. Chronicle receives: createCharacter(def, pixelsrc_jsonl)
  3. Chronicle stores the JSONL
  4. Chronicle calls pixelsrc to render → caches PNG
  5. 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.

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

  1. Open Settings > Community Plugins
  2. Disable Safe Mode if enabled
  3. Click Browse and search for "PixelSrc"
  4. Click Install, then Enable

Manual Installation

  1. Download the latest release from GitHub Releases
  2. Extract files to your vault's .obsidian/plugins/pixelsrc/ directory
  3. Reload Obsidian
  4. 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:

SettingDescriptionDefault
Default ScaleScale factor for rendered sprites (1-16x)4
Show WarningsDisplay rendering warnings below spritesOff
Transparency BackgroundShow checkered background for transparent pixelsOn
Live PreviewShow sprite preview while editingOn

Keyboard Shortcuts

ActionShortcut
Re-render current blockCtrl/Cmd + Shift + R
Toggle Live PreviewCtrl/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

  1. Check that the code block language is pixelsrc or pxl
  2. Verify JSON5 syntax (look for missing quotes or commas)
  3. Ensure the plugin is enabled
  4. 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.

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:

PanelPurpose
EditorWrite and edit Pixelsrc JSONL
PreviewReal-time rendered output
GalleryExample sprites to load and modify

Getting Started

  1. Open the web editor
  2. Click an example from the Gallery to load it
  3. Modify the JSONL in the Editor panel
  4. Watch the Preview update in real-time

Or paste your own Pixelsrc JSONL directly into the editor.

Keyboard Shortcuts

ActionShortcut
Render immediatelyCtrl/Cmd + Enter
Select allCtrl/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

  1. Select a scale factor (1x, 2x, 4x, or 8x)
  2. Click Download PNG
  3. File saves as pixelsrc-{scale}x.png

Copy to Clipboard

  1. Select a scale factor
  2. Click Copy
  3. 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:

  1. Create or load a sprite
  2. Copy the browser URL
  3. 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:

ErrorMeaningFix
Invalid JSON syntaxMissing quotes, commas, or bracketsCheck JSON formatting
Palette errorUndefined color token in gridDefine all tokens in palette
Grid errorInconsistent row lengthsEnsure all rows have same token count
Missing "type" fieldObject lacks required typeAdd "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-wasm for rendering
  • LZ-String - URL hash compression

No framework dependencies. The editor uses vanilla DOM manipulation for minimal bundle size.

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:

CodeMeaning
0Success
1Error (or warning in strict mode)
2Invalid 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:

  • .tres resource files for atlases
  • AnimationPlayer resources for animations
  • SpriteFrames resources for animated sprites

Unity Export

[export.unity]
enabled = true
pixels_per_unit = 16
filter_mode = "point"  # or "bilinear"

Generates:

  • .asset meta files for sprites
  • Sprite slicing data for atlases
  • Animation clips for animated sprites

libGDX Export

[export.libgdx]
enabled = true

Generates:

  • .atlas files 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

  1. Use incremental builds - Let the manifest track changes
  2. Validate before building - Faster than full renders
  3. Batch similar sprites - Reduces CLI startup overhead
  4. Cache in CI - Cache Cargo builds and manifest files
  5. Watch mode for development - Avoids manual re-running
  6. 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 --verbose to identify slow targets

Watch mode missing changes

  • Check debounce_ms setting (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 --verbose to see export target status

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:

  1. Dimensions: "16x16 pixels" not just "small"
  2. Color count: "using 4-8 colors"
  3. Style reference: "SNES-era" or "Game Boy style"
  4. 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:

GoodBad
{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:

  1. Define outline with 2-3 basic shapes
  2. Adjust proportions until shape reads correctly
  3. Add asymmetry for 3/4 view (shift shapes, angle triangles)
  4. 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

  1. Generate basic shape
  2. Add colors/shading
  3. Refine details
  4. Create variations

Ask for Variations

"Create 3 color variations of this sprite:

  1. Original colors
  2. Night version (darker, bluer)
  3. 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:

IssueTriggerWorkaround
Stripe artifacts5+ shapes with same colorKeep to 4 shapes max per color
Polygon fill gaps6+ vertices in polygonBreak into simpler shapes, use union
Overlap artifactsMultiple regions overlapping at same zUse different z-levels

Safe patterns:

  • 3-4 simple shapes (circle, rect, polygon with 3-5 vertices)
  • union of 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

  1. Be specific about size: "16x16", "32x32", "8x8"
  2. Name colors explicitly: "silver blade", "golden hilt", "brown handle"
  3. Reference real games: "like Zelda items", "SNES-era style"
  4. Request semantic tokens: "use descriptive token names like skin and hair"
  5. Ask for palettes separately: "first create a palette, then the sprite"
  6. 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:

  1. Ask the AI to regenerate with explicit validation:

    "Output valid JSON, one complete object per line. Verify each line is valid JSON before outputting."

  2. Check for common mistakes:

    • Missing commas between key-value pairs
    • Trailing commas after last element
    • Unquoted string values
    • Single quotes instead of double quotes
  3. Use pxl fmt to attempt auto-repair:

    pxl fmt generated.pxl
    

Wrong Token Format

Problem: Using [skin] or skin instead of {skin}.

Solutions:

  1. Include example in prompt:

    "Use curly brace token format: {skin}, {hair}, {_}"

  2. 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:

  1. Ask for explicit size validation:

    "Ensure each row has exactly 16 tokens"

  2. Use strict mode to catch issues:

    pxl render output.pxl --strict
    
  3. 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:

  1. Explicitly request transparency:

    "Use {_} mapped to #00000000 for all background pixels"

  2. 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:

  1. Check for typos in token names:

    // In palette
    "{skin}": "#FFCC99"
    
    // In grid - typo!
    "{skn}{skn}{skn}"
    
  2. Ensure all tokens used in grid are defined in palette

  3. Use pxl explain to see which tokens are missing:

    pxl explain generated.pxl
    

Colors Look Wrong

Problem: Colors don't match expectations.

Solutions:

  1. Verify hex format:

    • #RGB - 3 digits, expanded to 6
    • #RGBA - 4 digits, expanded to 8
    • #RRGGBB - 6 digits, standard
    • #RRGGBBAA - 8 digits, with alpha
  2. Check for RGB vs BGR confusion (rare)

  3. 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:

  1. Be explicit about both dimensions:

    "Create a 16 pixels wide by 16 pixels tall sprite with exactly 16 rows and 16 tokens per row"

  2. Verify after generation:

    pxl render generated.pxl --strict
    

Animation Frames Don't Match

Problem: Frames have different sizes or character shifts position.

Solutions:

  1. 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."

  2. 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:

  1. Reference pixel art style:

    "NES-style with chunky 2-3 pixel wide lines" "Detailed SNES-style with 1-pixel outlines"

  2. 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:

  1. Define palette first:

    "Create a palette named 'character_colors' with {skin}: #FFCC99, {hair}: #8B4513, etc. Then create all frames using this palette by name."

  2. 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:

  1. 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."

  2. 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:

  1. Use composition with smaller tiles:

    "Create four 16x16 tiles, then compose them into a 32x32 image"

  2. 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

  1. Start fresh: Ask for a complete regeneration with all constraints restated
  2. Simplify: Request a smaller sprite first, then scale up
  3. Manual fix: Edit the JSONL directly - it's just text
  4. 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

AlgorithmScaleQualitySpeedBest For
nearest1x-FastestNo antialiasing (default)
scale2x2xGoodFastSimple upscaling, retro look
hq2x2xBetterMediumBalanced quality/performance
hq4x4xBetterMediumHigher resolution output
xbr2x2xBestSlowerMaximum quality at 2x
xbr4x4xBestSlowerMaximum quality at 4x
aa-blur1xSubtleFastEdge 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:

RoleBlur WeightEffect
Anchor0%No blur (crisp details)
Boundary25%Light blur (soft edges)
Fill100%Full blur (smooth fills)
Shadow/Highlight100%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:

  1. pxl.toml defaults - Project-wide defaults
  2. Atlas-level - Per-atlas configuration
  3. Per-sprite - Individual sprite settings
  4. 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

FlagDescription
--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-aaDisable semantic-aware processing
--no-gradient-shadowsDisable 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: 2x
  • hq4x, 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

AlgorithmRelative Speed
nearest1x (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.

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.

TokenColorHex
{_}Transparent#00000000
{lightest}#9BBC0F#9BBC0F
{light}#8BAC0F#8BAC0F
{dark}#306230#306230
{darkest}#0F380F#0F380F

Reference: Nintendo Game Boy (BGB) on Lospec

nes

NES-inspired palette with key representative colors.

TokenColorHex
{_}Transparent#00000000
{black}#000000#000000
{white}#FCFCFC#FCFCFC
{red}#A80020#A80020
{green}#00A800#00A800
{blue}#0058F8#0058F8
{cyan}#00B8D8#00B8D8
{yellow}#F8D800#F8D800
{orange}#F83800#F83800
{pink}#F878F8#F878F8
{brown}#503000#503000
{gray}#7C7C7C#7C7C7C
{skin}#FCB8B8#FCB8B8

Reference: Nintendo Entertainment System on Lospec

pico8

PICO-8 fantasy console 16-color palette.

TokenColorHex
{_}Transparent#00000000
{black}#000000#000000
{dark_blue}#1D2B53#1D2B53
{dark_purple}#7E2553#7E2553
{dark_green}#008751#008751
{brown}#AB5236#AB5236
{dark_gray}#5F574F#5F574F
{light_gray}#C2C3C7#C2C3C7
{white}#FFF1E8#FFF1E8
{red}#FF004D#FF004D
{orange}#FFA300#FFA300
{yellow}#FFEC27#FFEC27
{green}#00E436#00E436
{blue}#29ADFF#29ADFF
{indigo}#83769C#83769C
{pink}#FF77A8#FF77A8
{peach}#FFCCAA#FFCCAA

Reference: PICO-8 on Lospec

grayscale

8-shade grayscale palette from white to black.

TokenColorHex
{_}Transparent#00000000
{white}#FFFFFF#FFFFFF
{gray1}#DFDFDF#DFDFDF
{gray2}#BFBFBF#BFBFBF
{gray3}#9F9F9F#9F9F9F
{gray4}#7F7F7F#7F7F7F
{gray5}#5F5F5F#5F5F5F
{gray6}#3F3F3F#3F3F3F
{black}#000000#000000

1bit

Minimal 1-bit black and white palette.

TokenColorHex
{_}Transparent#00000000
{black}#000000#000000
{white}#FFFFFF#FFFFFF

dracula

Dracula theme palette for code editor aesthetics.

TokenColorHex
{_}Transparent#00000000
{background}#282A36#282A36
{current}#44475A#44475A
{foreground}#F8F8F2#F8F8F2
{comment}#6272A4#6272A4
{cyan}#8BE9FD#8BE9FD
{green}#50FA7B#50FA7B
{orange}#FFB86C#FFB86C
{pink}#FF79C6#FF79C6
{purple}#BD93F9#BD93F9
{red}#FF5555#FF5555
{yellow}#F1FA8C#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"}}

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

FormatExampleNotes
Hex (short)#RGB, #RGBAEach digit doubled
Hex (long)#RRGGBB, #RRGGBBAAFull precision
RGBrgb(255, 0, 0)Integer or percentage
HSLhsl(0, 100%, 50%)Hue, saturation, lightness
HWBhwb(0 0% 0%)Hue, whiteness, blackness
OKLCHoklch(0.628 0.258 29.23)Perceptually uniform
color-mixcolor-mix(in oklch, red 70%, black)Blend two colors
Namedred, blue, transparentCSS 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:

ShortExpands ToResult
#F00#FF0000Red (255, 0, 0)
#0F0#00FF00Green (0, 255, 0)
#00F#0000FFBlue (0, 0, 255)
#ABC#AABBCC(170, 187, 204)
#F008#FF000088Red 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)"
}}
PatternEffect
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:

NameHex 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

ErrorCauseExample
empty color stringEmpty value""
color must start with '#'Missing hash (for hex)"FF0000"
invalid color length NWrong 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

  1. Use hex for simple colors - #F00 is clearer than rgb(255, 0, 0)
  2. Use color-mix for shadows/highlights - Consistent shading from base colors
  3. Use oklch for shadows - Perceptually uniform darkening across all hues
  4. Use CSS variables with color-mix - Define base colors once, derive variants
  5. Use transparent over #00000000 - More readable
  6. Test at 1x scale - Ensure colors are distinguishable at native size

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

CodeNameDescription
0SuccessCommand completed successfully
1ErrorRuntime error during execution
2Invalid ArgumentsInvalid 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

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.

FieldTypeDefaultDescription
namestringrequiredProject name
versionstring"0.1.0"Project version
srcpath"src/pxl"Source directory for .pxl files
outpath"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.

FieldTypeDefaultDescription
scaleinteger1Default scale factor for rendering
paddinginteger1Default padding between sprites in atlases
[defaults]
scale = 2
padding = 4

[atlases.<name>]

Define named atlas configurations. Multiple atlases can be defined.

FieldTypeDefaultDescription
sourcesarrayrequiredGlob patterns for sprite sources
max_size[w, h][1024, 1024]Maximum atlas dimensions
paddingintegerfrom defaultsPadding between sprites
power_of_twobooleanfalseConstrain to power-of-two dimensions
nine_slicebooleanfalsePreserve 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.

FieldTypeDefaultDescription
sourcesarray["animations/**"]Glob patterns for animation files
previewbooleanfalseGenerate preview GIFs
preview_scaleinteger1Scale factor for previews
sheet_layoutstring"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.

FieldTypeDefaultDescription
enabledbooleantrueEnable generic JSON export
atlas_formatstring"json"Output format identifier
[export.generic]
enabled = true
atlas_format = "json"

[export.godot]

Godot game engine export configuration.

FieldTypeDefaultDescription
enabledbooleanfalseEnable Godot export
atlas_formatstring"godot"Output format identifier
resource_pathstring"res://assets/sprites"Godot resource path prefix
animation_playerbooleantrueGenerate AnimationPlayer resources
sprite_framesbooleantrueGenerate SpriteFrames resources
[export.godot]
enabled = true
resource_path = "res://sprites"
animation_player = true
sprite_frames = true

Generated files:

  • .tres resource files for atlases
  • AnimationPlayer resources for animations
  • SpriteFrames resources for animated sprites

[export.unity]

Unity game engine export configuration.

FieldTypeDefaultDescription
enabledbooleanfalseEnable Unity export
atlas_formatstring"unity"Output format identifier
pixels_per_unitinteger16Pixels per Unity unit
filter_modestring"point"Texture filter: point or bilinear
[export.unity]
enabled = true
pixels_per_unit = 32
filter_mode = "point"

Generated files:

  • .asset meta files for sprites
  • Sprite slicing data for atlases
  • Animation clips for animated sprites

[export.libgdx]

libGDX game framework export configuration.

FieldTypeDefaultDescription
enabledbooleanfalseEnable libGDX export
atlas_formatstring"libgdx"Output format identifier
[export.libgdx]
enabled = true

Generated files:

  • .atlas files in libGDX TexturePacker format
  • Region definitions for sprite lookup

[validate]

Validation settings for the build process.

FieldTypeDefaultDescription
strictbooleanfalseTreat warnings as errors
unused_paletteslevel"warn"How to handle unused palettes
missing_refslevel"error"How to handle missing references

Validation levels: error, warn, ignore

[validate]
strict = true
unused_palettes = "error"
missing_refs = "warn"

[watch]

Watch mode configuration.

FieldTypeDefaultDescription
debounce_msinteger100Debounce delay in milliseconds
clear_screenbooleantrueClear 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:

SettingCLI FlagEnvironment Variable
Source directory--src-
Output directory--out or -o-
Verbose output--verbose or -v-

Validation

The configuration is validated on load. Common validation errors:

ErrorCause
project.name must be non-emptyMissing or empty project name
defaults.scale must be positiveScale set to 0
atlases.\<name\>.sources must contain at least one glob patternEmpty sources array
atlases.\<name\>.max_size dimensions must be positiveZero dimension in max_size
export.unity.pixels_per_unit must be positiveZero pixels_per_unit with Unity enabled

Vision & Philosophy

Changelog

Contributing