Home/Blog/Pixel Art Wasn't Made to Be Resized Like a Photo
TECH DEEP DIVE

Pixel Art Wasn't Made to Be Resized Like a Photo

Why bilinear and bicubic filtering destroy pixel art with blurry halos and color pollution. Master nearest-neighbor integer scaling (2x, 4x, 8x) for pristine, razor-sharp sprites.

Nearest-Neighbor • Integer Scaling • Canvas 2D • Bilinear vs Bicubic

There is a very easy way to make pixel art look bad: take a small sprite and resize it with the same settings you would use for a photograph.

The result usually looks fine for the first second. Then you notice the edges. A one-pixel outline is no longer one solid line. Small gaps start to close up. Colors that were never in the original palette appear around the edges, and the whole sprite gets that slightly soft look that is difficult to remove later.

Nothing went wrong with the PNG.

The problem happened when the image was resampled.

Pixel art is built around a fixed grid, so resizing it is less about reconstructing an image and more about deciding how that grid should be represented at another size. If the original is 32×32 and the target is 128×128, the cleanest possible answer is almost embarrassingly simple: every original pixel becomes a 4×4 block.

That gives us nearest-neighbor scaling.

It also explains why integer scaling shows up so often in emulators, sprite editors, and pixel-art tools. The method isn't trying to make the enlarged image look more photographic. It is trying to make sure the pixels that were already there stay exactly where they belong.


A pixel in a sprite is not quite the same thing as a pixel in a photograph

This is the part that tends to get lost when image scaling is explained purely as a sampling problem.

Take a tiny sprite and look at one of its diagonal edges. At the original resolution, the artist may have chosen the following arrangement deliberately:

██··
███·
·██·
··██

There isn't necessarily a smooth diagonal underneath those blocks waiting to be reconstructed. The staircase is the diagonal.

A normal photograph is different. An edge in the real scene can land between sensor samples, and the image stored by the camera is already a discrete representation of something continuous. When that image is enlarged, interpolation can estimate values between neighboring samples and produce a visually smoother result.

Pixel art often starts from the other direction. The artist has already decided where the edge crosses the grid.

That is why a resampling method that is perfectly reasonable for photographs can be the wrong tool here.

If the source pixel is dark gray and the pixel beside it is light gray, creating a medium gray between them may look like an improvement in a photograph. In a sprite, it may simply be a color that the artist never intended to use.

Continuous Photograph vs Discrete Pixel Art Resampling Comparison
Figure 1.1: Side-by-side comparison of resampling continuous photos vs. discrete pixel sprites. Photographs benefit from smooth bicubic interpolation, while pixel art outlines collapse into muddy blur.

Nearest-neighbor doesn't really “scale” the pixels

The name sounds more complicated than the operation.

Suppose the source is:

A B
C D

At 4×, nearest-neighbor produces:

AAAA BBBB
AAAA BBBB
AAAA BBBB
AAAA BBBB
CCCC DDDD
CCCC DDDD
CCCC DDDD
CCCC DDDD

The four source pixels have not been blended or reconstructed. They have just been given more room on the output grid.

At the implementation level, the mapping is essentially:

srcX = Math.floor(x / scale);
srcY = Math.floor(y / scale);
output[x, y] = input[srcX, srcY];

For scale = 4, output coordinates 0, 1, 2 and 3 all refer to source coordinate 0. Coordinates 4 through 7 refer to source coordinate 1.

That is why a four-times enlargement stays so predictable. There is a direct relationship between the two grids.

And this predictability is more useful than it might sound.

If a sprite has a one-pixel black outline, you know exactly what will happen to it. At 4× it becomes four pixels wide. A two-pixel gap becomes eight pixels. A single highlight remains a single source color, just occupying a larger block.

Nothing has to be guessed.


Where bilinear interpolation changes the picture

Now put the same source through bilinear interpolation.

Instead of asking “which source pixel belongs here?”, the algorithm asks something closer to “how much should the nearby source pixels contribute to this output position?”

Near an edge, that means the output can contain colors that weren't present in the source at all.

Imagine a black pixel next to a white one. Between them, an interpolating scaler can produce gray values. At a large enough scale, the edge becomes a transition rather than a hard boundary.

For a photograph, that's usually exactly what you want.

For pixel art, the problem isn't simply that the edge looks softer. The scaler has changed the information encoded by the edge. A two-color boundary has become a small gradient. A limited palette now contains additional colors. A carefully placed one-pixel feature may occupy several partially blended pixels.

Nearest-Neighbor vs Bilinear vs Bicubic Resampling Face-off
Figure 3.1: 8× magnification shootout. Nearest-neighbor preserves original palette integrity, while bilinear smears transitions and bicubic introduces noticeable ringing halos.

Once you look at a sprite this way, the reason for disabling interpolation becomes fairly obvious.

You aren't trying to make a low-resolution photograph look smoother.

You're trying to display the same drawing on a larger grid.


Bicubic isn't a better version of nearest-neighbor

It's tempting to treat image scalers as a quality ladder:

nearest-neighbor → bilinear → bicubic → better

That isn't really how they should be compared.

Bicubic can produce excellent results when enlarging photographs because it uses a larger neighborhood of samples and a more sophisticated interpolation function. The fact that it is more sophisticated doesn't make it more appropriate for a sprite.

The algorithm still has the same basic problem: it has no knowledge of which pixel boundaries are intentional.

A one-pixel eye and a noisy photographic edge look like samples to the resampler. It doesn't know that one should remain a hard block while the other might benefit from smoothing.

The “better” scaler is therefore often the one that makes fewer assumptions about the image.

For pixel art, nearest-neighbor makes almost no assumptions at all.


The scale factor is where things get interesting

Nearest-neighbor alone doesn't guarantee that the enlarged image will have the same visual rhythm as the source.

The clean case is an integer scale.

A 32×32 sprite can become 64×64, 96×96, 128×128, 160×160, 192×192, 256×256, and so on. Every source pixel can occupy the same number of output pixels in both directions.

At 4×:

1 source pixel = 4 × 4 output pixels

At 8×:

1 source pixel = 8 × 8 output pixels

There is no rounding involved in deciding where those blocks end.

A scale such as 2.5× is different. The output has 80 pixels for every 32 source pixels. Some source boundaries therefore have to land at different positions relative to the output grid. The resampling algorithm has to distribute the source pixels somehow, and depending on the implementation, that can make repeated patterns and one-pixel structures look uneven.

You can see this especially well with checkerboards and diagonal lines.

A flat region hides the problem. Repetition exposes it.

INTERACTIVE RESAMPLING LAB

Nearest-Neighbor vs. Bilinear Interpolation

Toggle interpolation mode and scaling factors to observe pixel grid stability and color palette pollution.

Unique Colors:0 (Pristine)
Integer 4× scale: Exact 1-to-16 square blocks. Zero blurred edges.

A checkerboard is a good test because it has nowhere to hide

Consider:

ABAB
BABA
ABAB
BABA

At 2×, the structure is perfectly regular:

AABB AABB
AABB AABB
BBAA BBAA
BBAA BBAA
AABB AABB
AABB AABB
BBAA BBAA
BBAA BBAA

Every original cell has exactly the same physical size.

Integer vs Fractional Scaling Checkerboard Diagnostic
Figure 6.1: Demonstrating harmonic shimmer. Left: Integer 8.0× maintains uniform 8×8 pixel cells. Right: Fractional 5.5× introduces irregular 5px/6px alternating columns, warping diagonal lines.

This is one reason checkerboards, one-pixel lines and small text are useful when testing an image scaler. They make inconsistencies much easier to see than a large area of flat color.

If you're building a pixel-art conversion tool, this kind of test image is worth keeping around. It can reveal a scaling bug immediately.


The browser can quietly undo all of this

There is another practical issue if the scaling happens in a browser.

Canvas has image smoothing, and a call such as:

ctx.drawImage(image, 0, 0, newWidth, newHeight);

doesn't by itself express the fact that the source is pixel art.

For a pixel-art resize, you generally want:

ctx.imageSmoothingEnabled = false;

before drawing the enlarged image.

Then an operation such as:

const scale = 4;
canvas.width = image.width * scale;
canvas.height = image.height * scale;
const ctx = canvas.getContext("2d");
ctx.imageSmoothingEnabled = false;
ctx.drawImage(
image,
0,
0,
canvas.width,
canvas.height
);

keeps the browser from applying the usual smoothing behavior.

This is a small line of code, but it belongs to the actual image-processing logic. If a tool promises pixel-perfect enlargement and forgets this setting, the problem isn't going to be fixed later by exporting the result as a PNG.


PNG is only where the pixels end up

PNG gets mentioned a lot in pixel-art workflows because it is lossless and works well with flat colors and hard edges.

But PNG doesn't determine how those pixels were created.

You can resize a PNG with bilinear interpolation and save another perfectly valid PNG. The file is still lossless. It just contains the wrong pixels for the job.

Likewise, nearest-neighbor can produce a clean result before the image is saved.

So there are really two separate questions:

How are the pixels transformed?

and

How are the resulting pixels stored?

Nearest-neighbor answers the first question. PNG is one good answer to the second.

Keeping those two things separate makes the whole pipeline easier to reason about.


The original resolution should usually remain the source of truth

Suppose the source sprite is 32×32 and the tool needs three exports:

  • 64×64
  • 128×128
  • 256×256

There is little reason to create the 256×256 version first and then resize that image down for the other outputs.

Keep the 32×32 image.

Generate each size from the original:

32×32
├── 2× → 64×64
├── 4× → 128×128
└── 8× → 256×256

That keeps the source grid intact and makes every export deterministic.

It also makes later changes much easier. If one pixel in the original sprite is corrected, every output size can simply be regenerated.

For a small graphics tool, this is a better model than treating the largest exported PNG as the “master” image.


Not every image should be scaled this way

Nearest-neighbor is not some universal high-quality resize mode.

If you're enlarging a photograph, a smooth illustration, or an image containing anti-aliased typography, interpolation is doing useful work. Removing it will often make those images look worse.

The useful question is what the pixels represent.

A photograph is sampled from a continuous scene.

A sprite may have been designed directly on the pixel grid.

Those two images can have the same dimensions and the same PNG extension while requiring completely different treatment.

That's why the scaling option should really be chosen based on the source image, not the file format.


The useful rule is surprisingly small

For a genuine pixel-art resize, the safest pipeline is not complicated:

keep the original grid → use nearest-neighbor → use an integer scale when possible → don't smooth the canvas → generate different sizes from the original.

There is no reconstruction step because there is nothing missing that needs to be reconstructed.

The 32×32 sprite already contains the decisions.

The 128×128 version should simply give those decisions more physical space.

And that is the part ordinary image scaling tends to forget: sometimes the pixels aren't approximations of the picture. They are the picture.

EXPORT HD PIXEL ART IN PIXKIT

Export Crisp Integer PNGs in PixKit Studio

Convert photos to sprites and instantly upscale to 2×, 4×, or 8× resolution using pure nearest-neighbor sampling. Zero blurry bilinear halos, zero color pollution.

Ready to Pixelate Your Own Images?

Launch PixKit Studio now to convert photos, game screenshots, and concept art into authentic retro pixel art. Free, instant, and runs 100% in your browser.

Topic Cluster & Next Reads
View All 10 Guides
← Back to All GuidesPixKit Retro DevLog • Published

Cookie & Privacy Notice

We use cookies to analyze web traffic and support Google AdSense advertising. Your uploaded images remain 100% private in your browser. Review our Privacy Policy.