Format: Sprite Sheet

The add-on exports sprite sheet animations in three formats: PNG (.png), Aseprite JSON (.json), and Packed Binary (.sprsh). All three render animation frames into a grid-based sprite sheet image. The difference is how metadata and the image are delivered.

  • PNG (.png) — A single sprite sheet image with no metadata.
  • Aseprite JSON (.json) — Aseprite-compatible JSON metadata alongside a sidecar PNG image.
  • Packed Binary (.sprsh) — A single file containing both the Aseprite JSON metadata and the PNG image bytes.

All exportable animations are packed sequentially into a single sheet. Frames are laid out left-to-right, top-to-bottom in the grid.

PNG format (.png)

A single PNG image containing all animation frames arranged in a grid. No metadata is included — the consumer must know the frame size and count ahead of time.

Aseprite JSON format (.json)

An Aseprite-compatible JSON metadata file alongside a sidecar PNG sprite sheet image. This format can be imported by Aseprite and any engine with Aseprite sprite sheet support (Godot, Phaser, LibGDX, etc.).

Schema

{
    "frames": [
        {
            "filename": "idle 0",
            "frame": { "x": 0, "y": 0, "w": 256, "h": 256 },
            "rotated": false,
            "trimmed": false,
            "spriteSourceSize": { "x": 0, "y": 0, "w": 256, "h": 256 },
            "sourceSize": { "w": 256, "h": 256 },
            "duration": 42
        }
    ],
    "meta": {
        "app": "Playable Workshop Tools for Blender",
        "image": "character.png",
        "format": "RGBA8888",
        "size": { "w": 1024, "h": 1024 },
        "scale": "1",
        "frameTags": [
            {
                "name": "idle",
                "from": 0,
                "to": 23,
                "direction": "forward"
            }
        ]
    }
}

Field reference

Frame

FieldTypeDescription
filenamestringFrame identifier ("animation_name index")
frameobjectRegion in the sheet: x, y, w, h (pixels)
rotatedboolAlways false (no rotation packing)
trimmedboolAlways false (no trim packing)
spriteSourceSizeobjectSame as frame (no trimming)
sourceSizeobjectOriginal frame dimensions: w, h
durationintFrame duration in milliseconds (1000 / fps)

Meta

FieldTypeDescription
appstringExporter identifier
imagestringFilename of the sidecar PNG image
formatstringPixel format (always "RGBA8888")
sizeobjectTotal sheet dimensions: w, h (pixels)
scalestringScale factor (always "1")
frameTagsarrayAnimation tag definitions

Frame tag

FieldTypeDescription
namestringAnimation name
fromintFirst frame index (inclusive)
tointLast frame index (inclusive)
directionstringPlayback direction (always "forward")

Frame indices in frameTags are global across all animations. For example, if “idle” uses frames 0–23 and “run” uses frames 24–31, the run tag would have "from": 24, "to": 31.

Packed binary format (.sprsh)

The packed binary format bundles the Aseprite-compatible JSON metadata and the sprite sheet PNG into a single .sprsh file.

Binary layout

Offset  Size    Type        Description
------  ------  ----------  ---------------------------
0       4       char[4]     Magic bytes: "SPSH"
4       4       uint32 LE   Format version (currently 1)
8       4       uint32 LE   JSON data length in bytes (N)
12      4       uint32 LE   Image data length in bytes (M)
16      N       bytes       JSON data (UTF-8, Aseprite schema)
16+N    M       bytes       PNG image bytes

Header (16 bytes)

OffsetSizeTypeDescription
04char[4]"SPSH" magic
44uint32 LEVersion (1)
84uint32 LEJSON length (N)
124uint32 LEImage length (M)

Reading a .sprsh file

import struct
import json

with open("character.sprsh", "rb") as f:
    magic = f.read(4)
    assert magic == b"SPSH"

    version, json_len, image_len = struct.unpack("<III", f.read(12))

    aseprite_data = json.loads(f.read(json_len).decode("utf-8"))
    image_data = f.read(image_len)  # Raw PNG bytes
use std::fs;

fn read_sprsh(path: &str) -> (serde_json::Value, Vec<u8>) {
    let data = fs::read(path).unwrap();
    assert_eq!(&data[0..4], b"SPSH");

    let version = u32::from_le_bytes(data[4..8].try_into().unwrap());
    let json_len = u32::from_le_bytes(data[8..12].try_into().unwrap()) as usize;
    let image_len = u32::from_le_bytes(data[12..16].try_into().unwrap()) as usize;

    let aseprite_data: serde_json::Value =
        serde_json::from_slice(&data[16..16 + json_len]).unwrap();
    let image_data = data[16 + json_len..16 + json_len + image_len].to_vec();

    (aseprite_data, image_data)
}

Notes

  • The JSON payload inside .sprsh uses the exact same Aseprite-compatible schema as the standalone .json format.
  • In .sprsh files, the meta.image field contains just the image name (no file path) since the image is embedded.
  • Image data is raw PNG file bytes, not decoded pixel data.
  • The sidecar PNG (for the .json format) is saved alongside the JSON file with the same base name.