Skip to content

Downscale Pixel Art to 1× — Undo an Upscale or Resize

A sprite saved at ×4, a screenshot, a store image resized by a fractional factor: each pixel is now a block of many, sometimes smeared by smoothing. The Cleanup panel measures the pixel size from the picture itself, shows the grid it found with how sure it is, and rebuilds the 1× sprite as a new sprite without touching the original.

Drop a sprite, frames, a GIF or an .aseprite file to paint on — or an upscaled picture to clean up
Runs in your browser. Files are not uploaded.
Studio Pixel workspace, Cleanup panel: a CC0 sprite resized ×4.25 with bilinear filtering, measured as a smooth resize with its grid drawn, and the 64×48 result previewed before Apply
The real Studio with a CC0 asset (OpenGameArt).
Exports to
  • .asepriteVerified· Aseprite 1.3.18
  • Palette filesMeasured· read back
  • Engine bundlesVerified· Godot 4.7.2 · Unity 6000.5
  • Frame PNG · .nerulioPlain files

What it does

  • Measure changes nothing: it reports the verdict (for example "Upscaled ×7" or "Smoothly resized ≈ ×4.25"), the pixel size with a confidence of sure, likely or unsure, the input and result sizes, the colour noise and a detected background, and draws the grid on the canvas.
  • Exact integer blocks are found with off-grid crops. Fractional nearest-neighbour scales, where cells are 2 or 3 px wide in turn, are found from the spacing of edges, and smooth resizes are undone with a least-squares inverse of the bilinear filter.
  • Each cell becomes one pixel by its most common colour or its Oklab medoid, never by averaging the edge, and near-identical colours are merged in Oklab, so outlines stay crisp.
  • Preview shows before and after at whole-number zoom, with a side-by-side compare. Apply adds the 1× sprite as a new sprite in one undo step, keeping frames, durations, pivots, boxes and tags.
  • All work runs in a worker off the main thread, and the same input and options always give the same bytes. A 384×576 ×4 upscale took 1.4 s end to end, a 363×544 bilinear resize 4.7 s.

How to do it

  1. Drop the upscaled PNG on this page; it opens in the Pixel workspace as one picture.
  2. Open the Cleanup panel, choose the scope (this frame, the tag or all frames) and press Measure; check the verdict and the grid drawn on the canvas.
  3. If the art has a solid background that belongs to the picture, set Background to keep; otherwise the border colour is made transparent.
  4. Press Preview to compare before and after, then Apply as new sprite and export the 1× result.

How close the 1× result is, by kind of upscale

Exact pixels against the known original: defaults / background kept. 69 CC0 cases, docs/STUDIO-PIXEL.md §5.

UpscaleSize found exactlyExact pixels
Nearest, whole number (×2–×8)100 %92.2 % / 97.4 %
Nearest, fractional (×2.5, ×3.3, ×4.7)100 %88.6 % / 97.0 %
Smooth (bilinear, bicubic, Lanczos)93 %70.5 % / 75.9 %
Blurred after upscaling67 %53.8 % / 66.4 %
JPEG after upscaling80 %4.5 % / 12.1 % (84.0 % within ΔE 0.02)

Engine exports and how each was checked

"Verified" means the exported files were loaded by the real engine or tool on real CC0 assets and the result was compared with the source. Anything that was not loaded is labelled as such.

TargetFilesCheck
.asepritelayers · blend · opacity · locked · indexed palette · frames · tagsVerified Aseprite 1.3.18The real Aseprite command line reopened the file as INDEXED with 2 layers, Multiply at opacity 160 and the locked layer not editable; the frames are pixel-identical.
Palette files.gpl · .pal · .hex · .ase · .act · .jsonMeasured read backEach format was written and read back identical; Aseprite's own .gpl, .pal, .hex and .act and a real Adobe .ase (16 colours) were read.
Engine bundlesGodot 4 · Unity 6 · Phaser 3/4 · PixiJS 8 … through Pack & ExportVerified Godot 4.7.2 · Unity 6000.5The sprite you drew is packed in Pack & Export; those engine files were loaded in each engine on real CC0 assets (47 of 49 runs pass; the 2 failures are a Phaser 3.90 bug the Phaser 3 preset avoids).
Frame PNG · .nerulioPNG-8 with the palette when an indexed frame fits it · project filePlain filesPlain files for any tool; no engine run is claimed for them.

Evidence

Cleanup benchmark, 69 CC0 cases with a known 1× original: the exact size found for 81 % (±1 px: 90 %) and 74.0 % of pixels exact with the background kept; the best competitor found 30 % of sizes (perfectPixel) and the best exact-pixel score was 44.6 % (unfake.js). An Aseprite-made ×4 nearest upscale came back 100 % exact and a ×3.78 bilinear resize 99.7 %, both with the size found automatically. A 1 px outline and a drop shadow matched Aseprite pixel for pixel (1846 and 705 px). .aseprite files written here reopen in Aseprite 1.3.18 as indexed, with their layers, blend mode and lock.

Limits

  • Not in the Pixel workspace (Aseprite has them): layer groups, linked cels, tilemap layers, reference layers, RotSprite and free rotation or scaling of a selection (only flips and 90°), gradient, spray, contour and text tools, custom brushes (square or round, 1–16 px), tiled drawing, Lua scripting and a command line.
  • The symmetry axis is typed, not dragged on the canvas. On touch there is no right button, so the background colour needs the swap button or X.
  • Cleanup: JPEG input comes back close (84 % of pixels within ΔE 0.02 with the background kept) but rarely exact (12 %); simulated generated art gets the exact size 20 % of the time (±1 px: 80 %); none of 7 real generated images was snapped automatically — you type the pixel size the panel suggests.
  • Indexed conversion picks the nearest colour in Oklab, not Aseprite's RGB distance: 68 % of the test sprite's pixels got a different (perceptually closer) pick. The Lospec search is the only network request and asks first.

Questions

What if the measured pixel size is wrong?

The verdict names its confidence, and a weak grid is drawn dashed in amber rather than applied as sure. Type the pixel size yourself in the Cleanup panel; nothing is changed until you press Apply, and Apply makes a new sprite.

Why is my result one pixel smaller or larger than expected?

When the picture was cropped a few pixels off the grid, the border cells are partial. In the benchmark the exact size was found for 81 % of the cases and the size was within 1 px for 90 %.

Does it work on JPEG screenshots?

Partly. JPEG noise means colours rarely come back bit-exact (12 % of pixels with the background kept), but 84 % land within ΔE 0.02 of the true colour, which is not visible at 1×. Use PNG when you have it.

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