Color utilities
The Color type and findClosestColor helper.
The Color type
type Color = { r: number; g: number; b: number }Three integer channels, each 0–255. No alpha — dithering operates on opaque RGB and leaves the alpha channel of your pixel buffer untouched.
Palettes are Color[]. Any length. See
Algorithms for common
palette examples (Game Boy, Mac, CGA, etc.).
findClosestColor
findClosestColor(color: Color, palette: Color[]): ColorGiven a target color and a palette, returns the palette entry closest to the target using the redmean perceptual distance approximation (Riemersma, 1996). It's a cheap formula — no LAB conversion, no gamma handling — that adapts the per-channel weighting to the red level, so it's noticeably more perceptual than plain RGB Euclidean while staying fast enough for the inner loop of every dither algorithm. See Credits for the paper.
import { findClosestColor, type Color } from '@ditherkit/core'
const palette: Color[] = [
{ r: 0, g: 0, b: 0 },
{ r: 255, g: 255, b: 255 },
{ r: 255, g: 0, b: 0 },
]
findClosestColor({ r: 200, g: 10, b: 10 }, palette)
// → { r: 255, g: 0, b: 0 }
findClosestColor({ r: 120, g: 120, b: 120 }, palette)
// → { r: 0, g: 0, b: 0 } (mid-grey lands closer to black under redmean)When to use it directly
Most users never call findClosestColor themselves — the dithering
algorithms call it internally for every pixel. Reach for it when
you're building a custom pipeline:
- Posterisation without error diffusion (map every pixel to its nearest palette color, no dithering)
- Palette previews (render a swatch grid showing which palette entry each source color maps to)
- Custom dithering variants (ordered dithering, blue noise, etc.) where you still want the snap-to-palette step
Notes
- Redmean is a perceptual approximation, not LAB. It's faster than proper perceptual colour spaces (LAB, OKLCH) and good enough for dithering. If you need true perceptual accuracy for some other use case, compute it yourself and feed the result into your own pipeline.
- Returns the colour, not the distance. The internal distance is
computed as a squared value to skip a
Math.sqrtin the hot path, butfindClosestColoronly returns the winning palette entry — the distance itself is never exposed. - Empty palette returns black.
findClosestColor(x, [])returns{ r: 0, g: 0, b: 0 }rather than throwing. This keeps the dithering algorithms safe against accidental empty palettes without crashing mid-image. - Ties go to the first match in palette order. If two palette entries are equidistant from the target, the one earlier in the array wins.