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.

- 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/bottomThe 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
- Drop the image or frames.
- Read the scale report; recover 1× if it is already scaled.
- Pick the integer scale.
- 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
| Symptom | Likely cause | How to check | Fix |
|---|---|---|---|
| The upscaled PNG looks soft on a web page | The page displays it at a different size and the browser smooths it | Compare the CSS size with the file size | Show 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 page | The editor or store resized it to a non-multiple (4.8×) | Compare the file size with the output resolution | Upscale by the largest whole factor that fits and pad instead of stretching |
| The result is much bigger and blockier than expected | The input was already an upscale (3× then 4× = 12×) | The checker reports an exact integer scale on the input | Recover 1× first, then scale |
| Coloured noise around outlines after uploading | The platform re-encoded the image as JPEG | Zoom into an edge of the uploaded copy | Upload PNG where the platform keeps it |
Other ways to do it
| Option | When 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 field | You already work in Aseprite; its docs suggest scales such as 400 % for posting animations. |
| Let the engine scale the 1× art | Inside 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.
| Target | Files | Check |
|---|---|---|
| PNG frames | anim_N.png · 1×–8× nearest | Measured PillowEvery exported colour re-read from the PNGs lies inside the locked palette. |
| Palette | .gpl · .hex (Lospec) · .json | Measured re-importEach format was exported and imported back through the UI: 16 → 16 colours. |
| pixel-lab.json | generic frames + palette | UNVERIFIEDGeneric 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).