Skip to content

Spine / libGDX Atlas Packer — .atlas with Rotation and Trim

The .atlas text format is shared by Spine and libGDX. This export writes it with rotation and trim, and the Spine runtime drew every region — rotated and trimmed ones included — in the verification run.

A Spine / libGDX .atlas is a text file that describes one or more page images and the named regions on them: for each region bounds (x, y, width, height on the page), optional offsets (whitespace stripped from the left and bottom, then the original size) and rotate when the region was packed turned. Spine skeletons find their attachment images by region name. Nerulio writes this format from any frames, with trim and 90° rotation, and the official Spine runtime spine-canvas 4.2 drew every region, rotated and trimmed ones included. libGDX reads the same format but was not run.

Drop frame images, a GIF or an .aseprite file to pack
Runs in your browser. Files are not uploaded.
Studio Pack & Export: the packed atlas page with every frame outlined, the used area and the export targets
The real Studio with a CC0 asset (OpenGameArt).
Exports to
  • Spine / libGDXVerified· spine-canvas 4.2
  • 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
  • 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

Reading a .atlas file

The file starts with a page: the image file name, then lines such as size, filter and pma (premultiplied alpha). Each region follows as its name and its property lines until the next name; a new page starts with another image name.

Offsets are measured from the bottom-left of the original image, the opposite of JSON atlases, which measure from the top. For a 40 × 29 frame whose visible 18 × 15 pixels start 10 px from the top, the bottom offset is 29 − 10 − 15 = 4.

A rotated region lies turned 90° counter-clockwise on the page, which is the Spine format's direction (TexturePacker JSON stores rotated frames clockwise), and bounds keeps the unrotated width and height. The runtime turns the region back when it draws it.

Page
An image file with its size, texture filter and premultiplied-alpha flag.
Region
A named rectangle on a page; skeleton attachments reference it by name.
bounds
x and y on the page, then the packed (unrotated) width and height.
offsets
Pixels stripped from the left and from the bottom, then the original width and height.
rotate
90 (or true) = stored turned 90° counter-clockwise.

Example: the six run frames as a .atlas (rotation allowed)

run.png
size:33,57
filter:Nearest,Nearest
pma:false
run_0
bounds:17,23,18,15
offsets:10,4,40,29
rotate:90
run_4
bounds:0,0,16,16
offsets:10,5,40,29

run_0: 18 × 15 visible pixels, stored turned → covers 15 × 18 on the page at (17, 23)
offsets: left 10, bottom 29 − 10 − 15 = 4, original 40 × 29
run_4: not rotated (16 × 16); bottom 29 − 8 − 16 = 5

Five of the six frames were stored turned, and the page is 33 × 57 instead of 38 × 50 without rotation. Every region keeps its full 40 × 29 size through offsets, so attachments do not shift between frames.

From a JSON atlas entry to a .atlas region

TexturePacker-style JSONSpine / libGDX .atlasHow it converts
frame {x, y, w, h}bounds:x,y,w,hSame numbers (unrotated size)
spriteSourceSize {x, y}offsets: left, bottombottom = sourceSize.h − y − h
sourceSize {w, h}offsets: …, width, heightSame numbers
rotated: true (stored clockwise)rotate:90 (stored counter-clockwise)The pixels are turned the other way, so the page is drawn again, not relabelled
meta.image, meta.sizepage name line, size:w,hSame

What it does

  • .atlas pages with bounds and offsets measured from the bottom, as the format expects.
  • rotate:90 stored counter-clockwise when rotation saves space.
  • Trim with the original size kept, alias of identical images, padding and multipack.
  • Checked with spine-canvas 4.2 (the Spine runtime); the libGDX runtime was not run.

How to do it

  1. Drop the images or frames.
  2. In Pack & Export choose Spine / libGDX.
  3. Allow rotation if you want the smallest pages.
  4. Point your Spine skeleton or libGDX TextureAtlas at the .atlas file.

Files you get

FileWhat it is for
run.atlasPages (size, filter:Nearest,Nearest, pma:false) and one region per frame with bounds, offsets for trimmed frames and rotate:90 for turned ones.
run.png (run-0.png, run-1.png …)The page images; several when multipack needs them.
README-SPINE-LIBGDX.mdHow the format is laid out, and the libGDX loading call.

Use it with Spine or libGDX

  1. Name your skeleton's attachments (or their paths) like the regions, for example run_0: the runtime looks up regions by that name.
  2. Load the .atlas with your Spine runtime's atlas loader next to the skeleton data; keep the PNG pages in the same folder, because the atlas names them without a path.
  3. libGDX: new TextureAtlas(Gdx.files.internal("run.atlas")), then findRegion("run_0"). This path was not run in Nerulio's checks.
  4. Allow rotation only when every program that reads the file handles rotate; the Spine preset allows it by default.

Troubleshooting

SymptomLikely causeHow to checkFix
A region is missing at runtimeThe attachment name and the region name differ: case, file extension or a folder prefixSearch the .atlas for the exact nameRename the frame before export, or the attachment path in Spine
Trimmed regions sit too high or too low in a custom loaderThe loader reads the second offset as a top offsetWith offsets:10,4,40,29, 4 is the margin at the bottomUse a Spine or libGDX loader, or convert: top = height − bottom − region height
Rotated regions appear sideways or mirrored in a custom loaderIt ignores rotate, or turns the wrong wayRegions with rotate:90Turn rotation off before exporting, or turn the region back clockwise
Dark or light fringes around soft edgesThe page's alpha does not match the pma flag and the blend modepma:false in the page linesKeep premultiplied alpha off for this export: its page lines always say pma:false

Other ways to do it

OptionWhen it is the better choice
Spine's own texture packerYou export skeletons from the Spine editor: it packs attachments while exporting and has settings Nerulio lacks, such as a polygon packing mode, bleed and duplicate padding.
A JSON atlas insteadThe frames are sprite animation rather than skeleton attachments: a JSON atlas carries the animations too; see PixiJS or Phaser.

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

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. Packer head-to-head on 5 real frame sets (CodeAndWeb free web packer, GAPTools, free-tex-packer): smallest sheet on all 5 with rotation allowed, 4 of 5 without; every frame restored pixel-exact.

Limits

  • Polygon (mesh) packing is not offered: none of the verified engines draws a mesh atlas from exported data.
  • GameMaker strips are UNVERIFIED in GameMaker itself.
  • Scale variants (@0.5x–@4x) use nearest-neighbour only.
  • Trimmed Starling XML is drawn wrongly by Phaser 3.90; use the "Sparrow XML (Phaser 3)" preset there.

Versions and sources

The official Spine runtime spine-canvas 4.2.120 parsed Nerulio's .atlas and drew every region as a region attachment through its SkeletonRenderer, for trimmed ninja frames and rotated archer frames; the pixels matched the source frames. libGDX (Java) was not run. Field meanings follow the Spine atlas format documentation.

Official documentation used

Questions

Is it tested in libGDX?

No. It was drawn by the Spine runtime (spine-canvas 4.2); libGDX reads the same format but was not run here.

Does it pack meshes (polygon packing)?

No, rectangles only: none of the verified runtimes draws a mesh atlas from exported data.

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).