QUICKUPSCALE

PRACTICAL GUIDE

Tilesets, animation and atlas handoff

Export every GIF display frame as PNG with Aseprite

Start here

On the tested build, aseprite --batch INPUT.gif --save-as frame000.png exported every composited display frame in order. The original six-frame GIF produced six complete 8×6 PNGs, from frame000.png through frame005.png.

Expected result:

Every exported RGBA byte matches the independent full-frame references. PNG files carry neither the animation’s frame delays nor its looping rule, so retain the original GIF and raw metadata.

What you need

Use the original 8×6 GIF and the downloadable inputs, scripts and references. The GIF has six image records, one global palette, an opaque logical background and loop count 0, meaning infinite looping. Some records cover only part of the canvas.

You need an already working Aseprite CLI and Bash. The package contains no editor executable. The actual run used a local GPLv2 Aseprite 1.1.7-dev build from commit 000ac2e; current desktop versions, GUI menus and browser GIF conversion were not executed.

Tested with: Local GPLv2 Aseprite 1.1.7-dev, source commit 000ac2e8949a0400a0b0604b2da4647040f732f3; Linux ARM64 container with Xvfb; independent Pillow 12.3.0 GIF and PNG decoding.

1. Use the recorded export route

Extract the package and choose a fresh output directory. The actual run used /runtime/aseprite, inputs under /work/assets and scripts mounted under /work/scripts:

export ASEPRITE_BIN=/runtime/aseprite
bash /work/scripts/export-gif.sh \
  /work/assets/partial-disposal.gif /work/runs/t32

In the ZIP, the script is at the package root. Substitute ./export-gif.sh and your own absolute paths for the recorded mounts; point ASEPRITE_BIN at your verified executable. Those substitutions are instructions for your environment, not a separately recorded execution.

The inner frame-export command was:

/runtime/aseprite --batch /work/assets/partial-disposal.gif \
  --save-as /work/runs/t32/frame000.png

The numbered filename selected sequence saving in this build. No frame-range, trimming or ignore-empty option was used. The wrapper refuses an existing output path and fails if no fresh frame files appear; inspect its logs as well as the frame count.

2. Check complete frames, not just image rectangles

The inspected GIF’s image records were:

Raw GIF image rectangles, disposal methods and delays
PNG indexSource rectangle (x,y,width,height)DisposalRaw delay
000(0,0,8,6)140 ms
001(2,1,4,3)20 ms
002(0,0,3,4)370 ms
003(4,3,3,2)1130 ms
004(0,4,2,2)120 ms
005(6,0,2,2)110 ms

All six actual exported PNGs were 8×6, including frame 001, whose stored GIF rectangle is only 4×3. Saving that rectangle alone would omit pixels carried over from earlier frames.

The package’s assets/golden/frame000.png through frame005.png are explicitly authored full-frame references. They are not newly generated Aseprite renders. The recorded native outputs were independently decoded and compared with those references and Pillow’s GIF composites.

3. Inspect the disposal boundaries

Frame 002 contains the temporary green block. In frame 003, pixel (1,1) is red again and (0,0) returns to the dark background: disposal 3 restored the earlier screen before the next image was drawn. Keeping every previous rectangle would leave green there; clearing everything would lose the red pixel. Disposal 2 also clears its blue rectangle before the next frame.

Actual complete Aseprite CLI frame 001: Complete screen including the blue partial rectangle, before disposal 2.
Frame 001, 8×6px. Complete screen including the blue partial rectangle, before disposal 2. Actual CLI export; not a GUI capture.
Actual complete Aseprite CLI frame 002: The temporary green block after disposal 2 cleared the preceding blue rectangle.
Frame 002, 8×6px. The temporary green block after disposal 2 cleared the preceding blue rectangle. Actual CLI export; not a GUI capture.
Actual complete Aseprite CLI frame 003: After disposal 3, the red pixel and dark background return before the next patch is drawn.
Frame 003, 8×6px. After disposal 3, the red pixel and dark background return before the next patch is drawn. Actual CLI export; not a GUI capture.

Independent Pillow verification checked the input rectangles, disposal, delays and composites, then every RGBA byte of all six actual native PNGs. All matched. The original GIF byte hash also remained unchanged.

4. Keep timing and loops separately

The wrapper also ran this inspection export:

/runtime/aseprite --batch /work/assets/partial-disposal.gif \
  --format json-array --data /work/runs/t32/native-timing.json

Its durations were [40, 1, 70, 130, 20, 10] ms. The raw GIF and original sidecar instead retain [40, 0, 70, 130, 20, 10] ms. The old editor interprets the zero-delay frame as 1 ms; its pinned duration code records that version’s clamp.

This is the round-trip failure: even perfect full-frame PNG pixels do not preserve GIF playback metadata, and the editor’s JSON is already an interpreted timing record. Keep the original GIF and raw fixture-manifest.json when zero delays or infinite looping matter. Do not infer uniform FPS from the PNG sequence.

These comparisons cover this opaque-background, global-palette fixture. Transparent GIF backgrounds, interlacing, changing local palettes and large-file performance remain untested.

Sources and next steps