How to separate sprites from AI sprite sheets
An image model drew 495 game icons as 4×4 sprite sheets with soft shadows. A grid cut broke them. Cutting by the sheet's own alpha, and asking for wider gaps, did not.
Contents
Every tile in ASMR Logo Craft Block shows a small picture: an apple, a wrench, a cowboy hat. There are 495 of them, and I had them all redrawn by an image model in a new style, each with its own soft shadow falling to the lower right. One image took about two minutes, so 495 separate jobs meant over sixteen hours of generation. Sixteen icons to a sheet made it 31 jobs.
That moves the work to the other end. Thirty-one square PNGs, 1254×1254 with real alpha, have to become 495 separate pictures, and the shadows are the part that has to survive. This post is how to separate sprites from sprite sheets an image model drew, as I ended up doing it in two games: the 495 tile pictures, and later two 3×3 icon sheets for Echo Bat's menus.
Why the grid cut fails
The obvious cut is a grid: split the 1254 pixels into four columns and four rows and save each cell. It assumes every icon stays inside its cell. My prompt asked for exactly that — "Keep every subject and its complete shadow inside its own cell" — and the model did not always listen. On the first five sheets the slicer reported fourteen pairs of icons whose pixels touched: jeans and dress, sneaker and boot, burger and pizza, a zebra against a backpack. Where two icons touch, a straight line between cells cuts through one of them.
Echo Bat's first cutter cut each grid cell and trimmed it, and on the device the gift icon's purple bottom edge showed up as stray marks on the arrow icon in the cell below. Crystals were clipped by the cell line. A neighbour's chest lid ended up on the secure shield. The model draws a picture of a grid, which is not the same as drawing inside one.
Cut by shape, not by grid cell
What worked in both games is the same idea. Find each object by its pixels, give every other pixel to the object it belongs to, and use the grid only to say which object is which. In the tile-picture slicer that comes down to:
- find the solid bodies: connected pixels above a high alpha threshold
- assign each body to the cell holding its centre
- hand every other visible pixel (shadow, fade, loose sparkle) to the nearest body by a cost that knows where the light comes from
- crop each object's pixels, exactly as drawn, and fit them into the tile canvas
Nothing is keyed or despilled. The sheet's alpha is the mask. Each step above went wrong at least once first, though, and the ways it went wrong are the useful part.
The specks you cannot see
The first version grew each object outward through any pixel with alpha above zero. On the pilot sheet, 13 of the 16 icons came back flagged as "its shadow runs past its cell".
A histogram of an empty-looking patch at the top of one cell explained it: 10,946 pixels at
alpha 0 and 1,054 at alpha 1–2, and the ones I sampled were all rgba(0,0,0,1). The empty space
of these sheets is sprinkled with invisible specks, close to one pixel in ten, and they connect
everything to everything. An object growing through "any alpha" walked across the gutter into
its neighbour.
The fix is a floor. A pixel counts as visible from alpha 3. An object grows through visible pixels, then takes only the alpha 1–2 fade that touches its own rim, at most 6 pixels deep, so the soft edge stays on the shadow and the specks in the gutters stay behind.
The same specks raised a false alarm. The slicer refuses a sheet that was not drawn on transparency, and sheets 2 and 4 failed that check: "not drawn on transparency, redraw the sheet". They were fine. Both had over a third of their pixels at exactly zero, well above the 25% the check wanted, so what tripped it was a corner pixel holding a speck rather than a true zero. Once "clear" meant "below alpha 3" both sheets sliced. The slicer's advice had been to draw them both again.
Pick the body threshold from the art
To find the objects, I first used alpha 24 as the cut-off for a "solid" pixel. After 13 sheets the slicer reported three empty cells: cowboy hat, blackberry, flamingo. All three were plainly on the sheet. An empty cell, in this design, means the object's solid pixels were counted into another cell's component. At alpha 24 a dark shadow counts as solid too, so an object and the shadow touching it can merge with a neighbour into one component whose centre lies elsewhere. I did not map the components to confirm that, but raising the threshold brought all three back.
So I sampled a row of alpha values running through a shadow between two hats:
253 252 252 252 252 251 251 252 252 251 250 246 216 48 27 18 13 7 4 3 2 80 252 …
209 200 192 185 181 173 161 150 138 121 95 77 65 52 37 22 12 6 4 2 1 1 2 …
Objects sit at 246–253. Contact shadows peak around 210. The threshold went to 240, between the two, and every cell had its object again. The number belongs to this model and this prompt; on other art I would measure it the same way before trusting it.
Shadows belong to the object above-left
That leaves the pixels between bodies: the shadows. Plain nearest-body distance gets them wrong where two objects stand close, because the shadow under an icon's lower right is often nearer the next icon's body than its own.
The prompt fixes the light at the upper left, so every shadow falls down and to the right of its object. The slicer bakes that in as a path cost, run with Dial's algorithm since the costs are small integers:
// Through an opaque body every step is cheapest. Through a shadow,
// stepping down or right (with the light) is cheap, up or left dear.
// Crossing clear space is dearest of all.
const STEP_BODY = 1;
const STEP_FORWARD = 2; // right, down
const STEP_BACK = 5; // left, up
const STEP_CLEAR = 4; // multiplier through pixels below alpha 3
const REACH = 0.5; // in cells: past this, a pixel is nobody's
const a = data[q * 4 + 3];
const nd = d + (a >= BODY_ALPHA ? STEP_BODY
: a < FADE_FLOOR ? step * STEP_CLEAR
: step);
A shadow pixel goes to whichever body reaches it most cheaply, which favours the body above and to the left of it over one that is merely closer. Each icon is then cropped around every pixel it owns, down to the faintest, so no shadow is ever cut off at a box edge.
The slicer also counts how many solid pixels (alpha 64 and up) of two different objects touch. At 10 or more, the line between them is a guess, and the icon goes on a redraw list. Those icons were drawn again alone, one subject per image, and the single picture replaced the cut. After all 31 sheets the list held 16. A visual review added more, and 23 were drawn alone in the end.
A body in two pieces
One icon still came out wrong, after everything above passed. The marionette lost the right end of its wooden cross bar.
The model's drawing was fine; my cut lost the piece. The cross bar connects to the puppet only through thin strings, which are below the body threshold, so at alpha 240 it is a separate body. The slicer seeded each cell from its largest body only, and the egg in the cell above reached the loose bar more cheaply than the puppet did.
The fix: any body at least 10% the size of a cell's largest body is also a seed for that cell. Small fragments are still handed out by distance. I re-cut all 495 and compared every file with the previous cut. Three changed: the marionette, a kite and a tea bag, and the kite and tea bag looked the same as before side by side.
Redrawing the marionette alone was not an option. The image model's safety filter refused a marionette drawn on its own under three different wordings, though it had drawn one on a sheet without complaint. The fixed cut from the sheet is the one in the game.
Ask the model for wide gaps
Echo Bat's icons are a different style: chunky painted pieces with a cyan rim light, nine to a sheet. I rewrote its cutter on the same idea (shape labelling with a numpy flood fill, since scipy was not installed), and it reported three joined pairs: medallion and gear, chest and secure shield, video and flame.
It was worse than three pairs. At an alpha threshold of 12, five icons on the second sheet (the no-ads shield, the secure shield and three coin piles) were one connected shape of 559,225 pixels. Even at alpha 240, three of them were still one solid piece. I got the cutter to part them by shrinking the solid area until it fell apart (15 pixels, in that case), and the result was usable. But it was a cutter making up a boundary the art did not have.
So instead of a better cutter, I asked for a better sheet. Both sheets were sent back with the picked version attached and this added to the prompt:
Change only their size and spacing, so the game can cut each icon out on its own.
Each icon sits centred in its own cell and fits inside the middle 70% of it:
an empty transparent margin of at least 15% of the cell on every side.
Keep every icon's light INSIDE its dark outline.
Every part of an icon touches the rest of it: no loose sparkles, coins or
crystals floating apart from the icon.
Both redraws came back with the same designs, and both cut on the first try with no joins and nothing touching the sheet edge. The bell's sound arcs are separate shapes and went to the bell, which is what the shape cut is for. The 18 icons in the game come from those two sheets.
I have not tested whether the same margins would have saved the 4×4 sheets. Those carry long cast shadows that are supposed to reach toward the next cell. What I know is that the tile-picture prompt asked for every shadow inside its cell in general terms and was not always followed, and the Echo Bat prompt gave a number and was.
Look at every one anyway
The checks catch joins, empty cells and edge contact. They do not catch a picture of the wrong thing. I put all 495 on contact sheets, 64 to a page, on the real tile colour, and went through them twice. That found a playing card drawn as a plain heart, plus a candy cane whose shadow ended in a straight vertical edge and a balloon with a hard patch at its lower right, none of which any check had flagged. Reviewing Echo Bat's proposals turned up a coin whose bat emblem looked close to the Batman logo, which stays off a store listing however cleanly it cuts.
What I would do from the start
- Prompt for spacing with numbers: a margin per side as a share of the cell, no loose parts, no glow outside the outline.
- Check the sheet is really transparent, counting near-zero alpha as clear, before cutting anything.
- Measure the body and shadow alpha on the actual art and set the body threshold between them.
- Cut by shape. Give shadows to the object the light says they belong to, and keep the faint fade at the rim.
- Report joins and edge contact, keep a path to redraw a single icon alone, and look at every result on the colour it will sit on.
The shadows cost something to keep. The pictures are WebP at quality 88 with alphaQuality: 100,
so the alpha plane is stored losslessly, and the 495 went from 4.9 MB to 5.6 MB, up 14%. I
had set myself a 30% ceiling for that, and the shadows were the part I was not willing to
lose.