Sprite Sheets, Explained

After reading this you will understand how a sprite sheet stores many frames in one image, how to compute the grid math for packing and slicing, and how to read the CSS and JSON coordinates the calculator produces.

What a sprite sheet is

A sprite sheet is a single image that holds many smaller images arranged on a grid. Instead of loading 24 separate frames of a walk cycle, you load one file and show a different rectangle of it each frame. The browser or game engine keeps the whole sheet in memory once, then draws slices from it.

Here is the hook. Suppose you have 16 icons, each 32×32 pixels. Stored separately, that is 16 HTTP requests. Packed into a 4×4 grid, it is one 128×128 image and one request. The pixels are identical; only the packaging changed. That packaging is what saves load time and draw calls.

The idea has two directions. Packing takes many frames and lays them out into one sheet, then reports where each frame landed. Splitting takes an existing sheet plus a grid description and cuts it back into separate files. This calculator does both.

When to use one, and when not to

Sprite sheets pay off when you have many small images that ship together and are drawn often: UI icons, tile sets, animation frames, particle textures. The win is fewer network requests and fewer texture bindings on the GPU. A game drawing 200 tiles per frame from one sheet issues far fewer state changes than one binding 200 textures.

They do not pay off for a handful of large, unrelated images. Two full-screen photos gain nothing from being glued together, and the combined file can exceed the maximum texture size some hardware allows (often 4096×4096 or 8192×8192). If your frames have different sizes and you need tight packing, a fixed grid wastes space; a bin-packing layout fits better but is harder to address by index.

Watch for texture bleeding. When a frame is scaled or the sheet uses linear filtering, pixels from the neighboring cell can leak across the boundary. A one or two pixel transparent gutter between frames prevents this. Budget for it in your grid math.

The grid math

Most sheets use a uniform grid: every cell is the same width and height. Let each frame be w by h pixels, with g pixels of gutter between cells and c columns. The pixel position of frame index i (counting from 0, left to right, top to bottom) is:

x_i = (i \bmod c)\,(w + g), \quad y_i = \left\lfloor \frac{i}{c} \right\rfloor (h + g)

Here i \bmod c is the column, and \lfloor i / c \rfloor is the row. The term w + g is the horizontal stride: how far you step to reach the next column. The floor function drops the fractional part, so index 5 in a 4-column grid gives row \lfloor 5/4 \rfloor = 1 and column 5 \bmod 4 = 1.

The full sheet size for n frames in c columns is:

r = \lceil n / c \rceil, \quad W = c\,w + (c-1)\,g, \quad H = r\,h + (r-1)\,g

The number of rows r rounds up because a partial last row still needs a full row of pixels. The gutter appears c-1 times horizontally, not c, because you only need gaps between cells, not after the last one.

A worked example with the demo data

Sixteen 32-pixel frames in four columns

Take 16 frames, each 32×32, no gutter (g = 0), packed into c = 4 columns. This matches the calculator defaults, so pressing the demo button reproduces every number below.

  1. Rows: r = \lceil 16/4 \rceil = 4.
  2. Sheet width: W = 4 \cdot 32 + 3 \cdot 0 = 128 pixels.
  3. Sheet height: H = 4 \cdot 32 + 3 \cdot 0 = 128 pixels.
  4. Frame 6 lands at column 6 \bmod 4 = 2, row \lfloor 6/4 \rfloor = 1.
  5. Its pixel position: x = 2 \cdot 32 = 64, y = 1 \cdot 32 = 32.

So frame 6 occupies the rectangle from (64, 32) to (96, 64). The calculator writes exactly this into the JSON and the CSS.

With 16 frames of 32 pixels and 4 columns, the sheet is 128 by 128 pixels in a 4 by 4 grid. Raising columns to 8 makes a 256 by 64 sheet (8 by 2). Dropping to 2 columns makes a 64 by 256 sheet (2 by 8). Adding a 2 pixel gutter with 4 columns grows the sheet to 134 by 134.

Reading the CSS and JSON output

The calculator gives you two ways to address frames. The JSON is a plain map from a name or index to a rectangle. Frame 6 from the example appears as coordinates and size:

JSON entry for frame 6 in the 128×128 demo sheet
FieldValueMeaning
x64Left edge in pixels
y32Top edge in pixels
w32Frame width
h32Frame height

The CSS uses the background-position trick. A single element gets the sheet as its background, a fixed width and height of one cell, and a negative offset to slide the correct frame into view. For frame 6 that offset is background-position: -64px -32px;. The negatives are correct: you move the background up and left so the target cell sits at the element's origin.

Animate a sprite by stepping background-position through each frame with CSS steps() timing, or by changing the offset in JavaScript on a timer. A 4 by 4 walk cycle at 12 frames per second cycles every 16/12 \approx 1.33 seconds.

Splitting a sheet back apart

Slicing reverses the math. You supply the frame size or the grid dimensions, and the calculator cuts each cell into its own image, then bundles them in a ZIP. If you know the sheet is 128×128 and holds a 4×4 grid, the frame size is 128/4 = 32 pixels. If instead you know the frame size, the column count is \lfloor W / (w+g) \rfloor when a gutter is present.

Splitting is only clean when the original sheet was built on a true uniform grid. If the sheet was packed with variable-size frames or an odd trailing row, a fixed grid will cut some frames off center or produce blank cells. Inspect the count: a 4×4 split of a sheet that really held 15 frames leaves one empty cell, which is expected.

Width grows and height shrinks as columns increase, while the total pixel area stays fixed at 16384 pixels. A square 4 by 4 layout balances the two.

Common mistakes

The errors below account for most broken sheets. Each has a numeric check you can run.

Forgetting the gutter in the stride
If frames are 32 pixels with a 2 pixel gutter, the stride is 34, not 32. Frame 3 sits at 3 \cdot 34 = 102, not 96. Using the wrong stride shifts every frame after the first.
Off-by-one on the row count
17 frames in 4 columns needs \lceil 17/4 \rceil = 5 rows, not 4. Rounding down clips the last frame.
Positive instead of negative CSS offset
A positive background-position pushes the sheet down and right, revealing empty space. Use -64px -32px, not 64px 32px.
Mixing frame sizes on a uniform grid
A 48 pixel frame dropped into a 32 pixel grid overflows into its neighbor by 16 pixels. Keep every frame the same size, or pad each to the largest one.

Related tools

If you generate small graphics to pack, several tools on this site produce grid-friendly images. The Placeholder Image Generator makes fixed-size labeled boxes useful as stand-in frames. The QR Code Generator & Reader and Barcode Generator output crisp square or rectangular graphics you might tile onto a sheet. For stylized frames, the Dithering Tool and ASCII Art from Image transform a source picture before you pack it.

Frequently asked questions

How many columns should a sprite sheet have?

Choose columns to keep the sheet roughly square and within your hardware's maximum texture size. For 16 frames of 32 pixels, 4 columns gives a balanced 128×128. Powers of two (2, 4, 8, 16) are common because older GPUs required power-of-two texture dimensions.

Do sprite sheets reduce file size?

They reduce request count more than byte count. One 128×128 PNG of 16 icons is often smaller than 16 tiny PNGs because each file carries its own header of a few hundred bytes. The pixel data is nearly identical; the savings come from shared overhead and fewer round trips.

What is the gutter for?

The gutter is transparent padding between frames. It stops color from a neighboring cell bleeding across the edge when the sheet is scaled or filtered. A 1 to 2 pixel gutter is usually enough. Remember it adds (c-1) pixels of width and (r-1) of height.

Are my images uploaded anywhere?

No. Everything runs locally in your browser, and your images are never sent to any server. Packing, slicing, and the ZIP download all happen on your own machine.

Can I split a sheet if I do not know the grid?

You need at least the frame size or the row and column counts. If you can measure one frame in pixels and the sheet has no gutter, divide the sheet width by the frame width to get columns. A 256×64 sheet with 32 pixel frames has 256/32 = 8 columns and 64/32 = 2 rows.