Skip to content

Pixel Art Upscaler Without AI — Integer Nearest 1× to 8×

For store pages, trailers and social posts, pixel art has to be enlarged without blur. An integer nearest-neighbour scale turns each pixel into an exact N×N block; the output size is shown before you save.

To enlarge pixel art without blur, multiply it by a whole number with nearest-neighbour: at 4× a 32 × 32 sprite becomes 128 × 128 and every pixel an exact 4 × 4 block. Fractional factors (4.25×) make some pixels wider than others, and smooth or AI upscalers add blur or invented detail. Nerulio’s Pixel Lab scales frames 1×–8× nearest and first checks whether the image is already an upscale, so you enlarge the 1× original rather than an enlarged copy.

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

Whole factors keep the grid; everything else bends it

Nearest-neighbour copies each source pixel into a block and computes nothing new. With a whole factor k every block is k × k, so a one-pixel line stays one block thick, diagonals stay regular stairs and the colours stay exactly the palette.

With 4.25× a 32-pixel row becomes 136 pixels, and 136 screen pixels cannot be split into 32 equal blocks: nearest gives widths 5, 4, 4, 4, 5, 4, 4, 4 … so every fourth pixel is 25 % wider and a clean diagonal turns into stairs of uneven steps. Bilinear or bicubic hide the unevenness by blending neighbours, which is exactly the blur you wanted to avoid.

For a target that is not a multiple of the art — a 1920 × 1080 trailer from 400 × 225 art is 4.8× — scale by the largest whole factor (4× → 1600 × 900) and pad or frame the rest. And check the input first: a 3× screenshot scaled 4× again is 12×, with any unevenness it already had multiplied; the checker recovers the 1× source when the image is an exact integer upscale.

Nearest-neighbour
Every output pixel copies the closest source pixel; no blending.
Integer scale
2×, 3×, 4× …: every art pixel becomes an equal square block.
Logical size
The 1× size of the art: 32 × 32 for a sprite shown at 128 × 128.

Example: sizes and block widths

A 32 × 32 sprite and a 400 × 225 scene:

32 × 32   ×2 →  64 × 64    every pixel 2 × 2
32 × 32   ×4 → 128 × 128   every pixel 4 × 4
32 × 32   ×8 → 256 × 256   every pixel 8 × 8
32 × 32   ×4.25 → 136      widths 5,4,4,4,5,4,4,4 …  (uneven)

400 × 225 for a 1920 × 1080 trailer: 1920/400 = 4.8, 1080/225 = 4.8
  largest whole factor 4 → 1600 × 900, pad 160 px left/right, 90 px top/bottom

The pattern 5, 4, 4, 4 comes from nearest sampling: output pixel x shows source pixel ⌊x ÷ 4.25⌋, so source pixel 0 covers x = 0–4 (5 pixels) and source pixel 1 covers x = 5–8 (4 pixels).

What it does

  • Integer scales 1× to 8× with nearest-neighbour only: no filter, no generated detail.
  • The checker detects whether the image is already an upscale (exact scale and offset), so you do not scale a scaled image.
  • If an exact grid is found, recover the 1× source first and scale that.
  • Works per frame for animations; export PNG.

How to do it

  1. Drop the image or frames.
  2. Read the scale report; recover 1× if it is already scaled.
  3. Pick the integer scale.
  4. Export the PNG.

Check the result

  • The output size is the input size times the factor exactly (32 × 4 = 128).
  • Run the result through the checker: it should report an exact integer scale with offset 0, 0.
  • Look at it at 100 % where it will be shown (store page, video editor): soft edges there mean the platform rescaled it.

Troubleshooting

SymptomLikely causeHow to checkFix
The upscaled PNG looks soft on a web pageThe page displays it at a different size and the browser smooths itCompare the CSS size with the file sizeShow it at its own size, or set CSS image-rendering: pixelated (per MDN: nearest to the closest whole multiple, then smooth for the remainder)
Uneven pixels in a trailer or on a store pageThe editor or store resized it to a non-multiple (4.8×)Compare the file size with the output resolutionUpscale by the largest whole factor that fits and pad instead of stretching
The result is much bigger and blockier than expectedThe input was already an upscale (3× then 4× = 12×)The checker reports an exact integer scale on the inputRecover 1× first, then scale
Coloured noise around outlines after uploadingThe platform re-encoded the image as JPEGZoom into an edge of the uploaded copyUpload PNG where the platform keeps it

Other ways to do it

OptionWhen it is the better choice
Scale variants in Pack & Export (sprite sheet maker)You need an engine atlas at @2x or @4x next to the 1× one; those variants are nearest-neighbour only.
Aseprite: File › Export › Export As with the Resize fieldYou already work in Aseprite; its docs suggest scales such as 400 % for posting animations.
Let the engine scale the 1× artInside a game, keep 1× textures and scale the view by whole numbers (Godot, Unity); pre-scaled textures only cost memory.

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

  • Whole factors 1×–8× only; there is no fractional or smooth mode.
  • A sheet is scaled as one picture: 2 px of padding between frames becomes 8 px at 4×.
  • No detail is added — right for pixel art, wrong if you wanted a photo upscaler.
  • 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

From the Pixel Lab tests in Chromium: 1×, 2×, 3×, 4× and 8× nearest upscales were detected exactly with offset 0, 0; a 2.5× nearest resize read 2.500×; a 3× sprite was recovered to 16 × 16 pixel-identical. The CSS behaviour is as described by MDN; Aseprite’s export dialog as described in its docs.

Official documentation used

Questions

Why only whole numbers?

A non-integer scale makes some pixels wider than others. The checker measures that too: a 2.5× nearest resize reads 2.500× and is not called integer.

Does it add detail like AI upscalers?

No. Every output pixel is a copy of a source pixel.

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