Pixel art blurs because the default scaling filter — bilinear — blends neighboring pixels to smooth the upscale, which is exactly wrong for art made of deliberate hard edges. Switch to nearest-neighbor sampling and the edges stay sharp. Then scale only by whole-number multiples (2x, 3x, 4x) so every source pixel maps to an equal block of screen pixels. Filter plus integer scale equals pixel-perfect, everywhere from CSS to your game engine.
Same Art, Two Completely Different Results
You finish a 32×32 sprite. In your editor, zoomed to 800%, it is crisp — every pixel a clean square. You put it on your website at a larger size, or load it into a game, and it turns into a soft, smeared mess. The colors are right. The shape is right. But the sharpness is gone.
You did not draw it wrong. The display is scaling it wrong. And the fix is two settings that almost every tool ships with backwards for pixel art.
![]()
What Scaling Actually Does
When a 32×32 image is shown at 128×128, the computer has to invent the missing pixels — it has 1,024 source pixels and needs to fill 16,384 screen pixels. How it invents them is the whole game.
There are two common methods:
- Bilinear filtering. For each new pixel, it samples the four nearest source pixels and blends them by distance. This produces smooth gradients between colors — perfect for photographs, where smooth is realistic. On pixel art it smears every hard edge into a gradient. This is the default in browsers, engines, and image viewers.
- Nearest-neighbor. For each new pixel, it picks the single closest source pixel and copies it, no blending. Hard edges stay hard. Each source pixel becomes a clean block. This is what pixel art needs and almost never gets by default.
The entire "my pixel art is blurry" problem is this: you are getting bilinear when you want nearest-neighbor.
Why is the wrong one the default? Because most images on screen are photos, screenshots, and UI gradients, where bilinear genuinely looks better. Pixel art is the exception that wants the un-smoothed path, and you have to ask for it explicitly almost everywhere.
Fix Part One: Force Nearest-Neighbor
The setting has a different name in every context, but it is always the same switch.
CSS / web:
.pixel-art {
image-rendering: pixelated; /* Chrome, Edge, Safari */
image-rendering: crisp-edges; /* Firefox fallback */
}
Apply it to any <img>, <canvas>, or background that holds pixel art you are scaling up.
HTML canvas (drawImage):
ctx.imageSmoothingEnabled = false; // must be set after every context resize
Game engines: the per-engine names are — Godot: Texture Filter → Nearest; Unity: Filter Mode → Point; Phaser: pixelArt: true in the game config; Construct: Sampling → Point.
That one flip removes the blur. But it does not, on its own, make the result perfect. For that you need the second half.
Fix Part Two: Scale by Whole Numbers Only
Turn off filtering but scale a 32px sprite to 50px — a 1.5625x multiplier — and a new problem appears. Some source pixels now cover 2 screen pixels and their neighbors cover only 1, because you cannot split a pixel. The result is uneven pixels: a sprite where some columns are visibly fatter than others. Straight lines look lumpy. This is sometimes called pixel shimmer or jitter.
The fix is integer scaling: only ever scale by a whole-number multiple — 2x, 3x, 4x, 5x. At 3x, every single source pixel becomes an identical 3×3 block. Perfect uniformity, every time.
So a 32×32 sprite should be displayed at 64, 96, 128, 160 — never 100, never 150. Nearest-neighbor keeps edges hard; integer scaling keeps every pixel the same size. You need both.
The math: pick your target display size, divide by the source size, and round down to a whole number. Want a 32px sprite around 200px tall? 200 ÷ 32 = 6.25, so use 6x = 192px. Slightly smaller than your target, but pixel-perfect. Always round down, never to a fraction.
The Moving-Camera Catch
Static sprites are easy. The hard case is a scrolling game where the camera moves at fractional speeds. Even with nearest-neighbor and integer scaling, a camera positioned at x = 100.37 lands your sprites on fractional pixel boundaries, and they shimmer as they move — pixels appear to jiggle by a row.
Two fixes:
- Snap the camera to whole pixels. Round the camera position to an integer every frame before rendering. Cheap and effective.
- Render to a low-res buffer, then integer-scale the whole buffer. Draw the entire game at native pixel resolution (say 320×180) into an off-screen texture, then upscale that one texture by an integer factor to fill the window. Every sprite, the camera, and any sub-pixel motion all get resolved at native resolution first, so nothing can land off-grid. This is how most polished pixel-art games do it, and it is what Unity's Pixel Perfect Camera and Godot's viewport stretch mode automate for you.
When You _Cannot_ Scale by Integers
Sometimes the window size just is not a clean multiple of your game resolution — a 320×180 game in a 1366×768 laptop screen wants 4.27x. Your options, worst to best:
- Fractional scale with nearest-neighbor. Sharp but uneven pixels. Acceptable at high multipliers (the unevenness is one pixel out of five), bad at low ones.
- Integer scale plus letterboxing. Use the largest integer that fits (4x = 1280×720) and put black bars around the remainder. Perfect pixels, small borders. This is the standard choice.
- Integer-prescale, then a tiny bilinear finish. Upscale to the nearest integer with nearest-neighbor, then do the small remaining fractional step with bilinear. The blur is spread thin and the edges mostly survive. A reasonable compromise for fullscreen web.
For most projects, integer scale plus letterboxing is the right default. Players accept thin borders far more readily than they accept lumpy pixels.
Build sheets that stay crisp at every scale.
Lay out your frames at native resolution and export clean PNGs — then apply nearest-neighbor and integer scaling on your end for pixel-perfect results in any engine or browser.
The Two-Line Summary You Can Tape to Your Monitor
- Edges blurry? You have bilinear filtering. Switch to nearest-neighbor / point /
image-rendering: pixelated. - Pixels uneven or shimmering? You are scaling by a fraction. Scale by whole numbers only, snap the camera, and letterbox the remainder.
Get those two right and your pixel art looks exactly as sharp on a 4K monitor as it did at 800% in your editor — which is the entire point of working in pixels in the first place.