Skip to content

Pixel Art Palette Extractor — .gpl, HEX and JSON

Get the real palette of a sprite — every colour with its pixel count and share — from one image or a whole animation, then keep it under a colour budget.

Extracting a palette lists the colours a sprite uses and how many pixels each covers, so you can spot accidental near-duplicates and keep a colour budget. Drop one image or all frames of an animation; the Palette stage builds one palette for all of them (16 colours by default, 2–256), shows each colour's exact pixel count and share, sorts it, and can merge the rarest colours into their nearest survivor to meet a target. Save it as GIMP .gpl, a HEX list or JSON; nothing is uploaded.

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

An exact colour list and an extracted palette are not the same thing

The Lab's extraction is a reduction. Pixels are first collected into buckets of 5 bits per channel (32 levels instead of 256), weighted by alpha, and the buckets are then split by median cut in Oklab. Two colours that differ by less than 8 levels in every channel land in the same bucket and come out as one averaged colour. That is what you want for a noisy or resized image, but not when you need the literal colours of hand-made pixel art.

For the literal list, use the Studio's Pixel workspace: Palette from the picture collects every exact colour (up to 255 plus the transparent entry), ordered by how often it is used. The Lab's counts, on the other hand, are exact for its palette: every pixel is assigned to its nearest palette colour in Oklab, with an exact-match shortcut, across every frame while the frames total under 4 megapixels.

A colour budget turns the counts into a decision. Set a target, and the rarest colours over the target are listed; merging moves each of them into its nearest surviving colour in Oklab, never into another colour that is also being removed, so the result really uses the target number of colours.

Colour budget
The maximum number of colours a sprite or a frame may use.
5-bit bucket
A group of colours whose red, green and blue agree in their top 5 bits (32 levels each).
Share
A colour's pixel count divided by all opaque pixels.
.gpl
GIMP's text palette: a "GIMP Palette" header, optional Name and Columns, then one "R G B name" line per colour.

Example: why two greens become one, and a budget from 16 to 8

Bucket arithmetic, then the budget test from the Lab's verification:

#3e8948 = (62,137,72)  → buckets 62>>3=7, 137>>3=17, 72>>3=9
#3f8a4a = (63,138,74)  → buckets 63>>3=7, 138>>3=17, 74>>3=9   same bucket
extracted: one colour, the pixel-weighted average of both
exact list (Studio, Palette from the picture): both colours, sorted by use
budget: 16 colours, target 8 → the 8 rarest are listed
merge: each goes to its nearest survivor in Oklab
export: the frames really use 8 colours, 0 outside the palette

If the two greens were drawn on purpose, count them in the Studio; if they came from resizing or compression, the merged colour is the one to keep.

What it does

  • Palette from all frames at once, with exact per-colour counts and percentages.
  • Sort by lightness, hue, saturation or frequency (greys grouped at the end of a hue sort).
  • Colour budget audit: target vs actual, the rarest colours listed, one-click merge into their nearest survivor.
  • Import and export GIMP .gpl, Lospec-style HEX lists and JSON; the format is read from the content, not the extension.

How to do it

  1. Drop the image or frames.
  2. Set the number of colours and sort order.
  3. Optional: set a colour budget and merge the rarest colours.
  4. Export the palette (.gpl, .hex or .json) or the recoloured frames.

Palette files the Lab writes

FileWhat it is for
palette.gplGIMP Palette text: Name:, Columns:, then R G B and the hex code as the colour name. Opens in GIMP and Aseprite.
palette.hexOne #rrggbb per line, the plain list Lospec also offers.
palette.jsonname, colors as #rrggbb, rgb as [r,g,b] triples, count and source.

Check the extracted palette

  • Compare the colour count with the Studio's exact list: a big difference means the image was noisy or resized.
  • Sort by frequency: colours with a share near 0 % are candidates for the budget merge or stray-pixel cleanup.
  • Import the exported file again (paste or file): it must give back the same number of colours.
  • After a merge, export the frames and count their colours in another tool: the count must equal the target.

Troubleshooting

SymptomLikely causeHow to checkFix
Two colours that should stay separate were mergedThey fall into the same 5-bit bucket (less than 8 levels apart per channel)Compare with Palette from the picture in the Studio's Pixel workspaceUse the Studio's exact list, or make the colours more distinct
Asked for 32 colours, got fewerThe image has fewer distinct buckets than requested; a box cannot be split below one colourThe palette shows fewer swatches than the countNothing is wrong: the image uses that many colours
A palette file will not importThe Lab reads GIMP .gpl, HEX lists and JSON only; a JASC .pal, a Paint.net .txt with AARRGGBB values, or a .gpl with Aseprite's "Channels: RGBA" line is rejectedThe error names the line it could not readDownload the .gpl or .hex version, or load the file in the Studio's Pixel workspace, which reads more formats
Counts cover only the visible frameAll frames together exceed 4 megapixelsThe panel says so next to the countsLoad fewer frames for exact totals

Other ways to do it

OptionWhen it is the better choice
Studio Pixel workspace: Palette from the pictureYou need the literal colours of hand-drawn art, or palette formats beyond .gpl/.hex/.json (.pal, .ase, .act). See the palette editor.
Starting from a published paletteYou want the sprite to follow a known palette instead of its own: Lospec palettes.
Checking scale and blur before extractingThe image may be an upscale: counts from a blurred upscale are meaningless until it is snapped to 1×; see the pixel-perfect checker.

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

  • Extraction merges colours closer than 8 levels per channel; it is not an exact inventory.
  • Reads and writes .gpl, HEX and JSON only.
  • Exact counts stop at 4 megapixels of total frame area.
  • 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; the budget merge from 16 to 8 colours was re-counted in the exported PNGs; .gpl, .hex and JSON round trips 16 → 16 colours). The bucket arithmetic follows src/pixel-engine.js. File layouts follow the GIMP specification and Lospec's own downloads.

Official documentation used

Questions

Are the pixel counts exact?

Yes, for animations up to 4 megapixels of total frame area; above that the counts cover the visible frame and the panel says so.

Can it read a .txt that holds a GIMP palette?

Yes. The format is detected from the content, so a .txt with a GIMP header is read as .gpl.

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