npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

colorlip

v1.0.0

Published

Composition-aware, perceptually tuned dominant color and palette extraction for images

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.

日本語版 README

colorlip preview

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, and swatches
  • 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 sharp and 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 dominant and accent for different roles
  • It is especially tuned for illustrations, artwork, thumbnails, and visually curated image sets

Install

Core only:

npm install colorlip

Node.js with the sharp adapter:

npm install colorlip sharp

sharp 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 | Uint8ClampedArray
  • width: image width
  • height: image height
  • channels: typically 3 or 4

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:

  1. Adapter input is resized to a maximum of 150x150 pixels. Raw-pixel core calls skip this step.
  2. A sampling pass estimates median saturation, saturation spread, and how centrally edges are concentrated.
  3. Pixels with alpha < 0.5 are ignored. Remaining pixels are included with alpha-based weighting.
  4. Pixels are filtered by adaptive saturation floor and configured brightness range.
  5. Surviving pixels are quantized into bins and weighted by center bias, edge strength, saturation, and alpha.
  6. Nearby bins are pre-merged in Lab space using an adaptive Delta E threshold.
  7. Clusters are scored from weighted presence, then adjusted by spatial distribution, center/border bias, centroid position, spread, and accent preference in OKLCH space.
  8. Final swatches are selected with another adaptive Delta E merge pass.
  9. dominant is 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.
  10. accent is chosen from perceptually distant candidates or swatches.
  11. If the main path produces no candidates, a simpler histogram-based fallback extractor is used.

Dominant Selection

palette.dominant is selected in two stages:

  1. colorlip first builds and scores cluster candidates from weighted image regions.
  2. After final swatches are decided, the leading swatches are re-ranked for dominant selection 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 < 128 are ignored
  • Pixels with alpha >= 128 are 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 Uint8Array and Uint8ClampedArray
  • channels is expected to be 3 or 4
  • 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:

  • DominantColor as a type alias of ColorlipColor
  • colorlip(...)
  • colorlipFromFile(...)
  • colorlipFromBuffer(...)
  • colorlipFromImage(...)
  • colorlipFromImageData(...)

License

MIT