colorlip
v1.0.0
Published
Composition-aware, perceptually tuned dominant color and palette extraction for images
Maintainers
Readme
colorlip
Perceptually tuned dominant color and palette extraction for Node.js and the browser.
colorlip is a lightweight, fast, TypeScript-first library for extracting dominant colors and compact palettes from images. It is tuned to pick colors that feel more visually representative, especially for illustrations, artwork, and product images where impression matters more than raw pixel counts. It is designed with visually driven use cases in mind, including illustration communities, social platforms, and commerce experiences.

Features
- Perceptually tuned color extraction for visually representative results
- Composition-aware weighting with center/border and edge-aware heuristics
- Adaptive palette extraction based on image statistics
- Natural merging of nearby colors using CIELAB Delta E
- Practical palette API with role-based
dominant,accent, andswatches - Rich color output:
hex,HSL,Lab,LCH,OKLab,OKLCH, CSS color strings, and hue category - Platform-agnostic core for raw pixel data
- Built-in adapters for
sharpand Canvas - Zero runtime dependencies in the core package
Why colorlip
colorlip is not just a color quantizer.
Many palette extractors mainly summarize image-wide color distribution. colorlip is tuned to choose colors that feel important in the image, using lightweight composition-aware heuristics and perceptual color spaces.
- It adapts thresholds from the image itself instead of relying only on fixed cutoffs
- It uses center/border and edge-aware weighting so large background areas do not always win
- It merges nearby colors perceptually in Lab, then picks
dominantandaccentfor different roles - It is especially tuned for illustrations, artwork, thumbnails, and visually curated image sets
Install
Core only:
npm install colorlipNode.js with the sharp adapter:
npm install colorlip sharpsharp is an optional peer dependency and is only required when you use colorlip/sharp.
Quick Start
Node.js
import { getColors, getPalette } from "colorlip/sharp";
const colors = await getColors("photo.jpg");
const palette = await getPalette("photo.jpg");
console.log(colors[0]?.hex);
console.log(palette.dominant?.hex);
console.log(palette.accent?.hex);
console.log(palette.swatches);Browser
import { getColors, getPalette } from "colorlip/canvas";
const colors = await getColors(imgElement);
const palette = await getPalette(imgElement);Raw Pixels
import { getColors, getPalette } from "colorlip";
const colors = getColors(pixelData, width, height, channels);
const palette = getPalette(pixelData, width, height, channels);Package Entry Points
colorlip
Core API for raw pixel buffers.
colorlip/sharp
Node.js adapter backed by sharp. It loads the image, downsizes it, and passes raw pixels to the core.
colorlip/canvas
Browser adapter backed by the Canvas API. It accepts browser image sources, draws them to a canvas, and passes ImageData to the core.
API
Core
import {
aggregateColors,
colorlip,
createDominantColor,
extractFallbackPalette,
getColors,
getHueCategory,
getPalette,
rgbToHex,
rgbToHsl,
} from "colorlip";getColors(data, width, height, channels, options?)
Extract dominant colors from raw pixel data.
data:Uint8Array | Uint8ClampedArraywidth: image widthheight: image heightchannels: typically3or4
Returns ColorlipColor[].
getPalette(data, width, height, channels, options?)
Extract a structured palette from raw pixel data.
Returns:
interface ColorlipPalette {
dominant: ColorlipColor | null;
accent: ColorlipColor | null;
swatches: ColorlipColor[];
}colorlip(...)
Compatibility alias of getColors(...).
Node.js Adapter
import {
colorlip,
colorlipFromBuffer,
colorlipFromFile,
getColors,
getColorsFromPixels,
getPalette,
getPaletteFromPixels,
} from "colorlip/sharp";getColors(source, options?)
source:string | Buffer | Uint8Array
Loads an image through sharp and returns Promise<ColorlipColor[]>.
getPalette(source, options?)
source:string | Buffer | Uint8Array
Loads an image through sharp and returns Promise<ColorlipPalette>.
getColorsFromPixels(...)
Re-export of the raw-pixel core API.
getPaletteFromPixels(...)
Re-export of the raw-pixel core palette API.
Legacy aliases
colorlipFromFile(...)colorlipFromBuffer(...)
Browser Adapter
import {
colorlip,
colorlipFromImage,
colorlipFromImageData,
getColors,
getColorsFromImageData,
getColorsFromPixels,
getPalette,
getPaletteFromImageData,
getPaletteFromPixels,
} from "colorlip/canvas";getColors(source, options?)
source:HTMLImageElement | ImageBitmap | Blob | string
Returns Promise<ColorlipColor[]>.
getPalette(source, options?)
source:HTMLImageElement | ImageBitmap | Blob | string
Returns Promise<ColorlipPalette>.
getColorsFromImageData(imageData, options?)
Extract colors directly from ImageData.
getPaletteFromImageData(imageData, options?)
Extract a palette directly from ImageData.
getColorsFromPixels(...)
Re-export of the raw-pixel core API.
getPaletteFromPixels(...)
Re-export of the raw-pixel core palette API.
Legacy aliases
colorlipFromImage(...)colorlipFromImageData(...)
Options
interface ExtractOptions {
numColors?: number; // default: 3
saturationThreshold?: number; // default: 0.15
brightnessMin?: number; // default: 20
brightnessMax?: number; // default: 235
quantizationStep?: number; // default: 12
}Default values:
{
numColors: 3,
saturationThreshold: 0.15,
brightnessMin: 20,
brightnessMax: 235,
quantizationStep: 12,
}Output
Each extracted color is returned as a ColorlipColor:
interface ColorlipColor {
r: number;
g: number;
b: number;
hex: string;
percentage: number;
hue: number;
saturation: number;
lightness: number;
hueCategory: HueCategory;
lab: { L: number; a: number; b: number };
lch: { L: number; C: number; H: number };
oklab: { L: number; a: number; b: number };
oklch: { L: number; C: number; H: number };
css: {
rgb: string;
hsl: string;
lab: string;
lch: string;
oklab: string;
oklch: string;
};
}Example:
{
r: 42,
g: 98,
b: 168,
hex: "#2A62A8",
percentage: 0.34,
hue: 213,
saturation: 60,
lightness: 41,
hueCategory: "blue",
lab: { L: 41.2, a: -2.3, b: -40.1 },
lch: { L: 41.2, C: 40.2, H: 266.7 },
oklab: { L: 0.49, a: -0.03, b: -0.12 },
oklch: { L: 0.49, C: 0.12, H: 256.7 },
css: {
rgb: "rgb(42 98 168)",
hsl: "hsl(213 60% 41%)",
lab: "lab(41.2 -2.3 -40.1)",
lch: "lch(41.2 40.2 266.7)",
oklab: "oklab(0.49 -0.03 -0.12)",
oklch: "oklch(0.49 0.12 256.7)",
},
}Utility Functions
import {
aggregateColors,
createDominantColor,
getHueCategory,
rgbToHex,
rgbToHsl,
} from "colorlip";| Function | Description |
| --- | --- |
| rgbToHex(r, g, b) | Convert RGB to #RRGGBB |
| rgbToHsl(r, g, b) | Convert RGB to { h, s, l } |
| getHueCategory(hue) | Convert hue to "red" \| "orange" \| ... \| "gray" |
| createDominantColor(r, g, b, percentage) | Build a full ColorlipColor |
| aggregateColors(colorSets, numColors?) | Merge multiple extraction results into top colors |
| extractFallbackPalette(...) | Run the simplified fallback extractor directly |
How It Works
The extraction pipeline is:
- Adapter input is resized to a maximum of
150x150pixels. Raw-pixel core calls skip this step. - A sampling pass estimates median saturation, saturation spread, and how centrally edges are concentrated.
- Pixels with
alpha < 0.5are ignored. Remaining pixels are included with alpha-based weighting. - Pixels are filtered by adaptive saturation floor and configured brightness range.
- Surviving pixels are quantized into bins and weighted by center bias, edge strength, saturation, and alpha.
- Nearby bins are pre-merged in Lab space using an adaptive Delta E threshold.
- Clusters are scored from weighted presence, then adjusted by spatial distribution, center/border bias, centroid position, spread, and accent preference in OKLCH space.
- Final swatches are selected with another adaptive Delta E merge pass.
dominantis chosen from the leading swatches, not only by raw rank but also by a dominant-preference pass in OKLCH space. This helps promote colors that feel more representative and visually salient, instead of always keeping the darkest or heaviest cluster at the top.accentis chosen from perceptually distant candidates or swatches.- If the main path produces no candidates, a simpler histogram-based fallback extractor is used.
Dominant Selection
palette.dominant is selected in two stages:
colorlipfirst builds and scores cluster candidates from weighted image regions.- After final swatches are decided, the leading swatches are re-ranked for
dominantselection using:- the original extraction score
- weighted presence
- a perceptual preference in OKLCH space
This extra pass keeps dominant closer to the color that feels like the image's representative color, especially for photos where a large dark area might otherwise win by area alone.
Alpha Handling
When the input has an alpha channel:
- Pixels with
alpha < 128are ignored - Pixels with
alpha >= 128are kept - Their contribution is weighted by
(alpha / 255) ** 2
This reduces semi-transparent edge noise while still allowing partially visible colors to contribute.
Notes
- The core accepts both
Uint8ArrayandUint8ClampedArray channelsis expected to be3or4- Browser string sources are fetched with
fetch(...) - Browser adapter resizing happens through
OffscreenCanvas - Grayscale or heavily filtered inputs may fall back to the simpler histogram path
Compatibility Aliases
These names remain available for compatibility:
DominantColoras a type alias ofColorlipColorcolorlip(...)colorlipFromFile(...)colorlipFromBuffer(...)colorlipFromImage(...)colorlipFromImageData(...)
