Skip to content

Align Sprite Frames — Fix Animation Jitter

Frames cut from different sheets or trimmed separately wobble when played. The Align panel puts them on one canvas with a common anchor and measures how far each frame drifts off a smooth path.

Frames that were cut or trimmed one by one have different sizes, so an engine that places each one by its corner or centre makes the character hop. Normalizing puts every frame of a tag on one canvas (the largest frame's width and height plus padding, unless you type a size) and places each frame's opaque bounds at the same anchor, such as bottom-centre, moving art by whole pixels and never scaling it. Measure then reports the wobble that is left, in pixels, and Fix removes it while keeping intended motion, or pins every frame to the first.

Drop a sprite sheet, numbered frames, a GIF / APNG or an .aseprite file
Runs in your browser. Files are not uploaded.
Studio Sprite workspace: one frame with its pivot, a hit box and a hurt box, and the timeline with animation tags
The real Studio with a CC0 asset (OpenGameArt).
Exports to
  • Godot 4Verified· Godot 4.7.2
  • Unity 6Verified· Unity 6000.5.3f1
  • Phaser 3 / 4Verified· Phaser 3.90 + 4.2
  • PixiJS 8Verified· PixiJS 8.21
  • Aseprite JSONVerified· Phaser load.aseprite · PixiJS 8.21
  • .asepriteVerified· Aseprite 1.3.18
  • Spine / libGDXVerified· spine-canvas 4.2
  • Starling / Sparrow XMLVerified· Phaser 4.2
  • LÖVEVerified· LÖVE 11.5
  • DefoldBuilt· Defold bob.jar 1.13.1
  • GameMakerUNVERIFIED
  • CSS spritesVerified· Chromium
  • GIF / APNGDecoded· Pillow

Aligning, then measuring what is left

Aligning answers one question: where does each frame sit on its canvas? With "Measure each frame's opaque bounds first" on, the Studio finds each frame's visible pixels, makes the canvas as wide and tall as the largest of them, and places every frame so that its bounds touch the anchor. Bottom-centre puts the lowest opaque row on the canvas bottom and centres the bounds horizontally; a half pixel is rounded, never resampled, so pixel art stays sharp.

Bottom-centre means the middle of the lowest opaque row, not the feet: a sword tip, a cape or a dust puff below the feet moves it. That is why aligning is followed by a measurement. Measure takes one reference point per frame (bottom-centre of the art, centre of the art, the alpha centroid or the pivot), fits a smooth path through the series with a local quadratic five frames wide, and reports how far each frame sits off that path: the maximum and the RMS in pixels. Above 1.5 px it warns that the shake is visible. Frame-to-frame movement is reported separately, because it includes motion drawn on purpose.

Pivots, boxes and collision polygons move with the art, so a pivot placed at the feet is still at the feet after aligning or fixing.

Anchor
Where each frame's opaque bounds are placed on the shared canvas: bottom-centre, centre, top, bottom, left, right or top-left.
Wobble (residual)
How far a frame's reference point sits off the smoothed path, in pixels.
Fix (keep motion) / Pin to first frame
Remove only the wobble around the smooth path, or hold every frame on frame 1's point.

Example: three trimmed frames onto one canvas

The frames of one tag were trimmed separately to 20 × 30, 22 × 28 and 18 × 31 px. Align with the default canvas, padding 0 and the bottom-centre anchor:

canvas  = largest width × largest height = 22 × 31
offset  x = round((22 − w) ÷ 2)    y = 31 − h
frame 1  20 × 30  →  x = 1, y = 1
frame 2  22 × 28  →  x = 0, y = 3
frame 3  18 × 31  →  x = 2, y = 0
check   every lowest opaque row ends at y = 31; every middle is at x = 1 + 10 = 0 + 11 = 2 + 9 = 11

If the art itself was drawn a pixel or two off, Measure shows it and Fix corrects it; a worked jitter measurement is on sprite jitter after trimming.

What it does

  • One canvas size for the frames of a tag, a selection or the whole sprite, with padding and an anchor (bottom-centre, centre, top-left …).
  • Each frame's opaque bounds are measured first; art moves by whole pixels and is never scaled.
  • Jitter is measured against a smooth path and marked on the timeline, in pixels (max and RMS).
  • Fix keeps the intended motion, or pins every frame to the first one.

How to do it

  1. Drop the frames or sheet and apply the import.
  2. Select the animation on the timeline and open the Align panel.
  3. Press Align frames, then Measure to see the wobble in pixels.
  4. Fix it, play the loop to check, and export.

Check the result

  • Play the tag with onion skin (F3): the feet of neighbouring frames should overlap instead of stepping sideways.
  • Press Measure: the Align panel shows "Wobble: max … px, RMS … px", and the timeline marks each frame's distance from the smooth path.
  • After Fix the panel shows the wobble before and after; play the loop again, including the step from the last frame back to the first.
  • In Pack & Export's Packed frames list every frame of the tag has the same canvas size.

Troubleshooting

SymptomLikely causeHow to checkFix
An error says a frame does not fitA typed canvas is smaller than a frame, and pixels are never scaledThe message names the largest frame's sizeLeave width and height empty (the largest frame is used) or type a bigger size
The character still hops after aligningBottom-centre follows the lowest opaque pixel: a weapon, cape or shadow under the feet moved itWith onion skin the lowest pixels line up but the feet do notMeasure with the centre of the art or the alpha centroid, or place pivots on the feet and use the Pivot reference
A walk cycle is pinned in place after FixPin to first frame holds every frame on frame 1's point, so motion drawn into the frames is removed tooFrame-to-frame movement was large while the wobble was smallUndo and use Fix (keep motion), or leave the tag as it was
Fix refuses to runJitter can only be fixed on frames that share one canvasThe message asks to normalize the frames firstPress Align frames for the scope, then Measure and Fix

Other ways to do it

OptionWhen it is the better choice
Draw every frame on one fixed canvas from the start (Aseprite keeps one canvas per sprite)The art is yours to redraw; nothing needs aligning afterwards.
Keep the trim offsets when packing instead of trimming frames apartThe frames were aligned before packing and only the packer broke it: see jitter after trimming and the sprite sheet packer.

Engine exports and how each was checked

"Verified" means the exported files were loaded by the real engine or tool on real CC0 assets and the result was compared with the source. Anything that was not loaded is labelled as such.

TargetFilesCheck
Godot 4.tres SpriteFrames · .tscn AnimatedSprite2D · .png.importVerified Godot 4.7.2Loaded by Godot; every animation frame drawn and compared with the source file. The scene uses Nearest filtering; rotation is never used.
Unity 6.unity.json · Editor/NerulioSpriteImporter.cs · AnimationClipVerified Unity 6000.5.3f1Batch mode: sprite rects, pivots, pixels, clip keys and durations checked.
Phaser 3 / 4atlas JSON (multiatlas) · .anims.jsonVerified Phaser 3.90 + 4.2Frames and animations drawn by both versions.
PixiJS 8spritesheet JSON · animations · anchor · meta.scaleVerified PixiJS 8.21Rotated and multi-page atlases included.
Aseprite JSONhash / array · frameTags · slicesVerified Phaser load.aseprite · PixiJS 8.21Tags keep their direction and repeat count.
.asepritelayers · tags · durations · slicesVerified Aseprite 1.3.18Opened and rendered by the Aseprite command line.
Spine / libGDX.atlasVerified spine-canvas 4.2Every region drawn by the Spine runtime, rotated and trimmed; libGDX itself was not run.
Starling / Sparrow XMLXML per pageVerified Phaser 4.2Phaser 3.90 draws trimmed XML frames wrongly (its own bug); the "Sparrow XML (Phaser 3)" preset turns trim off and passes in 3.90 and 4.2.
LÖVELua quads · nerulio_atlas.lua · main.luaVerified LÖVE 11.5Every frame drawn and every animation stepped through the shipped helper.
Defold.atlas · .tilesourceBuilt Defold bob.jar 1.13.1Built by Defold's command-line builder; the built texture's UVs were read back.
GameMakerspr_<name>_stripN.png · gamemaker.jsonUNVERIFIEDNot loaded in GameMaker (no headless GameMaker). The strips are cut the way its strip importer cuts them.
CSS sprites.css · preview .htmlVerified ChromiumEvery class drawn from the shipped stylesheet.
GIF / APNGone file per animationDecoded PillowEvery frame and delay decoded again after export; APNG is exact.

Evidence

GIF decoder: 408 of 408 frames identical to Pillow on 29 real GIFs; APNG 132 of 132 on 16 files. .aseprite: 231 corpus files imported, re-written and reopened in Aseprite 1.3.18 with the same tags, durations and pixels (231/231). Exports: 49 runs in the real engines on real CC0 assets, 47 pass — the 2 failures are Phaser 3.90's trimmed-XML bug, which the Phaser 3 preset avoids.

Limits

  • The anchor is geometric (lowest opaque row, centre of the bounds); nothing recognises feet.
  • Padding is 0–256 px, and a fix that would move a frame more than 64 px is clamped.
  • When a fix would push a frame past the canvas edge, the canvas grows instead of clipping the art.
  • A grid or island guess is a preview with a confidence; nothing is cut until you press Apply, and every choice has one-click alternatives.
  • .aseprite files whose layers do not compose exactly (e.g. group opacity) are imported flattened, and the Import panel says why.
  • Rotated frames in an imported atlas stay rotated; their export is UNVERIFIED.
  • Images up to 32,768 px per side (268 million pixels), subject to your browser's memory.

Versions and sources

Behaviour from src/game/frame-ops.js and src/game/jitter.js (unit-tested in tests/game-frame-ops.test.mjs and tests/game-jitter.test.mjs). The smoothing keeps deliberate motion: on a 16-frame sine it passes 0.998 of the motion at window 5, where a plain moving average keeps 0.852.

Official documentation used

Questions

Will aligning blur my pixel art?

No. Frames only move by whole pixels and keep their size; nothing is resampled.

Does alignment change the pivot?

Pivots move with the art, so the character stays where it was relative to its pivot.

Is it free, and do I need an account?

Everything on these pages is free and works without an account. There is no watermark and no export that is held back.

Are my sprites uploaded?

No. The Studio decodes, packs and exports in this browser tab. Projects are autosaved in the browser (IndexedDB) and can be saved as a .nerulio file.

Does it need Aseprite or an engine plugin installed?

No. .aseprite files are read and written in the browser, and each engine bundle carries what that engine needs (for Godot, the import settings and scene; for Unity and the Godot TileSet, a small editor script).