Toolkitly

How to resize pixel art without blurring it

You scale a 32×32 sprite up to 256×256 and it comes back as a smudge. Nothing is broken — your image editor did exactly what it was designed to do. Here is what it did, why it is wrong for pixel art, and the two settings that fix it.

1. Why your editor blurs it

Resizing an image means inventing pixels that were not there. A resampling filter decides what colour each new pixel gets, and the default in Photoshop, GIMP, Preview, Paint and every CMS on earth is bicubic or bilinear: look at the neighbouring source pixels, average them, weight by distance.

For a photograph that is the right answer. The photo is a sampling of a continuous scene, so interpolating between samples reconstructs something close to what the sensor would have recorded at the new resolution.

Pixel art is the opposite. There is no continuous underlying image being sampled — the pixels are the artwork, each one placed on purpose, usually from a palette of a few dozen colours. Averaging two neighbouring pixels produces a colour that is not in the palette, and a crisp one-pixel outline becomes a three-pixel gradient. Do it at 8× and every edge in the sprite turns into a soft ramp.

2. Nearest neighbour: copy, never average

Nearest-neighbour sampling asks a simpler question: for this output pixel, which single source pixel is closest? Copy that one. No averaging, no new colours, no soft edges.

Scaling 4× means every source pixel becomes a 4×4 block of identical pixels. The palette is unchanged, the outlines land exactly where the artist put them, and the result is indistinguishable from having drawn the art at that size with a chunkier brush — which is precisely what you want.

In practice the setting is called Nearest Neighbor (hard edges) in Photoshop, None in GIMP's interpolation dropdown, and Nearest neighbour in most online tools. The Pixel Art Resizer uses it by default and only offers smoothing as an explicit opt-out.

3. Scale by a whole number

Nearest neighbour alone is not quite enough. Consider 1.5×: a source pixel cannot become one and a half output pixels, so the filter alternates — this one gets a 2×2 block, the next gets 1×1, and so on across the sprite.

Nothing is blurred, and yet it looks wrong. Lines that were one pixel thick are now one pixel thick in places and two in others; a circle develops flat spots. The art reads as if it were drawn on uneven graph paper. The same thing happens at 2.5×, 3.7×, or any target size that is not a whole multiple of the source.

So: 2×, 3×, 4×, 8×. Which one matters far less than it being whole. 2× and 4× are the common picks because they also land on power-of-two texture sizes, which some GPUs and older mobile targets still care about.

If you genuinely need an in-between size — fitting a sprite into a fixed UI slot, for instance — scale to the nearest whole factor and centre the result in the slot, or add transparent padding. An uneven scale is not a compromise; it is a defect that shows up in every frame of the animation.

4. Downscaling, and the one case where it is lossless

Scaling down is not symmetrical with scaling up. Going from 64×64 to 32×32 means throwing away three quarters of the pixels, and no amount of scaling back up recovers them. If the art needs to exist at 32×32, the honest answer is usually to redraw it — automatic downscaling of pixel art rarely survives contact with a one-pixel detail.

There is one exception, and it comes up constantly with downloaded assets: the file is 512×512, but the art inside it is 64×64 that somebody blew up 8× before saving. Every visible "pixel" is really an 8×8 block. Dividing that back down by 8 loses nothing at all — it just undoes the upscale.

Spotting it is mechanical. In a clean upscale, every run of identical pixels along a row or a column has a length that is a multiple of the factor, so the greatest common divisor of all those run lengths is the factor. One stray anti-aliased pixel drops the divisor to 1 — which is the correct answer, because the image is then not a clean upscale and dividing it would destroy real detail.

When you do divide down, take the most common colour in each block rather than one sampled pixel. If the image ever passed through a smoothing filter, sampling one pixel might land on an interpolated edge; a majority vote across the block cannot.

5. The export is only half the job

A crisp PNG can still arrive blurry, because the engine applies its own texture filter on import. This catches people out constantly: the file on disk is perfect, the sprite in the game is soft.

Godot: select the texture, open the Import dock, set Filter to Nearest, then click Reimport. For a whole project, set the default texture filter to nearest in Project Settings rather than fixing textures one at a time.

Unity: Texture Type Sprite (2D and UI), Filter Mode Point (no filter), Compression None. Set Pixels Per Unit to your art's pixel size so sprites land on whole world units.

Phaser and the web: pass pixelArt: true in the game config. For sprites shown as plain <img> elements, add image-rendering: pixelated in CSS — without it the browser will smooth the image the moment it is displayed at anything but 100%.

And always export PNG. JPEG compression works by discarding high-frequency detail, which is another way of saying "hard edges" — it puts a halo around every outline and shifts colours off the palette.

Common problems

Edges are soft even though I picked nearest neighbour

Something downstream is filtering. Check the engine's import settings, and for web output check the CSS — a scaled <img> smooths by default.

Some pixels are wider than others

The scale is not a whole number. Round the target size to the nearest whole multiple of the source dimensions.

Colours changed slightly after resizing

The filter was still interpolating, or the file was saved as JPEG. Nearest neighbour plus PNG output keeps the palette byte-identical.

The transparent background turned white

The output format has no alpha channel. Export PNG or WebP, not JPEG.

The file is huge for a small sprite

The art is probably already upscaled. Restore it to native resolution and let the engine scale it at runtime.

Resize a sprite now