# 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:** ```python 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:** ```json { "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. ```json { "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. ```json { "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. ```json { "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. ```json { "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:** ```csharp 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(); } void Update() { frameTimer += Time.deltaTime; if (frameTimer >= frameDuration) { frameTimer = 0f; currentFrame = (currentFrame + 1) % walkFrames.Length; spriteRenderer.sprite = walkFrames[currentFrame]; } } } ``` ### Godot **SpriteFrames Setup:** ```gdscript # 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:** ```gdscript 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:** ```javascript 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:** ```javascript 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:** ```javascript 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:** ```javascript 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:** ```javascript 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