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

wasm-imagequant

v0.1.4

Published

High-performance perceptual color quantization and PNG compressor in WebAssembly (Rust)

Readme

wasm-imagequant


🌟 Overview & Why wasm-imagequant?

For years, libimagequant (the underlying engine of pngquant) has been the gold standard for 8-bit palette color quantization. However, it imposes two severe constraints in modern web and commercial environments:

  1. GPL v3 Viral Licensing Barrier: Its licensing model forces any proprietary, commercial, or paid software using it to open-source their entire codebase, or purchase expensive commercial exceptions.
  2. WebAssembly Impedance Mismatch: Its architecture is heavily tied to POSIX threads, Rayon multi-threading, and non-trivial memory layouts, making it bloated, difficult to integrate, and sub-optimal in single-threaded WebAssembly sandboxes.

wasm-imagequant was built from the ground up to solve this. Released under the permissive MIT / Apache-2.0 dual licenses, it is completely commercial-friendly and unencumbered by copyleft restrictions. Powered by modern color science (Oklab Perceptual Color Space), it achieves superior color fidelity, smaller file sizes, predictable memory consumption, and blazing performance.


📊 Comprehensive Benchmarks (vs libimagequant)

1. Synthetic Challenge Benchmark (Gradient + Noise + Translucency Alpha, 800×600)

Run pnpm run bench (or cargo run --release --example compare) from root to reproduce.

| Metric | Industry Standard libimagequant | Engine wasm-imagequant | Difference & Advantage | | :--- | :--- | :--- | :--- | | Perceptual Error (Oklab MSE) | 0.000948 | 0.000563 | ★ 40.63% lower error, no color banding or hue shifts | | Quantization & Remap Time | 60.67 ms | 46.56 ms | ★ 1.30x faster, VP-Tree metric cache converges rapidly | | Peak Runtime Memory | Dynamic matrix + Rayon state | $O(\text{Width})$ Rolling Ring Buffer | ★ 99.9% lower memory, zero WASM 32-bit OOM hazard | | Binary Size & Dependencies | C/FFI or complex multi-thread runtime | Zero external C dependencies, WASM is 117 KB | ★ Ultra-lightweight, instant browser bootstrap | | PNG Packing Depth | Palette only (requires external encoder) | Built-in Adaptive 1/2/4/8-bit PNG Encoder | ★ 50%~80% extra reduction on simple icons and diagrams | | Commercial License | GPL v3 (forces open-source derivative) | Permissive (MIT / Apache-2.0) | ★ 100% free for proprietary and commercial software |

2. Real-World Ultra-HD Wallpaper Tests (1080P / 4K UHD / 12 Megapixels)

Run pnpm run bench:photos (or cargo run --release --example test_user_photos) to test on real-world photos.

| Test Image & Resolution | Raw Bitmap (RGBA) | Quantizer Engine | Pure Remap Time | Output PNG Size | Total Compression | Perceptual Error (Oklab MSE) | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | bingimg_20230504_UHD(1920×1080, 2.07 MP) | 7.91 MB | wasm-imagequantlibimagequant | 346.3 ms343.8 ms | 1,061 KB1,127 KB | 86.90%86.08% | 0.000086 (7.0% better)0.000092 | | bingimg_20230806_UHD(3840×2160 4K, 8.29 MP) | 31.64 MB | wasm-imagequantlibimagequant | 1,900.4 ms1,337.3 ms | 5,389 KB5,829 KB | 83.37%82.01% | 0.000123 (25.6% better)0.000166 | | Seaside-Nature-Scenery(3992×2992, 11.94 MP) | 45.56 MB | wasm-imagequantlibimagequant | 1,567.4 ms1,338.5 ms | 4,380 KB5,495 KB | 90.61%88.22% | 0.000044 (Better fidelity)0.000045 |

[!NOTE]

  • libimagequant automatically uses all available CPU cores on native Linux via Rayon; wasm-imagequant processes 12 Megapixels in ~1.5s on a single CPU core, achieving an algorithmic throughput of over 8 million pixels/sec.
  • On the 12-Megapixel wallpaper, wasm-imagequant generates a PNG that is 1.11 MB smaller (20.3% smaller file size) than libimagequant.

🔬 Architectural Innovations & Pipeline

flowchart TD
    classDef step fill:#1e293b,stroke:#38bdf8,stroke-width:2px,color:#f8fafc;
    classDef win fill:#064e3b,stroke:#34d399,stroke-width:2px,color:#f8fafc;

    A["Raw RGBA Pixels Buffer (Uncompressed)"]:::step --> B["1. 19-bit Spatial Histogram Downsampling<br/>(HistItem: Aggregates sub-pixel mean values)"]:::step
    B --> C["2. Variance-based Perceptual Median Cut<br/>(Oklab Space Median Cut)"]:::step
    C --> D["3. Lloyd-Voronoi Centroid Relaxation<br/>(With Dynamic Outlier Recycling)"]:::step
    D --> E["4. Vantage Point Tree Metric Acceleration<br/>(Bound Inheritance & Fast Pruning)"]:::step
    E --> F["5. Adaptive Serpentine Floyd-Steinberg<br/>(O(Width) Ring Buffer & Noise Suppression)"]:::step
    F --> G["6. Adaptive Bit-Depth Indexed-PNG Encoder<br/>(1/2/4/8-bit Packing + tRNS Trimming)"]:::step
    G --> H["Ultra-Compact High-Fidelity Indexed PNG"]:::win

1. End-to-End Oklab Perceptual Space & Static LUT (src/color.rs)

Replaces traditional non-linear sRGB and flawed CIELAB models with the modern Oklab perceptual space, completely avoiding purple shifts and perceived hue distortion. Features a precomputed static lookup table SRGB_TO_LINEAR_LUT that eliminates expensive powf(2.4) operations, converting coordinates via L1 cache hits.

2. 19-bit Perceptual Weighted Spatial Binning (src/hist.rs)

Applies a 19-bit spatial hash (5-bit R, 5-bit G, 5-bit B, 4-bit A) to compress tens of millions of raw pixels into thousands of feature bins while preserving the precise weighted color averages. This shrinks clustering iterations computation by over 99%.

3. Variance-based Split & Lloyd-Voronoi Centroid Relaxation (src/quant.rs)

The first stage splits boxes along the maximum variance dimension to build an initial palette; the second stage executes Lloyd-Voronoi K-Means relaxation to converge centroids to the optimal Voronoi centers. Includes an outlier recycling mechanism to reactivate unassigned clusters, ensuring all palette entries deliver maximum fidelity.

4. Vantage Point Tree with Distance Bound Inheritance (src/vptree.rs)

Solves the nearest-neighbor search bottleneck in non-Euclidean perceptual space using a Vantage Point Tree. By inheriting the previous pixel's distance as the initial search upper bound and leveraging triangle inequality pruning, spatial queries visit over 70% fewer nodes.

5. Adaptive Serpentine Floyd-Steinberg Dithering (src/dither.rs)

Employs alternating left-to-right and right-to-left serpentine scanning to eliminate directional worm artifacts. An adaptive damping factor automatically suppresses noise on flat color regions and clamps extreme error diffusion on high-contrast edges. The rolling buffer keeps memory consumption at a strict $O(\text{Width})$.

6. Compact Adaptive Bit-Depth PNG Encoder (src/png.rs)

Built-in pure Rust PNG encoder that eliminates external C dependencies. Automatically packs indices into 1-bit (≤2 colors), 2-bit (≤4 colors), 4-bit (≤16 colors), or 8-bit streams, and trims trailing opaque alpha in the tRNS chunk for optimal compression.


🚀 Quick Start

1. Installation

Install via your preferred package manager (pnpm recommended):

# via pnpm
pnpm add wasm-imagequant

# via npm
npm install wasm-imagequant

# via yarn
yarn add wasm-imagequant

2. Frontend & Bundler Integration (Vite, Webpack, Next.js, Nuxt, React, Vue)

wasm-imagequant is distributed with native ES Modules and TypeScript definitions.

Standard Browser Usage

import init, { compress_png } from 'wasm-imagequant';

// 1. Initialize WebAssembly runtime (fetches and compiles wasm_imagequant_bg.wasm)
await init();

// 2. Extract RGBA pixel buffer from HTML Canvas
const ctx = canvas.getContext('2d')!;
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);

// 3. Compress using wasm-imagequant
// Signature: compress_png(rgbaData, width, height, maxColors, ditherStrength)
const result = compress_png(
  imgData.data,  // Uint8Array: raw RGBA pixels (width * height * 4)
  canvas.width,  // Image width
  canvas.height, // Image height
  256,           // Max palette colors (2 - 256, default: 256)
  0.8            // Dither strength (0.0: off, 1.0: full diffusion, default: 0.8)
);

// 4. Access output PNG bytes and preview
const pngBytes: Uint8Array = result.data;        // Uint8Array: valid standard PNG binary stream
const paletteSize: number = result.palette_size; // Actual number of colors assigned
const blob = new Blob([pngBytes], { type: 'image/png' });
const previewUrl = URL.createObjectURL(blob);

Vite Explicit Static Asset Import

If your bundler requires explicit URL resolution for static .wasm files:

import init, { compress_png } from 'wasm-imagequant';
import wasmUrl from 'wasm-imagequant/wasm_imagequant_bg.wasm?url';

await init(wasmUrl);

Non-blocking Web Worker (Recommended for High Resolutions)

Execute image quantization off the main thread to guarantee 60 FPS UI responsiveness:

// worker.ts
import init, { compress_png } from 'wasm-imagequant';

let isReady = false;

self.onmessage = async (e: MessageEvent<{ rgba: Uint8Array; width: number; height: number; maxColors?: number; dither?: number }>) => {
  if (!isReady) {
    await init();
    isReady = true;
  }
  const { rgba, width, height, maxColors = 256, dither = 0.8 } = e.data;
  const result = compress_png(rgba, width, height, maxColors, dither);
  // Zero-copy transfer the result buffer back to the main thread
  self.postMessage(result.data, [result.data.buffer]);
};

3. Server-side & Node.js Integration (ESM)

In Node.js 18+ or serverless cloud functions:

import fs from 'node:fs/promises';
import init, { compress_png } from 'wasm-imagequant';

// Pass binary buffer directly to init() in Node.js
const wasmBuffer = await fs.readFile(
  new URL('node_modules/wasm-imagequant/wasm_imagequant_bg.wasm', import.meta.url)
);
await init(wasmBuffer);

// Compress raw RGBA buffer
const result = compress_png(rgbaBuffer, width, height, 256, 0.8);
await fs.writeFile('output.png', result.data);

4. Interactive Web Playground

Launch the local interactive visual playground with one command:

git clone https://github.com/reed-soul/wasm-imagequant.git
cd wasm-imagequant
pnpm install
pnpm dev

Open http://localhost:3000/playground/ in your browser to inspect side-by-side comparisons, adjust sliders for color count and dithering, and inspect real-time compression metrics.

4. Native Rust Usage (CLI / Microservice)

Add to your Cargo.toml:

[dependencies]
wasm-imagequant = { path = "../wasm-imagequant" } # Or from crates.io
use wasm_imagequant::quantize_and_compress;

fn main() -> Result<(), String> {
    let width = 1920;
    let height = 1080;
    let raw_rgba = vec![255u8; width * height * 4];

    let png_bytes = quantize_and_compress(
        &raw_rgba,
        width as u32,
        height as u32,
        256, // max_colors
        0.8  // dither
    )?;

    std::fs::write("output.png", png_bytes).map_err(|e| e.to_string())?;
    Ok(())
}

🎯 Best Practices & Tuning Guide

| Image Domain | Recommended maxColors | Recommended dither | Rationale & Expected Benefit | | :--- | :--- | :--- | :--- | | Natural Photos / Wallpapers / Gradients | 256 | 0.7 ~ 0.85 | Oklab color space eliminates banding; adaptive noise suppression avoids speckled grain on sky and skin. | | UI Dashboards / Architectural Diagrams / Screenshots | 32 ~ 64 | 0.0 ~ 0.2 | Disabling dithering keeps vector lines and text sharp, improving Deflate compression by 30% ~ 50%. | | Pre-indexed Graphics (Mode P, ≤200 colors) | 16 ~ 32 | 0.0 | With maxColors=256, existing colors are preserved 100% losslessly. Setting maxColors ≤ 16 activates 4-bit packing, halving the file size. |


📂 Project Structure

wasm-imagequant/
├── Cargo.toml                # Root Rust crate configuration (LTO + opt-level 3)
├── package.json              # Root pnpm scripts (dev, build, test, bench)
├── README.md                 # English documentation (default)
├── README.zh-CN.md           # Simplified Chinese documentation
├── index.html                # Root router redirecting to /playground/
│
├── src/                      # Core quantization engine source
│   ├── lib.rs                # Public native API & wasm-bindgen exports
│   ├── color.rs              # Oklab perceptual conversions & static LUT table
│   ├── hist.rs               # 19-bit perceptual weighted histogram downsampling
│   ├── quant.rs              # Variance split + Lloyd-Voronoi relaxation + outlier recycling
│   ├── vptree.rs             # Vantage Point Tree metric search with bound inheritance
│   ├── dither.rs             # Serpentine Floyd-Steinberg with O(W) rolling buffer
│   └── png.rs                # Adaptive 1/2/4/8-bit compact Indexed-PNG encoder
│
├── playground/               # Visual Web Testing Platform
│   └── index.html            # Dual-pane side-by-side comparison, parameter sliders & stats
│
├── tests/                    # Integration & unit test suite
│   └── test_all.rs           # Roundtrip color tests, VP-Tree precision, transparency & bit-depth
│
└── examples/                 # CLI & benchmarks
    ├── compare.rs            # Head-to-head benchmark against libimagequant
    └── test_user_photos.rs   # Comprehensive benchmark on real-world 1080P/4K/12MP photos

📄 License

This project is licensed under either of:

Commercial Protection: You may freely use, modify, embed, and distribute this library in closed-source proprietary software, commercial applications, and paid cloud services without any GPL viral copyleft obligations.