ROADMAP.md
Forward-looking work for Color Thief. Everything from the original v2 improvement plan and the v3 rewrite has shipped (see the git history and CLAUDE.md for the delivered feature set). What remains below is not yet built.
Features we've deliberately decided not to build, so we don't keep revisiting them:
Browser wide-gamut support shipped in the gamut option work (#266): the browser loader reads through a P3 canvas, quantization runs in gamut-aware OKLCH, and Color objects carry .gamut with P3-faithful .css()/.oklch() while .rgb()/.array()/.hex() stay sRGB. Node still returns sRGB only.
What remains is P3 output on the Node path. sharp can already surface the embedded ICC tag via metadata(), so detection is easy; the work is driving its libvips color-transform pipeline to emit P3-encoded pixels (tagged display-p3) so getColor/getPalette reach parity with the browser. Possible later refinement: per-color rather than per-extraction gamut tagging, if a use case needs a mixed-gamut palette.
Region extraction (#176) crops the decoded pixel buffer right after loading, so every loader, quantizer, and progressive path works unchanged — including custom loaders supplied via configure(). Cropping earlier, inside the loaders (a drawImage source rect in the browser, sharp.extract() in Node), would skip decoding the parts of the image that get thrown away. Worth measuring first: it forks the crop logic across two loaders, and a custom loader can't be trusted to honor a region, so the central crop has to stay as the fallback either way.
The worker option became a no-op in v3.5 and the isWorkerSupported / extractInWorker / terminateWorker exports in colorthief/internals became deprecated shims. All of it comes out in v4.
Why it went: the worker only ever offloaded quantization, while decode, pixel sampling, and the structured clone of the pixel array stayed on the main thread — and serializing Array<[r, g, b]> cost several times more than the quantization it saved (at 2 MP / quality 10, ~20 ms of clone to avoid ~2.5 ms of quantize; the gap widens with image size). It also carried a hand-inlined copy of MMCQ that drifted from the real one: it quantized in RGB while the main path defaults to OKLCH, and skipped the few-color short-circuit and filter relaxation, so worker: true returned different colors than the default. The supported answer is to run Color Thief inside your own worker with an ImageBitmap source, which moves the whole pipeline off-thread and transfers pixels without copying.
Move Color Thief from a raw extractor toward a theming toolkit by generating a balanced, accessible N-role color scheme from an image — the space Material Color Utilities (HCT) targets. This is the largest differentiator and the direction the market is heading (dynamic/adaptive theming). We're already partway there: OKLCH quantization, semantic swatches, WCAG contrast, and textColor are all in place. The new work is scheme synthesis — deriving a harmonious, contrast-safe set of roles rather than just reporting the colors that are present. Hard; scope before committing.
A Rust implementation of the full MMCQ algorithm lives in src/wasm/, and the WasmQuantizer TypeScript adapter (src/quantizers/wasm.ts) is in place. What's missing is a plug-and-play distribution: today the module is source only and must be compiled by hand.
Current state (power-user only):
wasm-pack.wasm-pack build --target web in src/wasm/.WasmQuantizer — new WasmQuantizer(await import('./pkg/color_thief_wasm.js')).Goal — drop-in replacement, no build step:
.wasm and ship it as a published artifact (e.g. a @colorthief/wasm package, or a bundled binary loaded on demand) so consumers get it without a Rust toolchain.colorthief package pure JS with zero native dependencies — WASM stays opt-in via configure({ quantizer }) or per-call { quantizer }.Quantizer contract as the default MMCQ, so it's a true drop-in.What's already built (for reference):
Uint8Array, calls into WASM, and parses the 7-byte-per-color result (3 bytes RGB + 4 bytes little-endian population)