Aseprite File Format (.ase/.aseprite) & GIMP Palette (.gpl) Specifications #
Official Aseprite Specifications: aseprite/aseprite Recommended Ebitengine Integration Library: SolarLune/goaseprite
1. References & Types #
ASE files use Intel (little-endian) byte order.
BYTE: An 8-bit unsigned integer valueWORD: A 16-bit unsigned integer valueSHORT: A 16-bit signed integer valueDWORD: A 32-bit unsigned integer valueLONG: A 32-bit signed integer valueFIXED: A 32-bit fixed point (16.16) valueFLOAT: A 32-bit single-precision valueDOUBLE: A 64-bit double-precision valueQWORD: A 64-bit unsigned integer valueLONG64: A 64-bit signed integer valueBYTE[n]: “n” bytes.STRING:WORD: string length (number of bytes)BYTE[length]: characters (in UTF-8). The'\0'character is not included.
POINT:LONG: X coordinate valueLONG: Y coordinate value
SIZE:LONG: Width valueLONG: Height value
RECT:POINT: Origin coordinatesSIZE: Rectangle size
PIXEL: One pixel, depending on the image pixel format:- RGBA:
BYTE[4], each pixel has 4 bytes in order Red, Green, Blue, Alpha. - Grayscale:
BYTE[2], each pixel has 2 bytes in order Value, Alpha. - Indexed:
BYTE, each pixel uses 1 byte (the index).
- RGBA:
TILE: Tilemaps: Each tile can be an 8-bit (BYTE), 16-bit (WORD), or 32-bit (DWORD) value with masks for bit meaning.UUID: A Universally Unique Identifier stored asBYTE[16].
2. File Architecture #
The format consists of an ASE header followed by frames. Color depth can be 8 (Indexed), 16 (Grayscale), or 32 (RGBA), compressed with ZLIB.
To read the sprite:
- Read the ASE Header (128 bytes).
- For each frame (indicated by header frame count):
- Read the Frame Header (16 bytes).
- For each chunk in this frame (indicated by frame header chunk count):
- Read the chunk data (Layer info, Cel, Palette, Tags, Slices, or User Data).
3. Header Specification (128 Bytes) #
DWORD File size
WORD Magic number (0xA5E0)
WORD Frames
WORD Width in pixels
WORD Height in pixels
WORD Color depth (32 bpp = RGBA, 16 bpp = Grayscale, 8 bpp = Indexed)
DWORD Flags:
1 = Layer opacity has valid value
2 = Layer blend mode/opacity is valid for groups
4 = Layers have a UUID
WORD Speed (milliseconds between frames - DEPRECATED: use frame header duration)
DWORD Reserved (0)
DWORD Reserved (0)
BYTE Transparent palette index (for Indexed sprites)
BYTE[3] Ignore
WORD Number of colors (0 means 256 for old sprites)
BYTE Pixel width ratio
BYTE Pixel height ratio
SHORT X position of grid
SHORT Y position of grid
WORD Grid width (default 16x16)
WORD Grid height
BYTE[84] For future (set to 0)4. Frame Header (16 Bytes) #
DWORD Bytes in this frame
WORD Magic number (0xF1FA)
WORD Old chunk count (if 0xFFFF, use new field)
WORD Frame duration (in milliseconds)
BYTE[2] Reserved (0)
DWORD New chunk count (if 0, use old field)Each chunk follows this format:
DWORD Chunk size (>= 6 bytes)
WORD Chunk type
BYTE[] Chunk data5. Primary Chunk Types #
- Layer Chunk (
0x2004): Flags (Visible, Editable, Background), Layer type (0=Normal, 1=Group, 2=Tilemap), Blend mode, Opacity, Name. - Cel Chunk (
0x2005): Layer index, X/Y position, Opacity, Cel type (0=Raw, 1=Linked, 2=ZLIB Compressed Image, 3=ZLIB Compressed Tilemap), Z-Index. - Tags Chunk (
0x2018): Animation tag ranges (From frame,To frame), Loop direction (0=Forward,1=Reverse,2=Ping-pong,3=Ping-pong Reverse), Repeat count, Tag name. - Palette Chunk (
0x2019): New palette size, First/Last color index, RGBA values, optional Color name strings. - Slice Chunk (
0x2022): Slice keys, 9-patch center bounds, pivot X/Y coordinates relative to slice origin. - Tileset Chunk (
0x2023): Tileset ID, Tile width/height, compressed tilemap images.
6. GIMP Palette File Format Extension (.gpl) #
Aseprite supports reading and writing GIMP Palette (.gpl) files extended with RGBA alpha channel information:
GIMP Palette
Channels: RGBA
#
0 0 0 0 Transparent
254 91 89 255 Red
247 165 71 255 Orange
243 206 82 255 Yellow
106 205 91 255 Green
87 185 242 255 Blue
209 134 223 255 Purple
165 165 167 255 GrayHeader MUST specify Channels: RGBA, and each palette entry contains Red Green Blue Alpha Name.
7. Zero-Allocation goaseprite Integration Pattern #
When using SolarLune/goaseprite with Ebitengine, pre-slice all frame sub-images at initialization time and reuse a persistent ebiten.DrawImageOptions field so Draw() performs zero heap allocations:
package entity
import (
"fmt"
"image"
"github.com/SolarLune/goaseprite"
"github.com/hajimehoshi/ebiten/v2"
)
type AsepriteActor struct {
File *goaseprite.File
Frames []*ebiten.Image
X, Y float64
drawOp ebiten.DrawImageOptions
}
func NewAsepriteActor(jsonBytes []byte, atlas *ebiten.Image) (*AsepriteActor, error) {
if len(jsonBytes) == 0 {
return nil, fmt.Errorf("sprite-animation: empty Aseprite JSON manifest")
}
if atlas == nil {
return nil, fmt.Errorf("sprite-animation: nil atlas image")
}
file := goaseprite.Read(jsonBytes)
if file == nil || len(file.Frames) == 0 {
return nil, fmt.Errorf("sprite-animation: failed to parse Aseprite frames from JSON")
}
// Pre-slice every frame at load time (zero SubImage calls in Draw)
frames := make([]*ebiten.Image, len(file.Frames))
for i, f := range file.Frames {
rect := image.Rect(f.X, f.Y, f.X+f.Width, f.Y+f.Height)
frames[i] = atlas.SubImage(rect).(*ebiten.Image)
}
return &AsepriteActor{
File: file,
Frames: frames,
}, nil
}
func (a *AsepriteActor) Update(dt float32) {
a.File.Update(dt)
}
func (a *AsepriteActor) Draw(screen *ebiten.Image) error {
idx := a.File.CurrentFrame
if idx < 0 || idx >= len(a.Frames) {
return fmt.Errorf("sprite-animation: frame index %d out of bounds [0, %d)", idx, len(a.Frames))
}
a.drawOp.GeoM.Reset()
a.drawOp.GeoM.Translate(a.X, a.Y)
screen.DrawImage(a.Frames[idx], &a.drawOp)
return nil
}