Skip to content

Sprite Palette Swap — Recolour Ramps and Team Colours

Every recolour here is a palette transform: pixels keep their palette index, so two frames that shared a colour still share it and the darkest shade stays the darkest.

A palette swap recolours a sprite by mapping each colour of one shading ramp onto the step in the same position of another ramp: the darkest red of a cloak becomes the darkest blue, the highlight becomes the highlight, and nothing else changes. In the Pixel Lab's Recolour stage you select the source colours, generate a target ramp from one base colour (or pick one), and apply it to every frame; team variants (red, blue, green, yellow or any #RRGGBB) export as one ZIP with a folder and a .gpl per variant.

Drop your sprite frames (PNG) — several frames share one palette
Open the Pixel Lab
Runs in your browser. Files are not uploaded.
Pixel Lab: CC0 sprite frames locked to one extracted palette, with the palette and the share of the pixels in each colour
The real Pixel Lab with a CC0 asset (OpenGameArt).
Outputs
  • PNG framesMeasured· Pillow
  • PaletteMeasured· re-import
  • pixel-lab.jsonUNVERIFIED

Map by position in the ramp, never by nearest colour

Replacing each colour by its nearest match in the new palette breaks shading: two neighbouring shades of red can both land on the same blue, and a light red can land on a darker blue than its shadow. The Lab sorts the selected source colours by Oklab lightness and maps them onto the target ramp by position, so the first becomes the darkest target, the last the lightest, and the order cannot invert.

A target ramp can be generated from a single base colour. It keeps the source ramp's lightness steps, takes the base colour's hue and chroma (so the base's own lightness is not used), boosts chroma in the mid-tones and rotates hue by up to 12° across the ramp, darker end one way and lighter end the other. The result is a starting point to adjust, not a finished art decision.

Every recolour is a palette transform: a pixel keeps its palette index and only the colour behind the index changes. Two frames that shared a shade still share it, which is why the swap cannot introduce flicker or stray colours between frames.

Ramp swap
Mapping selected source colours onto a target ramp by their position from dark to light.
Team variant
The same sprite recoloured with a different base colour for each team or player.
Hue window
A range of hues (centre ± degrees) whose colours are replaced while keeping their own lightness.
Status tint
A recipe that pulls every colour toward one hue (frozen, poison, burn, ghost, damage flash).

Example: a red cloak ramp turned into team blue

Computed with the Lab's own functions (L = Oklab lightness, h = hue):

source (red cloak)   #5a1e28 L0.33 h13 | #a02c3a L0.48 h18 | #dc5a50 L0.63 h27 | #f5a08c L0.79 h34
base: team blue      #3467d6 (L0.54 is ignored; hue 263°, chroma 0.18 are used)
generated ramp       #002d8c L0.35 | #004ed5 L0.48 | #4f7bff L0.62 | #99acff L0.76
hue along the ramp   262° → 262° → 267° → 273°
mapping by position  darkest→darkest … lightest→lightest; black and white untouched
3 colours onto a 5-step ramp: positions 0, 0.5, 1 → steps 0, 2, 4
test: 4 variants × 8 frames = 32 PNGs; 3 selected slots changed, 13 byte-identical

The generated lightness follows the source within 0.03; the lightest blue lands a little lower because sRGB cannot hold that blue at full chroma. Nudge it by hand if the highlight looks dull.

What it does

  • Ramp swap: selected source colours map onto a target ramp by position in lightness, never by nearest colour.
  • Auto ramp from one base colour, keeping the source ramp's lightness steps.
  • Team colour batch: N variants (red, blue, green … or any #RRGGBB) exported as one ZIP with a folder and a .gpl per variant.
  • Hue-range replace and status presets (frozen, poison, burn, ghost, damage flash), labelled as recipes.

How to do it

  1. Drop the frames.
  2. Pick the colours of the ramp to change on the palette.
  3. Choose a ramp, base colour, hue window or status preset and preview it.
  4. Apply, or export the team variants.

Check a swap

  • Before applying, compare the source and target swatches side by side: both rows should run dark to light.
  • Step through the frames: every frame should change only the selected colours, and outlines, skin and eyes stay as they were.
  • Open one variant's .gpl: the untouched slots have the original colours in the original order.
  • Put the variants next to each other at 1×: the teams should be distinguishable at game size, not only zoomed in.

Troubleshooting

SymptomLikely causeHow to checkFix
The shading came out inverted or flatA colour outside the ramp (outline, skin) was selected with itThe selected swatches do not form one dark-to-light sequenceSelect only the ramp's colours; the order you click in does not matter, lightness decides
Other parts changed colour tooThe cloak shares a palette entry with another part of the spriteSelect that swatch and compare its pixel count with the cloak alone: other parts use it tooGive that part its own colour first (for example in the Studio's Pixel workspace), then swap
The team colour is much darker or lighter than the base I pickedThe generated ramp keeps the source lightness steps and uses only the base's hue and chromaCompare the base swatch with the middle of the generated rampPick a target ramp by hand, or choose a base with the hue you want and adjust the steps
The hue window catches too much or nothingThe window (± degrees) is too wide or too narrow, and near-grey colours are ignoredThe preview marks the matching colours with a tickNarrow or widen the window; greys need the ramp swap instead

Other ways to do it

OptionWhen it is the better choice
Team-colour variants in the Studio's Pixel workspaceThe sprite has layers and tags: variants apply to every frame and layer and each becomes a new sprite; in an indexed sprite a single palette edit recolours every frame. See the palette editor.
One-colour palette swap with a toleranceYou only need to replace a single colour, with a shading offset: palette swap.
Aseprite: Edit › Replace ColorYou work in Aseprite and replace colours one at a time, with a tolerance, on the selection or on all cels.

Outputs and how each was checked

"Measured" means the downloaded file was opened again outside the page (Pillow, numpy or an independent parser) and the property was measured. Loading these files in a game engine was not tested.

TargetFilesCheck
PNG framesanim_N.png · 1×–8× nearestMeasured PillowEvery exported colour re-read from the PNGs lies inside the locked palette.
Palette.gpl · .hex (Lospec) · .jsonMeasured re-importEach format was exported and imported back through the UI: 16 → 16 colours.
pixel-lab.jsongeneric frames + paletteUNVERIFIEDGeneric JSON; not loaded in any engine.

Evidence

8 anti-aliased frames → one 16-colour palette with 0 exported colours outside it; an unchanged region identical in all 8 frames with dithering off and with Bayer 4×4; the anti-alias remover took 271 colours to 16 without moving a single silhouette pixel; integer scale, offset and 2.5× non-integer scaling detected exactly.

Limits

  • Variants export as PNG frames and .gpl files, not as .aseprite.
  • One palette for all loaded frames: a sheet with two unrelated characters must be split first.
  • Status presets are recipes, not authored art.
  • Frames larger than 4096 × 4096 are refused: every pass runs on the main thread, and there is no cancel button yet.
  • A sprite sheet is treated as one image; cut it into frames first (sprite sheet cutter).
  • With a dithering mode on, the cleanup counts include dither pixels; set dithering to None first.
  • The exported JSON is generic; importing it into Godot or Unity was not tested.

Versions and sources

Nerulio: docs/PIXEL-LAB.md (Chromium; 4 team variants × 8 frames exported as 32 PNGs in 4 folders, each changing exactly the 3 selected palette slots and leaving the other 13 byte-identical). The ramp above was computed with src/game/palette.js. Aseprite's Replace Color follows its documentation.

Official documentation used

Questions

Will shading stay consistent across frames?

Yes. Recolouring rewrites the palette, not pixels, so every frame keeps the same index for the same shade; in the test, 4 variants changed exactly the 3 selected slots and left the other 13 byte-identical.

Is there a one-colour swap with tolerance?

Yes, the separate palette swap tool does a single colour with a tolerance and a shading offset.

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