Files
gh-willibrandon-pixel-plugin/skills/pixel-art-exporter/export-formats.md
2025-11-30 09:06:21 +08:00

21 KiB
Raw Blame History

Export Formats Reference

This document provides comprehensive technical specifications for exporting pixel art in various formats, including static images, animated formats, spritesheets, and game engine-specific outputs.

PNG Export Options

PNG (Portable Network Graphics) is the primary format for pixel art due to lossless compression and transparency support.

Transparency Handling

Full Alpha Channel:

  • 8-bit alpha channel (0-255 transparency levels)
  • Preserves semi-transparent pixels from antialiasing or effects
  • Essential for overlay sprites, particles, or UI elements
  • File size increases with alpha complexity

Binary Transparency:

  • Pixels are either fully opaque (255) or fully transparent (0)
  • Smaller file size compared to full alpha
  • Sufficient for most pixel art with hard edges
  • Compatible with older systems

No Transparency:

  • All pixels opaque
  • Smallest file size for PNG
  • Use when background is always solid
  • Useful for tiles, backgrounds, or when transparency not needed

Background Colors

Transparent Background:

  • Default for sprites, characters, UI elements
  • Allows compositing over any background
  • Required for multi-layer game objects

Solid Background:

  • Specify hex color (e.g., #FF00FF for magenta)
  • Useful for preview/reference images
  • Can use specific color for manual masking later
  • Common for texture atlases with padding

Checkerboard Pattern:

  • Not exported, but visible in editor
  • Indicates transparent areas during editing
  • Final export still uses true transparency

Color Depth Options

Indexed Color (8-bit):

  • Maximum 256 colors
  • Smallest file size
  • Perfect for retro pixel art
  • Preserves exact palette
  • Example: NES-style art (54 colors), Game Boy (4 colors)

RGB (24-bit):

  • 16.7 million colors
  • No transparency channel
  • Use when alpha not needed
  • Moderate file size

RGBA (32-bit):

  • 16.7 million colors + alpha
  • Full transparency support
  • Largest file size
  • Industry standard for modern games

Compression Settings

PNG Compression Levels:

  • Level 0: No compression (fastest, largest)
  • Level 1-5: Low compression (fast encoding)
  • Level 6: Default (balanced)
  • Level 9: Maximum compression (slowest, smallest)

Best Practices:

  • Use level 9 for final game assets (one-time cost)
  • Use level 1-3 for rapid iteration/testing
  • Compression is lossless at all levels
  • File size varies by image complexity, not canvas size

GIF Export

GIF format provides animation support with wide compatibility but limited color palette.

Frame Timing Control

Frame Duration:

  • Measured in centiseconds (100ths of a second)
  • Minimum: 1cs (10ms) - browser limit typically 20ms
  • Common values: 10cs (100ms), 5cs (50ms), 20cs (200ms)
  • Per-frame timing allows variable animation speeds

Timing Examples:

Walking cycle: 10cs per frame (10 FPS)
Idle breathing: 30cs per frame (3.3 FPS)
Fast attack: 5cs per frame (20 FPS)
Slow blink: [50cs, 5cs, 50cs] (hold, blink, hold)

Aseprite Frame Duration:

  • Set in timeline (milliseconds in Aseprite)
  • Export converts to centiseconds
  • Maintains exact timing in GIF output

Looping Options

Infinite Loop:

  • Default for game sprites
  • NETSCAPE2.0 extension with loop count 0
  • Plays continuously until stopped
  • Standard for web animations

Count-Based Looping:

  • Specify exact number of repetitions
  • Example: Loop 3 times then stop
  • Useful for intro animations, effects
  • Browser support varies

No Loop (Play Once):

  • Loop count set to 1
  • Stops on final frame
  • Good for cutscenes, transitions
  • Fallback: some viewers may still loop

Color Quantization for GIF

256 Color Limitation:

  • GIF maximum palette size
  • Quantization reduces full-color to 256 colors
  • Critical consideration when exporting from 24-bit source

Quantization Algorithms:

Global Palette:

  • Single 256-color palette for all frames
  • Ensures color consistency across animation
  • Analyze all frames to build optimal palette
  • Best for most pixel art animations

Local Palette:

  • Different palette per frame
  • Allows more colors across animation
  • Can cause color shifting/flickering
  • Rarely appropriate for pixel art

Dithering Options:

  • None: Hard color boundaries (best for pixel art)
  • Floyd-Steinberg: Error diffusion, adds noise
  • Ordered: Pattern-based, creates artifacts
  • Recommendation: Disable dithering for pixel art

Optimization Techniques

Frame Disposal Methods:

  • Restore to Background: Clear before next frame
  • Do Not Dispose: Keep previous frame
  • Restore to Previous: Revert to prior state

Delta Frame Encoding:

  • Only encode changed pixels between frames
  • Significant size reduction for small changes
  • Automatic in most GIF encoders
  • Example: Idle animation (80% size reduction)

Transparency Optimization:

  • Designate one color as transparent
  • Reduce file size by not encoding unchanged areas
  • Works with delta encoding
  • Choose unused color for transparency index

Size Reduction Checklist:

  • Use global palette
  • Enable delta encoding
  • Minimize frame dimensions (crop to animation bounds)
  • Reduce frame count if possible
  • Lower frame rate for slower animations

File Size Considerations

Size Factors:

Small (1-50 KB):  16×16 sprite, 4-8 frames, simple movement
Medium (50-200 KB): 32×32 sprite, 10-20 frames, detailed animation
Large (200KB-1MB): 64×64+ sprite, 30+ frames, complex animation

Optimization Trade-offs:

  • Frame count vs. smoothness
  • Canvas size vs. detail
  • Frame rate vs. file size
  • Color count vs. palette accuracy

Spritesheet Layouts

Spritesheets combine multiple frames or sprites into a single image for efficient loading and rendering.

Horizontal Strip

All frames arranged in a single horizontal row.

+----+----+----+----+----+
| F1 | F2 | F3 | F4 | F5 |
+----+----+----+----+----+

Use Cases:

  • Simple animations (walk, run, jump)
  • Scrolling backgrounds
  • UI button states

Advantages:

  • Simple to parse (X offset only)
  • Easy to calculate frame position: frame_x = frame_index * frame_width
  • Good for texture atlases with power-of-2 heights

Disadvantages:

  • Very wide images with many frames
  • Inefficient for non-power-of-2 widths
  • GPU texture size limits (typically 4096-8192px)

Vertical Strip

All frames arranged in a single vertical column.

+----+
| F1 |
+----+
| F2 |
+----+
| F3 |
+----+
| F4 |
+----+

Use Cases:

  • Mobile-optimized layouts
  • Vertical scrolling effects
  • Legacy systems preferring tall textures

Advantages:

  • Simple to parse (Y offset only)
  • Frame position: frame_y = frame_index * frame_height
  • Better for portrait-oriented displays

Disadvantages:

  • Very tall images
  • Less common than horizontal
  • Same texture size constraints

Grid Layout

Frames arranged in rows and columns.

+----+----+----+----+
| F1 | F2 | F3 | F4 |
+----+----+----+----+
| F5 | F6 | F7 | F8 |
+----+----+----+----+
| F9 | 10 | 11 | 12 |
+----+----+----+----+

Configuration:

  • Rows: Number of vertical divisions
  • Columns: Number of horizontal divisions
  • Example: 4 columns × 3 rows = 12 frames

Frame Calculation:

frame_x = (frame_index % columns) * frame_width
frame_y = (frame_index // columns) * frame_height

Use Cases:

  • Large animation sets (16+ frames)
  • Multiple animations in one sheet
  • Power-of-2 texture optimization

Advantages:

  • Balanced dimensions (square or near-square)
  • GPU-friendly (easier to fit power-of-2)
  • Compact representation

Best Practices:

  • Use power-of-2 dimensions: 256×256, 512×512, 1024×1024
  • Add padding between frames (1-2px) to prevent bleeding
  • Organize by animation (row per animation type)

Packed Layout

Optimal space usage with variable frame positions.

+--------+----+----+
|        | F3 | F4 |
|   F1   +----+----+
|        | F5 | F6 |
+----+---+----+----+
| F2 | F7      | F8|
+----+---------+---+

Characteristics:

  • Frames positioned to minimize whitespace
  • Requires metadata for frame positions
  • Non-uniform layout

Algorithms:

  • Maxrects: Maximum rectangles bin packing
  • Skyline: Skyline bottom-left heuristic
  • Guillotine: Recursive subdivision

Use Cases:

  • Variable-size sprites (UI elements, items)
  • Texture atlases with many assets
  • Maximizing GPU texture memory

Advantages:

  • Smallest total texture size
  • Efficient memory usage
  • Professional game asset pipelines

Disadvantages:

  • Requires JSON/XML metadata
  • More complex to parse
  • Not suitable for uniform animations

Metadata Example:

{
  "frame1": {"x": 0, "y": 0, "w": 32, "h": 64},
  "frame2": {"x": 0, "y": 64, "w": 16, "h": 16},
  "frame3": {"x": 32, "y": 0, "w": 16, "h": 16}
}

JSON Metadata Formats

JSON files provide frame data, animation metadata, and asset information for game engines.

Aseprite JSON Format

Native format exported by Aseprite with comprehensive metadata.

{
  "frames": {
    "sprite_0.aseprite": {
      "frame": {"x": 0, "y": 0, "w": 32, "h": 32},
      "rotated": false,
      "trimmed": false,
      "spriteSourceSize": {"x": 0, "y": 0, "w": 32, "h": 32},
      "sourceSize": {"w": 32, "h": 32},
      "duration": 100
    },
    "sprite_1.aseprite": {
      "frame": {"x": 32, "y": 0, "w": 32, "h": 32},
      "rotated": false,
      "trimmed": false,
      "spriteSourceSize": {"x": 0, "y": 0, "w": 32, "h": 32},
      "sourceSize": {"w": 32, "h": 32},
      "duration": 100
    }
  },
  "meta": {
    "app": "http://www.aseprite.org/",
    "version": "1.3.0",
    "image": "sprite.png",
    "format": "RGBA8888",
    "size": {"w": 128, "h": 32},
    "scale": "1",
    "frameTags": [
      {
        "name": "walk",
        "from": 0,
        "to": 3,
        "direction": "forward"
      }
    ],
    "layers": [
      {"name": "Layer 1", "opacity": 255, "blendMode": "normal"}
    ],
    "slices": []
  }
}

Key Fields:

  • frames: Per-frame data with positions and durations
  • frameTags: Animation sequences (walk, run, idle)
  • direction: forward, reverse, pingpong
  • duration: Frame duration in milliseconds

TexturePacker JSON Format

Industry-standard format used by many tools and engines.

{
  "frames": {
    "hero_walk_00.png": {
      "frame": {"x": 2, "y": 2, "w": 32, "h": 32},
      "rotated": false,
      "trimmed": true,
      "spriteSourceSize": {"x": 2, "y": 1, "w": 32, "h": 32},
      "sourceSize": {"w": 36, "h": 34},
      "pivot": {"x": 0.5, "y": 0.5}
    }
  },
  "meta": {
    "app": "https://www.codeandweb.com/texturepacker",
    "version": "1.0",
    "image": "spritesheet.png",
    "format": "RGBA8888",
    "size": {"w": 512, "h": 512},
    "scale": "1"
  }
}

Key Features:

  • trimmed: Indicates if transparent pixels were removed
  • spriteSourceSize: Position of trimmed sprite in original
  • pivot: Rotation/scaling origin point (0-1 normalized)
  • Widely supported by game frameworks

Unity Sprite Atlas Format

Unity-specific JSON structure for sprite atlases.

{
  "name": "PlayerAtlas",
  "spriteSheet": {
    "sprites": [
      {
        "name": "player_idle_0",
        "rect": {"x": 0, "y": 0, "width": 32, "height": 32},
        "pivot": {"x": 0.5, "y": 0},
        "border": {"x": 0, "y": 0, "z": 0, "w": 0},
        "alignment": 7,
        "pixelsPerUnit": 32
      },
      {
        "name": "player_idle_1",
        "rect": {"x": 32, "y": 0, "width": 32, "height": 32},
        "pivot": {"x": 0.5, "y": 0},
        "border": {"x": 0, "y": 0, "z": 0, "w": 0},
        "alignment": 7,
        "pixelsPerUnit": 32
      }
    ]
  }
}

Unity-Specific Fields:

  • pixelsPerUnit: Sprite resolution (typically 32 or 100)
  • alignment: Pivot preset (7 = bottom-center)
  • border: 9-slice borders for UI scaling
  • Compatible with Unity's SpriteAtlas system

Godot SpriteFrames Format

Godot engine's resource format for AnimatedSprite nodes.

{
  "frames": [
    {
      "name": "walk",
      "frames": [
        {"texture": "res://sprites/player_walk_0.png", "duration": 0.1},
        {"texture": "res://sprites/player_walk_1.png", "duration": 0.1},
        {"texture": "res://sprites/player_walk_2.png", "duration": 0.1},
        {"texture": "res://sprites/player_walk_3.png", "duration": 0.1}
      ]
    },
    {
      "name": "idle",
      "frames": [
        {"texture": "res://sprites/player_idle_0.png", "duration": 0.5},
        {"texture": "res://sprites/player_idle_1.png", "duration": 0.5}
      ]
    }
  ],
  "speed": 10.0,
  "loop": true
}

Godot Features:

  • Animation-based organization (not single frames)
  • Per-frame duration in seconds
  • Global speed multiplier
  • Directly compatible with AnimatedSprite node

Scaling Considerations

Pixel art requires special handling when scaling to maintain crisp, pixel-perfect appearance.

Nearest-Neighbor Algorithm

How It Works:

  • Each output pixel uses value from closest input pixel
  • No interpolation or blending
  • Preserves hard edges and exact colors

Mathematical Definition:

output[x, y] = input[floor(x / scale), floor(y / scale)]

Results:

  • Perfectly sharp pixels
  • No color bleeding or artifacts
  • Integer scales produce perfect results
  • Non-integer scales may show uneven pixels

Essential for Pixel Art:

  • Maintains artistic intent
  • Preserves hand-placed pixels
  • No blur or antialiasing
  • Industry standard for retro aesthetics

Why Other Algorithms Blur Pixels

Bilinear Interpolation:

  • Averages 4 nearest pixels
  • Creates smooth gradients
  • Problem: Blurs pixel art edges
  • Use case: Photography, 3D textures

Bicubic Interpolation:

  • Averages 16 nearest pixels
  • Even smoother than bilinear
  • Problem: Severe blurring for pixel art
  • Use case: High-quality photo scaling

Lanczos Resampling:

  • Advanced algorithm, high quality
  • Problem: Introduces ringing artifacts
  • Use case: Photographic downscaling

Visual Comparison:

Original (8×8):          Nearest (16×16):        Bilinear (16×16):
████████                 ████████████████        ░░░░░░░░░░░░░░░░
██    ██                 ████        ████        ░░██        ██░░
██    ██    ------>      ████        ████        ░░██        ██░░
████████                 ████████████████        ░░░░░░░░░░░░░░░░
(Sharp edges)            (Sharp edges)           (Blurred edges)

Common Scale Factors

Integer Scales (Recommended):

  • 1x: Native resolution (32×32 remains 32×32)
  • 2x: Double size (32×32 becomes 64×64)
  • 4x: Quad size (32×32 becomes 128×128)
  • 8x: Octo size (32×32 becomes 256×256)

Benefits:

  • Perfect pixel mapping (1 source pixel → N×N output pixels)
  • No distortion or uneven pixels
  • Crisp, clean appearance

Non-Integer Scales (Use with Caution):

  • 1.5x: Some pixels larger than others
  • 3x: Still integer, good quality
  • 2.5x: Uneven pixel sizes, slight distortion

Best Practices:

  • Prefer integer scales (2x, 3x, 4x)
  • Design base sprites at smallest playable size
  • Scale up for high-DPI displays
  • Use viewport/camera scaling instead of asset scaling when possible

Maintaining Pixel-Perfect Appearance

Canvas Alignment:

  • Position sprites on integer coordinates
  • Avoid sub-pixel positioning (0.5, 1.3, etc.)
  • Round camera position to integers

Viewport Scaling:

  • Scale entire game viewport, not individual sprites
  • Maintain aspect ratio
  • Use integer scale factors

Export Settings:

  • Disable antialiasing
  • Use nearest-neighbor filter
  • Set texture filtering to "Point" or "Nearest"

GPU Texture Settings:

  • Mag Filter: Nearest/Point
  • Min Filter: Nearest/Point (not Mipmap)
  • Wrap Mode: Clamp or Repeat as needed

Game Engine Integration Examples

Unity

Import Settings:

Texture Type: Sprite (2D and UI)
Sprite Mode: Multiple (for spritesheets)
Pixels Per Unit: 32 (or native sprite size)
Filter Mode: Point (no filter)
Compression: None
Format: RGBA 32 bit

Sprite Atlas Usage:

using UnityEngine;

public class SpriteAnimator : MonoBehaviour
{
    public Sprite[] walkFrames;
    private SpriteRenderer spriteRenderer;
    private int currentFrame = 0;
    private float frameTimer = 0f;
    private float frameDuration = 0.1f;

    void Start()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
    }

    void Update()
    {
        frameTimer += Time.deltaTime;
        if (frameTimer >= frameDuration)
        {
            frameTimer = 0f;
            currentFrame = (currentFrame + 1) % walkFrames.Length;
            spriteRenderer.sprite = walkFrames[currentFrame];
        }
    }
}

Godot

SpriteFrames Setup:

# Import spritesheet
var texture = preload("res://sprites/player.png")

# Create SpriteFrames resource
var sprite_frames = SpriteFrames.new()

# Add animation
sprite_frames.add_animation("walk")
sprite_frames.set_animation_speed("walk", 10.0)

# Add frames (assume 32×32 frames in horizontal strip)
for i in range(4):
    var atlas = AtlasTexture.new()
    atlas.atlas = texture
    atlas.region = Rect2(i * 32, 0, 32, 32)
    sprite_frames.add_frame("walk", atlas, i)

AnimatedSprite Configuration:

extends AnimatedSprite

func _ready():
    # Disable filtering for pixel-perfect
    texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST

    # Play animation
    play("walk")

Project Settings:

Rendering > Textures > Canvas Textures > Default Texture Filter: Nearest
Display > Window > Stretch > Mode: viewport
Display > Window > Stretch > Aspect: keep

Phaser

Loading Spritesheet:

class GameScene extends Phaser.Scene {
    preload() {
        // Load spritesheet
        this.load.spritesheet('player', 'assets/player.png', {
            frameWidth: 32,
            frameHeight: 32
        });
    }

    create() {
        // Create animation from frames 0-3
        this.anims.create({
            key: 'walk',
            frames: this.anims.generateFrameNumbers('player', {
                start: 0,
                end: 3
            }),
            frameRate: 10,
            repeat: -1
        });

        // Create sprite
        const player = this.add.sprite(100, 100, 'player');
        player.play('walk');

        // Disable texture smoothing for pixel-perfect
        player.setTexture('player').setOrigin(0.5, 0.5);
    }
}

Game Configuration:

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    pixelArt: true, // Enables nearest-neighbor filtering
    render: {
        antialias: false,
        pixelArt: true
    },
    scene: [GameScene]
};

const game = new Phaser.Game(config);

Generic Engine Patterns

Frame-Based Animation Loop:

class Animation {
    constructor(frames, frameDuration) {
        this.frames = frames; // Array of frame data {x, y, w, h}
        this.frameDuration = frameDuration; // Milliseconds per frame
        this.currentFrame = 0;
        this.elapsedTime = 0;
    }

    update(deltaTime) {
        this.elapsedTime += deltaTime;
        if (this.elapsedTime >= this.frameDuration) {
            this.elapsedTime = 0;
            this.currentFrame = (this.currentFrame + 1) % this.frames.length;
        }
    }

    getCurrentFrame() {
        return this.frames[this.currentFrame];
    }
}

Spritesheet Rendering:

function drawSprite(context, image, frame, x, y, scale) {
    // Use nearest-neighbor scaling
    context.imageSmoothingEnabled = false;

    context.drawImage(
        image,
        frame.x, frame.y, frame.w, frame.h, // Source
        Math.floor(x), Math.floor(y),       // Destination (integer coords)
        frame.w * scale, frame.h * scale    // Scaled size
    );
}

JSON Metadata Parsing:

async function loadSpritesheet(imagePath, jsonPath) {
    const [image, metadata] = await Promise.all([
        loadImage(imagePath),
        fetch(jsonPath).then(r => r.json())
    ]);

    const animations = {};

    // Parse Aseprite JSON format
    for (const tag of metadata.meta.frameTags) {
        const frames = [];
        for (let i = tag.from; i <= tag.to; i++) {
            const frameKey = Object.keys(metadata.frames)[i];
            const frameData = metadata.frames[frameKey];
            frames.push({
                x: frameData.frame.x,
                y: frameData.frame.y,
                w: frameData.frame.w,
                h: frameData.frame.h,
                duration: frameData.duration
            });
        }
        animations[tag.name] = new Animation(frames, frames[0].duration);
    }

    return { image, animations };
}

Best Practices Summary:

  • Always disable texture filtering (use nearest/point)
  • Position sprites on integer pixel coordinates
  • Use integer scale factors
  • Set imageSmoothingEnabled = false for canvas
  • Configure engine for pixel-perfect rendering
  • Load metadata alongside spritesheet images
  • Cache parsed animations for performance