Pixel art blurs because the default scaling filter, bilinear, blends neighboring pixels together. Switch to nearest-neighbor and the edges stay hard. Then scale only by whole numbers (2x, 3x, 4x) so every source pixel becomes an equal-sized block. You need both, in CSS and in game engines alike.
Sharp in the Editor, Soft in the Game
You finish a 32x32 sprite. At 800% in your art program every pixel is a clean square. Then you put it on a web page at a larger size, or load it into a game, and it comes out soft and smeared. Colors and shape are fine. The edges are gone.
The drawing is not the problem. The display is scaling it with the wrong filter, and most tools default to that filter.
What Scaling Does
Show a 32x32 image at 128x128 and the computer has to make up the missing pixels: 1,024 source pixels have to fill 16,384 screen pixels. The filter decides how.
Bilinear filtering looks at the four nearest source pixels for each new pixel and blends them by distance. On photos that gives smooth, natural transitions. On pixel art it turns every hard edge into a gradient. Browsers, engines, and image viewers use it by default.
Nearest-neighbor copies the single closest source pixel, with no blending. Each source pixel becomes a solid block and edges stay hard. This is what pixel art needs.
So "my pixel art is blurry" almost always means you are getting bilinear when you want nearest-neighbor.
Why is bilinear the default? Most images on screen are photos, screenshots, and UI with gradients, and bilinear looks better on all of them. Pixel art is the exception, so you have to ask for nearest-neighbor explicitly.
Turn On Nearest-Neighbor
Every platform has the switch under a different name.
CSS:
.pixel-art {
image-rendering: pixelated;
}
Put it on any <img>, <canvas>, or element with a background image that shows scaled-up pixel art.
HTML canvas (drawImage):
ctx.imageSmoothingEnabled = false; // resizing the canvas resets this, so set it again after
Game engines: Godot calls it Texture Filter -> Nearest, Unity Filter Mode -> Point, Phaser pixelArt: true in the game config, and Construct Sampling -> Point. The engine import guide shows where each setting lives.
That gets rid of the blur. It does not finish the job.
Scale by Whole Numbers Only
With filtering off, scale a 32px sprite to 50px (1.5625x) and a new problem shows up. A pixel cannot be split, so some source pixels cover two screen pixels and their neighbors cover one. Some columns come out visibly fatter than others and straight lines look lumpy.
The fix is integer scaling: only scale by whole numbers. At 3x every source pixel becomes an identical 3x3 block.
A 32x32 sprite should be shown at 64, 96, 128, or 160. Never 100 or 150.
The math: divide your target size by the source size and round down. For a 32px sprite around 200px tall: 200 / 32 = 6.25, so use 6x, which is 192px. A little smaller than you wanted, but every pixel is even.
Seeing It in the Editor
The editor's Pixel toggle in View Options switches nearest-neighbor rendering on and off. Zoom to 300% and turn it off: the sprites go soft right away. That is bilinear filtering, the same blur your engine applies until you change its setting. The editor zoom moves in 25% steps, so stick to 200%, 300%, 400% and so on when you are judging sharpness.
A sharp preview here does not make it sharp in your game. Filtering is an engine setting, not something stored in the PNG. Load a perfect sheet into a project with linear filtering and it will still blur.
Moving Cameras
Static sprites are the easy case. In a scrolling game the camera often sits at fractional positions. Even with nearest-neighbor and integer scaling, a camera at x = 100.37 puts sprites between pixel boundaries, and they shimmer as they move.
There are two fixes:
- Snap the camera to whole pixels. Round its position every frame before rendering. Cheap, and often enough.
- Render to a low-res buffer, then scale the buffer. Draw the whole game at native resolution (say 320x180) into an off-screen texture, then scale that one texture up by a whole number to fill the window. Every position gets resolved at native resolution first, so nothing can land off the grid. Unity's Pixel Perfect Camera and Godot's viewport stretch mode set this up for you.
When the Window Is Not a Clean Multiple
A 320x180 game on a 1366x768 laptop screen would need 4.27x. The options, worst to best:
- Fractional scale with nearest-neighbor. Sharp but uneven. Tolerable at high multipliers, where one pixel in five is off, and bad at low ones.
- Integer scale plus letterboxing. Use the largest whole number that fits (4x = 1280x720) and fill the rest with black bars. Perfect pixels, thin borders. This is the usual choice.
- Integer prescale, then a small bilinear step. Scale up by the largest whole number with nearest-neighbor, then cover the remaining fraction with bilinear. The blur is spread over a tiny step and edges mostly survive. A fair compromise for fullscreen web games.
Option 2 is the safe default. Thin borders bother players less than lumpy pixels.
Export the sheet at native resolution.
Lay out your frames at 1x and export a clean PNG, then let nearest-neighbor and integer scaling handle the enlarging in your engine or browser.
Two Symptoms, Two Fixes
- Blurry edges? You have bilinear filtering. Switch to nearest-neighbor, point filtering, or
image-rendering: pixelated. - Uneven or shimmering pixels? You are scaling by a fraction. Use whole numbers, snap the camera, and letterbox the rest.
Fix both and the sprite looks as sharp on a 4K monitor as it did at 800% in your art program.
Try it in the editor
The editor is free and needs no account. Open it in another tab and work through the steps above with your own frames.