Sprite Sheet Viewer

Drop a sprite sheet — or several frame images at once — and watch it play. Set the frame rate, step through it frame by frame, and take the frames back out when you are done.

Drop a sprite sheet, or a set of frame images, or click to choose

PNGWEBPGIFBMPJPEG

Drop several images at once and each one becomes a frame, ordered by filename: walk1, walk2, walk10.

What This Tool Does

Drop a sprite sheet and watch it play. The frame rate, the loop mode and the zoom are all live, you can step through one frame at a time, and two overlays exist for the question a viewer is usually really asking — which frame is wrong. When you are done, the frames come back out: this page opens the export panel on the PNG frames tab, with every frame seated in the shared box.

It is the same studio the splitter, the GIF page and the sheet maker use. All four outputs — PNG frames, an animated GIF, a packed sheet and its atlas JSON — are offered here too; only the tab that opens first, and how its frames are sized, differ.

One Sheet, Or A Pile Of Frames

Both inputs are accepted, and they take different routes.

One image is treated as a sheet. It is measured: a regular lattice read off the sheet's own gutters wins outright, and failing that, sprites are traced individually when more than one is found and their widths or heights differ by more than 10 percent of the largest. Failing both, the grid slicer stays in force with numbers you type. The status line always says which happened and why, and one button switches to the other.

Several images are treated as frames — one image each, in filename order, stitched into a single composite sheet. Nothing is detected on that composite: its geometry was laid out here, so there is no lattice to measure and no sprites to trace, and the status line says so instead of reporting a measurement that never happened.

Drop the image files themselves, not the folder holding them: a folder arrives as one entry naming the directory rather than as its contents, and that entry is not an image. PNG, WEBP, GIF, BMP and JPEG are accepted, and pasting an image from the clipboard works as well as dropping one.

Filename Order

Filenames are compared one character at a time, and where both names have digits at the same point the whole digit run is compared by value rather than as text. That is what puts walk2 ahead of walk10 without anyone having to rename anything. Letters are compared ignoring case and accents, and if two names are otherwise identical the shorter of the two comes first, so frame7 precedes frame07 rather than the two landing in whatever order the browser supplied them.

Filename order is only a first guess, though, and the strip is where you fix it.

Watching It Closely

The playback bar starts the animation at 12 frames a second and offers anything from 1 to 60. Forward, reverse and ping-pong are the three loop modes; ping-pong turns at each end without holding the endpoint for an extra beat. Zoom steps through 1×, 2×, 4× and 8×. The transport buttons jump to the first or last frame and step one frame at a time, the scrub bar seeks anywhere, and seeking pauses first — a scrub bar fighting a running animation snaps back and reads as a broken control. Moving the frame-rate slider keeps the frame that is on screen rather than restarting from the beginning.

Two overlays answer "which frame is wrong", and both are off until you ask for them:

OverlayWhat it draws
Onion skinThe previous frame, or both neighbours, ghosted behind the current one
Box outlineThe frame box as a dashed rectangle, plus a solid line on the anchor row

The ghosts are the neighbours in the strip, not in the direction of play, so they stay on the same sides in reverse and do not flip over mid-bounce in ping-pong. The anchor line is the one to read: every frame's anchored edge is pinned to that row, so a sprite whose own edge sits off it is the sprite that bobs.

Reordering, And Choosing What Plays

Every frame is a thumbnail in the strip. Click one — or click its box on the sheet above — to leave it out of the animation and out of every export. A From/To range narrows playback to a run of frames, row chips pick one row of a measured sheet, and one button switches off every frame with no opaque pixel in it, which is what stops the animation freezing on the blanks a last row is usually padded with.

Frames can be dragged into a different order in the loose-images case only, and that limit is deliberate rather than unfinished. A grid's sequence is its lattice and a traced sheet's is its reading order; both are re-derived from the sheet whenever a setting changes, so a hand-made order there would be silently thrown away on the next slider release. A set of dropped files is the one input whose sequence is genuinely yours to decide.

Where dragging is offered, each thumbnail prints its frame number, and the whole thing works from the keyboard: tab to a frame's handle, press space to pick it up, move it with the arrow keys, press space again to drop it, or escape to cancel. Every step is announced. Note that the From/To range still addresses frames by their original number rather than by their position on screen — which is why the numbers are printed.

The Frame Box

Every frame is seated in one shared box, and since this release the preview, the packed sheet, the atlas, the GIF and the boxed ZIP all read that one set of placements. What plays is what downloads.

Automatic sizing takes the largest selected frame on each axis independently, adds the padding on both sides, and rounds up to the next even number — or to the next power of two if you ask. Manual sizing takes a typed width and height, or one of the presets: 32, 64, 128, 256 and 512. Padding insets all four sides, and the anchor decides where the sprite sits in what is left. The default is bottom-centre so that feet stay planted; all nine anchors are one click away.

Trimming the transparent edges first is what makes an anchor mean anything on a sheet whose frames are padded to a fixed canvas — and turning it on now changes the preview too, because the preview draws from the trimmed placements rather than the raw cells. A cell that trims away to nothing renders blank. The switch is offered only where it could act: a traced sprite's box is already tight against its own pixels, and a sheet with no transparency has no transparent edge to find, so in both cases the control says why instead of greying out silently.

A sprite bigger than the box is reported rather than resized behind your back. The red badge names the offending frames, spelling out the first six and counting the rest. In the preview and in the GIF the canvas grows so the whole overhang stays visible; on the packed sheet the sprite is drawn from a negative offset inside its own cell instead, so it runs under its neighbour or off the edge of the PNG, and a boxed ZIP frame is exactly the box size so the overhang is cut off at its edges. Raising the box size, deselecting the frame or switching to scale-down all resolve it; scale-down shrinks by the same factor on both axes so proportions survive.

Getting The Frames Back Out

Four outputs. The GIF, the packed sheet and the atlas are all built from the same placements, and so is the ZIP — unless you switch it to Raw crop, which takes each frame's own rect instead:

OutputWhat you get
PNG frames (ZIP)One PNG per selected frame — the box size, or each frame's own size as sliced
Animated GIFThe box size times 1×, 2×, 4× or 8×, at the frame rate you set
Sprite sheet (PNG)The selected frames packed into an even grid, strip or fixed column count
Atlas (JSON)TexturePacker format, keyed by file name or as an array

Frame files are numbered in the order they play and zero-padded to the frame count, so a file manager lists them the way they run rather than putting frame_10 ahead of frame_2. When the frames came from loose images rather than from one sliced sheet, each file also carries the name of the image it came from — frame_03_walk_idle.png — after the number rather than in front of it, so the sort order still follows playback even once you have dragged the frames into a different order. A GIF can only play forward, so reverse and ping-pong are honoured by baking the order into the frames it contains — which means a ping-pong GIF holds 2n − 2 frames for n selected ones. The ZIP does not do that; it ships one PNG per selected frame however the loop is set. GIF delays are stored in hundredths of a second, so most frame rates cannot survive the container exactly, and the panel says what the file will really play at whenever that differs from what you asked for.

One more button bundles the sheet, its atlas and the GIF into a single ZIP under a shared name, produced by exactly the same calls the individual downloads make — so the bundle cannot contain a sheet that disagrees with its own atlas.

Limits

A single sheet is refused past 16384 pixels on a side, because the canvas allocation itself takes the tab down at that point; above 8192 and up to that limit, the tool warns that detection and export will take a few seconds rather than looking hung. A batch of loose images is refused as soon as the composite they would be stitched into crosses the same 16384-pixel limit — checked as the images are read, so the batch stops early rather than after decoding all of them, and nothing is loaded at all.

Frequently asked questions

Is my sprite sheet uploaded anywhere?
No. The image is decoded, drawn and re-encoded by JavaScript running in the same browser tab — the heavier passes run in a Web Worker served from this page — and nothing is ever sent over the network. That is also why the tool keeps working offline once the page has loaded.
Can I drop a folder of separate frame images?
Drop the images themselves rather than the folder that holds them. A dropped folder arrives as a single entry naming the directory, which is not an image, so it comes back with a message saying it could not be read; selecting or dragging several image files works. When more than one file arrives, each image becomes one frame, they are put in filename order, and they are stitched into one composite sheet behind the scenes — so the strip, the preview and every export behave exactly as they do for a real sheet. No detection runs on that composite, because its geometry was laid out here and is already known exactly.
Why do my frames play in the wrong order — walk10 before walk2?
They do not. Filenames are walked a character at a time, and where both names have digits the whole digit run is compared by value rather than as text, so walk2 comes before walk10 with no zero padding needed. Letters are compared ignoring case and accents. When two names are otherwise identical the shorter one comes first, so frame7 precedes frame07 instead of the order depending on which file the browser happened to hand over first. If the filenames are simply not the order you want, drag the frames in the strip.
What is the onion skin for?
Comparing a frame against the ones on either side of it, which is how a frame that jumps gives itself away. Previous ghosts the frame before this one; Both ghosts the frames on either side. Ghosts are drawn behind the current frame at a quarter opacity, and they always mean the neighbours in the strip — they do not swap sides when the loop is set to reverse, and they do not flip over halfway through a ping-pong bounce. Below two frames nothing is ghosted at all, and with exactly two frames the single neighbour is drawn once rather than twice over itself.
What do the box outline and the anchor line show?
The dashed rectangle is the frame box every frame is seated in — the same box the packed sheet, the atlas JSON and the GIF are built from. The solid horizontal line across it is the anchor row: the row each frame's anchored edge is pinned to, which under the default bottom-centre anchor is the ground line the characters stand on. A sprite whose own anchored edge does not sit on that line is one whose artwork is offset relative to its neighbours, and that offset is exactly what makes a character appear to bob during playback. The line accounts for padding, so it moves with the usable area rather than sitting a fixed distance from the box border.
Does what I see in the preview match what downloads?
Yes, and that is what this release changed. The preview, the packed sheet PNG, the atlas JSON, the animated GIF and — with Boxed frames selected — the ZIP all read one array of placements, so a frame sits in the same place in all five. One caveat is worth knowing. When a sprite is bigger than the box and you have asked to be warned rather than have it scaled down, the preview and the GIF grow their canvas so the whole overhang stays visible, while the packed sheet still draws that sprite from a negative offset inside its own cell — so on the sheet it runs under its neighbour or off the edge, and a boxed ZIP frame cuts the overhang off at the file's own edges. The red badge under the box controls names exactly which frames are affected.
Why is my exported GIF a different size than it used to be?
Because the GIF is now the frame box rather than a bounding box the GIF path worked out for itself. A 63-pixel grid exports at 64 pixels, since the automatic box size rounds up to the next even number, and any padding you have set is included too. That is the price of the GIF and the packed sheet finally agreeing about where a frame sits. The scale buttons multiply that size by 1, 2, 4 or 8, and the exact output raster is printed on the export panel so you can see it before committing.
Can I get the frames back out at their own size instead of the box size?
Yes — the PNG frames tab carries a Raw crop / Boxed switch. This page starts on Boxed, because the input it is built around is a set of loose images that genuinely differ in size; Raw instead gives one PNG at each frame's own size, as sliced — note that Raw takes the frame rect itself, so with trimming on it still ships the full untrimmed slice rather than a tight crop. Either way there is exactly one PNG per selected frame, numbered in the order they play and zero-padded to the frame count so a file manager lists them the way they run.