---
title: "An image pixelator in two drawImage calls"
description: "Drop a picture, pick a block size, export a pixel-art PNG. The whole effect is drawing the image tiny, then scaling it back up with image smoothing turned off."
date: "2026-05-26"
tags: ["canvas", "image", "tool", "frontend"]
---

I saw [@rauchg](https://x.com/rauchg/status/1876248159116197998) share a little
"Pixelate Me" app (built in v0, designed by Taras Donchenko) and wanted to
rebuild the core. It's a satisfying one, because the effect that looks like it
needs a shader is really just two `drawImage` calls. Drop something in:

## The whole trick

To pixelate, shrink then grow. Draw the image into a tiny canvas (say a
sixteenth of the size) with smoothing on, so each block averages to one color.
Then draw that tiny canvas back to full size with smoothing *off*, so every
sample becomes a hard square instead of a blurry gradient:

```ts
const sw = Math.round(w / block);   // 1/16th the width
const sh = Math.round(h / block);

const tmp = document.createElement("canvas");
tmp.width = sw; tmp.height = sh;
tmp.getContext("2d").drawImage(img, 0, 0, sw, sh);  // shrink (smooth)

ctx.imageSmoothingEnabled = false;                   // the whole trick
ctx.drawImage(tmp, 0, 0, sw, sh, 0, 0, w, h);        // blow it back up
```

That one flag, `imageSmoothingEnabled = false`, is the difference between a
blurry upscale and crisp pixel art. The block size is just the divisor: bigger
divisor, chunkier pixels.

## Keep it full resolution

It's tempting to do all this at display size, but then your export is basically
a screenshot. Run it at the image's native dimensions instead and the PNG you
download is sharp at full size. Transparency rides along for free, so a cut-out
subject stays cut out against whatever you drop it onto.

Recreated from [Pixelate Me](https://pixelate-me.vercel.app) by
[@rauchg](https://x.com/rauchg/status/1876248159116197998), designed by Taras
Donchenko. Grab the component on the [Image Pixelator](/components/pixelator)
page.
