mrkeyoor.com_
Sat 08 Aug 21:59 UTC
npmUtilsupdated 08 Aug 2026

image-q

image-q is a TypeScript image-quantization library for reducing full RGBA pixel data to a smaller color palette. It supplies WuQuant, RGBQuant, NeuQuant, multiple color-distance formulas, nearest-color mapping, several error-diffusion dithering kernels, progress-aware async wrappers, and SSIM comparison. It works on pixel buffers and browser canvas data, not encoded PNG, JPEG, GIF, or WebP files.

Verdict

Choose image-q when the job is specifically palette construction and RGBA remapping, especially when algorithm choice matters. Do not install it expecting an image-file toolkit or background processing.

API stability4/5Version 4.0.0 exposes a small basic API plus namespaced advanced classes, and the README records the important method renames from the 2.x line. The current package has explicit import and require exports and bundled declarations. The last major primarily repaired bundle export shapes, but there is no published compatibility policy and the advanced class surface remains broader and easier to break than the four basic functions.
Docs3/5The README inventories supported inputs, palette algorithms, distance formulas, dithering kernels, builds, and breaking changes, while the linked generated API site exposes class signatures. Practical end-to-end examples are thin, the linked demo is explicitly labeled outdated, the TODO still asks for more examples, and users must infer the decode and encode boundary themselves.
Maintenance2/5The repository is not archived and npm does not deprecate version 4.0.0, but the latest release was published in January 2022 and the last repository push was in October 2023. GitHub reports 11 open issues and pull requests. The code is usable and finished-looking, yet stale generated documentation and @types/node 16.9.1 in runtime dependencies show that packaging upkeep is limited.
Ecosystem3/5The package recorded 3,454,193 downloads in the measured week and supports Node buffers, browser ImageData, canvas elements, ESM, CommonJS, and TypeScript. Its 167 GitHub stars and narrow integration surface are modest for that traffic. There are no built-in codecs or framework adapters, so real projects commonly pair it with canvas, pngjs, sharp, or browser APIs.

Use it if

  • You already have decoded RGBA pixels and need a 256-color or smaller palette in JavaScript
  • You want to compare WuQuant, RGBQuant, and NeuQuant without wiring separate implementations
  • You need alpha-aware quantization and a choice of nearest-color or error-diffusion output
  • You need the same deterministic quantization code in a browser and Node.js, with bundled TypeScript declarations
Skip it if

Setup reality

Install image-q and import its namespace; version 4.0.0 publishes both ESM and CommonJS entry points and bundled declarations, with no native compilation or peer dependency. The first surprise is that an image filename is not valid input. Decode the file elsewhere, then construct iq.utils.PointContainer from an ImageData, HTML canvas, HTML image element, RGBA Uint8Array, Uint32Array, or Node Buffer plus explicit width and height. After quantization, image-q gives you a PointContainer or palette, not a PNG or GIF, so another library must encode the returned bytes. The byte input must represent four RGBA bytes per pixel and match width times height; the Buffer conversion views four-byte words directly. The convenient defaults are WuQuant, 256 colors, BT.709 Euclidean distance with alpha, and Floyd-Steinberg dithering. Async methods improve event-loop responsiveness by scheduling generator steps, but they do not create worker threads, shorten total CPU work, provide cancellation, or set a memory ceiling. Progress callbacks receive numeric progress from the algorithms, yet there is no AbortSignal. Browser canvas reads can throw when the canvas is tainted by a cross-origin image. The published runtime dependency on @types/node can also introduce global Node types into installations that expected a browser-only utility.

Patterns

Reduce RGBA pixels to 64 colorsquantize-rgba-sync

import * as iq from 'image-q'

const input = iq.utils.PointContainer.fromUint8Array(rgba, width, height)
const palette = iq.buildPaletteSync([input], { colors: 64 })
const output = iq.applyPaletteSync(input, palette)
const quantizedRgba = output.toUint8Array()

rgba must contain four bytes per pixel. The result is raw RGBA data and still needs an image encoder.

Use the yielding API with progress callbacksquantize-with-progress

const palette = await iq.buildPalette([input], {
  colors: 128,
  onProgress: (value) => console.log('palette', value)
})

const output = await iq.applyPalette(input, palette, {
  onProgress: (value) => console.log('image', value)
})

These calls yield between generator steps but stay on the same JavaScript thread; use a worker for CPU isolation.

Quantize browser ImageDataread-image-data

const source = iq.utils.PointContainer.fromImageData(imageData)
const palette = iq.buildPaletteSync([source], { colors: 32 })
const result = iq.applyPaletteSync(source, palette)

const next = new ImageData(
  new Uint8ClampedArray(result.toUint8Array()),
  result.getWidth(),
  result.getHeight()
)

ImageData is decoded pixel data. Creating it does not encode a downloadable PNG or JPEG.

Build a point container from a canvasread-browser-canvas

const pixels = iq.utils.PointContainer.fromHTMLCanvasElement(canvas)
const palette = iq.buildPaletteSync([pixels], { colors: 16 })
const reduced = iq.applyPaletteSync(pixels, palette, {
  imageQuantization: 'nearest'
})

getImageData fails on a canvas tainted by an image loaded without suitable cross-origin permission.

Read pixels from a loaded image elementread-html-image

await image.decode()
const pixels = iq.utils.PointContainer.fromHTMLImageElement(image)
const palette = iq.buildPaletteSync([pixels], { colors: 48 })

Wait for the image to decode first. The helper creates an internal canvas and is subject to the same cross-origin rules.

Wrap a decoded Node.js RGBA bufferread-node-buffer

const pixels = iq.utils.PointContainer.fromBuffer(
  decodedRgbaBuffer,
  width,
  height
)
const palette = iq.buildPaletteSync([pixels], { colors: 256 })

Pass decoded RGBA bytes, not the bytes of a PNG or JPEG file. The buffer length must be divisible by four.

Build one palette from several imagesshare-palette

const palette = iq.buildPaletteSync(frames, {
  colors: 128,
  paletteQuantization: 'wuquant'
})

const reducedFrames = frames.map((frame) =>
  iq.applyPaletteSync(frame, palette)
)

Sampling all frames first produces a shared palette, useful for animation, but holds every supplied PointContainer in application memory.

Select RGBQuant instead of the defaultchoose-palette-algorithm

const palette = iq.buildPaletteSync([input], {
  colors: 32,
  paletteQuantization: 'rgbquant',
  colorDistanceFormula: 'euclidean-bt709-noalpha'
})

The defaults are wuquant and euclidean-bt709. Ignoring alpha is appropriate only when transparency should not affect matching.

Apply an Atkinson dithering kernelchoose-dithering-kernel

const output = iq.applyPaletteSync(input, palette, {
  colorDistanceFormula: 'euclidean-bt709',
  imageQuantization: 'atkinson'
})

The distance formula used while applying should normally match the one used to build the palette.

Map each pixel to its nearest palette colordisable-dithering

const flat = iq.applyPaletteSync(input, palette, {
  imageQuantization: 'nearest'
})

nearest avoids diffusion artifacts and is faster, but gradients usually band more visibly than with the default Floyd-Steinberg mode.

Sort and export palette colorsinspect-palette

palette.sort()
const paletteRgba = palette
  .getPointContainer()
  .toUint8Array()

sort mutates palette order and clears its nearest-color cache; export returns four RGBA bytes per palette entry.

Calculate SSIM after quantizationcompare-image-quality

const score = iq.quality.ssim(input, output)
console.log({ score })

Both PointContainers must have identical dimensions or ssim throws. A higher score indicates closer structural similarity.

Alternatives

PackageRegistryPick it when
sharpnpmNode services that need decoding, resizing, quantization, encoding, and high native-code throughput in one pipeline
jimpnpmPure JavaScript image loading and editing matter more than having a large choice of quantization algorithms
imageminnpmBuild pipelines that optimize encoded image files through format-specific plugins