mrkeyoor.com_
Wed 23 Sept 00:34 UTC
npmUtilsupdated 22 Sept 2026

image-q review

image-q 4.0.0 reduces decoded RGBA pixels to a smaller palette in JavaScript. Our browser build was 50.9 KB minified and 13.6 KB gzipped. The package includes WuQuant, RGBQuant, two NeuQuant variants, several color-distance formulas, nearest-color mapping, error-diffusion kernels, yielding async wrappers, and an SSIM comparison function. It accepts pixel containers such as `ImageData`, canvas data, typed arrays, and Node buffers. It does not decode or encode PNG, JPEG, GIF, or WebP files.

Verdict

image-q 4.0.0 installed in 0.7 seconds and its browser build measured 50.9 KB minified and 13.6 KB gzipped in our sandbox. Choose it for algorithm-level control over decoded RGBA quantization; choose an image codec or full processor when the input and output are files.

We installed it

Lab card: what happened when we installed image-qScreenshot of image-q documentation
Install✓ · 0.7s2 packages on disk · 4 MB
ImportESM import works · require() works · CommonJS package with exports map
Browser13.6 KBgzipped (50.9 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does image-q install cleanly?

Yes. In a fresh container with an empty cache, npm install image-q finished in 0.7s, leaving 2 packages and 4 MB on disk. npm audit reported no known vulnerabilities.

How much does image-q add to a browser bundle?

13.6 KB gzipped (50.9 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does image-q work with both ESM and CommonJS?

Yes. Both import 'image-q' and require('image-q') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does image-q include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

image-q or sharp: which should you use?

sharp: Use it in Node when decoding, resizing, palette reduction, and encoding should happen in one native pipeline. image-q 4.0.0 installed in 0.7 seconds and its browser build measured 50.9 KB minified and 13.6 KB gzipped in our sandbox.

When should you not use image-q?

The input is an image filename or compressed file buffer: image-q only accepts decoded pixel data and returns decoded pixel data

API stability4/5Version 4.0.0 has four basic sync and promise functions around stable `PointContainer` and `Palette` values, plus namespaced classes for direct algorithm use. Its exports map supports CommonJS and ESM, and declarations cover the current surface. The README records older 2.x renames and says 4.0 repaired exported bundle types. There is no stated compatibility policy, so advanced class users carry more upgrade risk than callers using the basic functions.
Docs3/5The README identifies accepted pixel sources, output array forms, palette algorithms, distance functions, error-diffusion kernels, module builds, imports, breaking changes, and the slow CIEDE2000 option. It points to generated API pages for signatures. The demo is labeled outdated, the TODO still requests more examples, and there is no concise file-to-pixels-to-file walkthrough or operational guidance for workers, cancellation, canvas security, and memory use.
Maintenance2/5Version 4.0.0 was released on 2022-01-08 and GitHub shows the last push on 2023-10-17. The repository is not archived, while 11 issues and pull requests remain open. The code and types are usable, yet the outdated demo and an old Node declaration package in runtime dependencies show limited packaging attention. A new project should expect to own compatibility fixes if browser or TypeScript behavior shifts.
Ecosystem3/5The npm endpoint recorded 3,682,920 downloads during the measured week. image-q accepts browser `ImageData`, canvases, typed arrays, Node buffers, CommonJS, ESM, and TypeScript, which makes it easy to place between many decoders and encoders. The repository has 167 stars and no built-in codecs or framework adapters. Most integrations are hand-built pixel pipelines with canvas, pngjs, GIF tooling, or another image library.

Use it if

  • Decoded RGBA data needs a palette of 256 colors or fewer in Node or a browser
  • You need to compare WuQuant, RGBQuant, and NeuQuant under the same API
  • Alpha-aware matching and selectable dithering kernels matter for the output
  • The same typed JavaScript quantizer must run on both browser pixels and Node buffers
Skip it if

Setup reality

Our fresh install of image-q 4.0.0 completed in 0.7 seconds. It left 2 packages and 4 MB on disk, with one direct dependency, no peers, bundled TypeScript types, and 0 known audit vulnerabilities. The MIT tarball is 1,372 KB unpacked.

The package is CommonJS with an exports map, and both require() and ESM import worked in our Node 22 sandbox. esbuild produced a browser bundle of 50.9 KB minified and 13.6 KB gzipped. There are no credentials, native builds, or config files. Its runtime dependency is @types/node, even though those definitions are compile-time material and can add Node globals to a browser-oriented TypeScript project.

Decode images elsewhere. Construct a PointContainer from RGBA ImageData, canvas pixels, a Uint8Array, Uint32Array, or Node Buffer plus width and height. Four bytes represent each pixel, so the byte length and dimensions must agree. Quantization returns a palette and another pixel container, not a downloadable image. A separate codec must turn toUint8Array() into PNG, GIF, or another format. Cross-origin images can taint a canvas and make its pixel read throw.

buildPalette() and applyPalette() yield between generator steps through a scheduler, which can let timers run but does not move calculation to another core. There is no AbortSignal or built-in memory limit. Use a Web Worker or worker thread when CPU isolation matters. Defaults choose WuQuant, 256 colors, BT.709 Euclidean distance with alpha, and Floyd-Steinberg dithering. Keep the same distance formula for palette building and application unless the mismatch is deliberate.

Patterns

Reduce decoded pixels to 64 colors quantize-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` needs four bytes per pixel. The returned array is still raw RGBA and requires a separate encoder.

Yield between quantization steps report-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 promises yield on the same JavaScript thread. Put the work in a worker when another CPU must handle it.

Remap browser ImageData quantize-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` contains decoded pixels. Constructing it does not create PNG or JPEG bytes.

Quantize a browser canvas read-canvas-pixels

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

A canvas tainted by an image without suitable CORS permission will reject the underlying pixel read.

Wrap a decoded Node buffer read-node-rgba

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

Pass RGBA pixel bytes, not compressed PNG or JPEG file bytes. Buffer length must match the dimensions.

Build one palette for every frame share-animation-palette

const palette = iq.buildPaletteSync(frames, {
  colors: 128,
  paletteQuantization: 'wuquant',
})
const reduced = frames.map((frame) =>
  iq.applyPaletteSync(frame, palette)
)

A shared palette can reduce color flicker across frames, while retaining all supplied containers increases memory use.

Select RGBQuant and ignore alpha choose-rgbquant

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

Ignoring alpha is appropriate only when transparency should have no effect on color matching.

Apply Atkinson error diffusion choose-dithering

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

Use the same distance formula during palette creation and pixel mapping unless you have tested a deliberate difference.

Map straight to the nearest color disable-dithering

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

Nearest mapping avoids diffusion artifacts and work, though smooth gradients can show stronger bands.

Read sorted palette bytes export-palette

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

`sort()` changes palette order and clears its nearest-color cache. Export uses four RGBA bytes for each entry.

Compare the remapped image measure-ssim

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

Both point containers must have identical dimensions. The function throws when their widths or heights differ.

Alternatives

PackageRegistryPick it when
sharpnpmUse it in Node when decoding, resizing, palette reduction, and encoding should happen in one native pipeline
pngquant-binnpmUse it when the job is specifically command-line PNG quantization with the pngquant binary
color-thief-nodenpmUse it when you only need a dominant color or small palette from an image rather than remapped pixels

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.