npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fast-pixelizer

v1.2.0

Published

Fast, zero-dependency image pixelation. Works in browser and Node.js.

Readme

fast-pixelizer

Fast, zero-dependency image pixelation library. Works in browser and Node.js.

npm license bundle size


Snap Mode — Turn Fake Pixel Art into Real Pixel Art

Most pixel art you find online is broken — scaled up with blurry interpolation, anti-aliased edges, and misaligned grids. snap() automatically detects the original pixel grid and rebuilds it with perfectly uniform cells.

As of 1.2.0, repeated snap() runs stay stable on already-clean images and square canvases no longer drift into mismatched X/Y grids.

Recent regression examples:

| Input | Previous | 1.2.0 | | ---------------------- | ------------------ | --------- | | 1.gemini.png | 201x201 | 200x200 | | 2.well-converted.png | 65x69 on re-snap | 201x201 | | 3.gpt.png | 148x150 | 148x148 | | 4.gpt.png | 98x179 | 97x97 |

| Before (blurry, misaligned) | After (clean, uniform) | After + Grid overlay | | :-------------------------------------------: | :-----------------------------------------: | :--------------------------------------------------: | | before | after | grid |

How it works:

  1. K-means++ color quantization — reduces noise to make grid edges detectable
  2. Edge profile analysis — scans horizontal & vertical color boundaries
  3. Periodicity detection — recovers the repeating cell size even when visible boundaries are sparse
  4. Lattice regularization — rebuilds a globally uniform grid instead of letting local cut drift accumulate
  5. Majority-vote resampling — picks the dominant color per cell
  6. Uniform re-rendering — every cell gets the exact same pixel size

No manual resolution input needed. The grid is auto-detected.

Input quality note

snap() works best when the source image really came from a low-resolution square grid that was later scaled up.

ChatGPT-generated "pixel art" often bakes the inconsistency into the source image itself: some cells are already wider, taller, softer, or slightly off-axis before snap() ever sees them. In those cases snap() can regularize the output and force it back onto a square lattice, but it cannot perfectly recover information that was never on a clean grid to begin with, so quality is not guaranteed.

If you are generating new source images specifically for snap(), prefer Nano Banana or any workflow that preserves a true square low-res lattice from the start.

import { snap } from 'fast-pixelizer'

const result = snap(imageData)
// → { data, width, height, detectedResolution, colCuts, rowCuts }

Pixelate Mode — Generate Pixel Art from Any Image

| | Original | clean | detail | | :-------: | :------------------------------: | :------------------------------------------: | :--------------------------------------------: | | 32×32 | original | clean-32 | detail-32 | | 64×64 | original | clean-64 | detail-64 |

clean — picks the most frequent color in each cell. Sharp, graphic pixel art look.

detail — averages all colors in each cell. Smoother gradients, more texture.

import { pixelate } from 'fast-pixelizer'

const result = pixelate(imageData, { resolution: 32 })

Install

npm install fast-pixelizer

Usage

The input accepts a browser ImageData, a node-canvas image data object, or any plain { data: Uint8ClampedArray, width: number, height: number }.

Browser

import { pixelate, snap } from 'fast-pixelizer'

const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
ctx.drawImage(myImage, 0, 0)

const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)

// Generate pixel art
const result = pixelate(imageData, { resolution: 32 })

// Or repair existing pixel art
const repaired = snap(imageData)

// Draw back
const out = new ImageData(result.data, result.width, result.height)
ctx.putImageData(out, 0, 0)

Node.js (with sharp)

import sharp from 'sharp'
import { pixelate, snap } from 'fast-pixelizer'

const { data, info } = await sharp('./photo.png')
  .ensureAlpha()
  .raw()
  .toBuffer({ resolveWithObject: true })

const input = {
  data: new Uint8ClampedArray(data.buffer),
  width: info.width,
  height: info.height,
}

// Generate pixel art
const result = pixelate(input, { resolution: 32 })

// Or repair existing pixel art
const repaired = snap(input, { colorVariety: 64 })

await sharp(Buffer.from(result.data), {
  raw: { width: result.width, height: result.height, channels: 4 },
})
  .png()
  .toFile('./output.png')

API

snap(input, options?): SnapResult

Detects the pixel grid in existing pixel art and re-snaps it to a clean, uniform grid.

options: SnapOptions

| Option | Type | Default | Description | | -------------- | ------------------------- | ------------ | ------------------------------------------------------------------------ | | colorVariety | number | 32 | K-means color count. Higher = more detail, slower detection. | | output | 'original' \| 'resized' | 'original' | 'original' = uniform grid at ~original size. 'resized' = grid-sized. |

SnapResult

interface SnapResult {
  data: Uint8ClampedArray
  width: number
  height: number
  detectedResolution: number // auto-detected grid size
  colCuts: number[] // column boundaries (for grid overlay)
  rowCuts: number[] // row boundaries (for grid overlay)
}

pixelate(input, options): PixelateResult

input: ImageLike

interface ImageLike {
  data: Uint8ClampedArray
  width: number
  height: number
}

Compatible with the browser's built-in ImageData, node-canvas, and raw pixel buffers.

options: PixelateOptions

| Option | Type | Default | Description | | ------------ | ------------------------- | ------------ | ------------------------------------------------------------------------------------------- | | resolution | number | required | Grid size. 32 means a 32×32 cell grid. Clamped to image dimensions automatically. | | mode | 'clean' \| 'detail' | 'clean' | 'clean' = most-frequent color per cell. 'detail' = average color per cell. | | output | 'original' \| 'resized' | 'original' | 'original' = same dimensions as input. 'resized' = output is resolution × resolution. |

PixelateResult

interface PixelateResult {
  data: Uint8ClampedArray
  width: number
  height: number
}

Examples

// Snap: auto-detect grid and repair
snap(img)
snap(img, { colorVariety: 64, output: 'resized' })

// Pixelate: generate pixel art
pixelate(img, { resolution: 32 })
pixelate(img, { resolution: 64, mode: 'detail', output: 'resized' })

Try it locally

Clone the repo and run the library against the sample image to see the output yourself:

git clone https://github.com/handsupmin/fast-pixelizer.git
cd fast-pixelizer
npm install
npm run examples

Output images will be written to examples/. Replace docs/original.png with any image to try your own.


Performance

| Function | Resolution | Image size | Time | | ---------- | ---------- | ---------- | ------ | | pixelate | 32 | 512×512 | ~1ms | | pixelate | 128 | 512×512 | ~3ms | | pixelate | 256 | 1024×1024 | ~12ms | | snap | auto | 512×512 | ~50ms | | snap | auto | 1024×1024 | ~150ms |

  • pixelate: pre-allocated Uint16Array(32768) bucket table — no Map, no per-call heap allocations.
  • snap: K-means++ quantization + periodicity-guided grid recovery. Heavier than pixelate but still fast enough for real-time use.
  • Cell boundaries use Math.round to eliminate pixel gaps and overlaps between adjacent cells.
  • Both functions iterate in row-major order for CPU cache locality.
  • Zero runtime dependencies.

Web Worker (browser)

For large images, run inside a Worker to keep the main thread unblocked:

// pixelate.worker.ts
import { pixelate, snap } from 'fast-pixelizer'

self.onmessage = (e) => {
  const { input, options, mode } = e.data
  const result = mode === 'snap' ? snap(input, options) : pixelate(input, options)
  self.postMessage(result, [result.data.buffer]) // transfer buffer, no copy
}
// main thread
const worker = new Worker(new URL('./pixelate.worker.ts', import.meta.url), { type: 'module' })
worker.postMessage({ input, options, mode: 'snap' }, [input.data.buffer])
worker.onmessage = (e) => console.log(e.data) // SnapResult or PixelateResult

Contributing

Contributions are welcome! See CONTRIBUTING.md.


License

MIT