DEV Community

IC ice
IC ice

Posted on

I Built a Browser-Only Pixel Art Converter — Here's How It Works Under the Hood

A while back, I got tired of explaining to people how to convert images to pixel art using Photoshop, Aseprite, or some obscure command-line tool. The friction was real — install this, export that, tweak the palette manually.

So I built PixelCrafts — a fully client-side pixel art converter that runs entirely in the browser using the Canvas API. No backend, no file uploads, no account required.

Here's how it works and why I made certain technical decisions.


The Core: Canvas API + Pixel Downsampling

The fundamental operation is simple: take an image, downsample it to a low resolution, then scale it back up with imageSmoothingEnabled = false to get that hard-edged pixel look.

const offscreen = document.createElement('canvas');
offscreen.width = Math.floor(img.width / pixelSize);
offscreen.height = Math.floor(img.height / pixelSize);

const ctx = offscreen.getContext('2d');
ctx.imageSmoothingEnabled = false;
ctx.drawImage(img, 0, 0, offscreen.width, offscreen.height);

// Scale back up — pixels stay sharp
outputCtx.imageSmoothingEnabled = false;
outputCtx.drawImage(offscreen, 0, 0, outputCanvas.width, outputCanvas.height);
Enter fullscreen mode Exit fullscreen mode

The pixelSize slider controls the block size — higher values = chunkier pixels, lower = finer detail. Everything is computed in-memory, frame by frame as you drag the slider.


Color Palette Quantization

This was the trickiest part. Retro hardware had strict color limits (NES: 54 colors, Game Boy: 4 shades of green), so I needed palette quantization — mapping every pixel to the nearest color in a fixed set.

The approach I use is nearest-neighbor in RGB space:

function nearestColor(r, g, b, palette) {
  let minDist = Infinity;
  let nearest = palette[0];
  for (const [pr, pg, pb] of palette) {
    const dist = (r - pr) ** 2 + (g - pg) ** 2 + (b - pb) ** 2;
    if (dist < minDist) {
      minDist = dist;
      nearest = [pr, pg, pb];
    }
  }
  return nearest;
}
Enter fullscreen mode Exit fullscreen mode

For best results with photos, I also implemented an adaptive palette generator that clusters the image's dominant colors using a median-cut algorithm — so if you don't want to pick a retro preset, you can auto-extract a palette directly from the image.

You can also import palettes from Lospec or PixilArt by slug or URL — the palette data is fetched client-side and parsed into HEX arrays.


Style Presets

PixelCrafts ships with 10 retro presets:

Preset Palette Vibe
NES 54-color hardware palette Classic platformer
Game Boy 4-tone greenscale Handheld nostalgia
PICO-8 16-color Fantasy console
Vaporwave Purples, pinks, teals Aesthetic
Matrix Green-on-black Terminal
Glitch Art Distorted + chromatic Experimental
Thermal Heat map tones Infrared look
Old Movie Sepia + grain Silent film era
Anime High contrast + clean Cel-shaded
SNES Extended 256-color 16-bit era

Each preset sets the palette, pixel size default, and enables relevant post-processing effects automatically.


Post-Processing Effects (All on Canvas)

Beyond pixelation, I layered several real-time visual effects — all implemented as canvas compositing passes:

  • Scanlines — overlays alternating dark horizontal lines to mimic CRT rows
  • Bloom — a Gaussian blur blended additively over bright areas
  • CRT mask — RGB sub-pixel dot pattern overlay
  • Chromatic aberration — offsets the R and B channels by a few pixels
  • Noise / grain — per-pixel random alpha overlay
  • Outline — edge detection pass using a simple Sobel-like kernel
  • Hue shift / temperature / tint — pixel-level HSL adjustments via getImageData

All effects run after the quantization step, so the pixelated palette stays intact while the CRT effects sit on top.


Privacy by Default: Zero Uploads

Everything runs locally. When you drag an image into PixelCrafts:

  1. The browser reads it via FileReader or createObjectURL
  2. It's drawn onto an offscreen <canvas>
  3. All transformations happen in-memory
  4. You download the result via canvas.toBlob()URL.createObjectURL()

Your image data never leaves your device. There's no server, no S3 bucket, no logs. This was a deliberate choice — many of the use cases (profile photos, game assets in development, client work) involve images people reasonably don't want uploaded to a stranger's server.


Minecraft Mode

One of the more fun features: a dedicated Minecraft Pixel Art mode. Instead of color palette quantization with arbitrary HEX values, it maps each pixel to the nearest Minecraft block by color (using a pre-built block → average color lookup table). The output includes:

  • A pixelated block-by-block preview
  • A full material list with block counts
  • Scale controls for the final structure size

Useful for Minecraft map art builders who want to turn a photo into a buildable plan.


Batch Conversion

For indie devs converting sprite sheets or icon sets, there's also a batch mode — upload multiple images, apply the same style settings to all of them, and download individually or as a zip.


Try It

PixelCrafts is free, no account needed, and works on mobile too.

If you're building something similar or have thoughts on the palette quantization approach, drop a comment — I'm always looking to improve the algorithm, especially for edge cases like skin tones and gradients.

Feedback and feature requests welcome: support@pixelcrafts.art

Top comments (0)