DEV Community

SEN LLC
SEN LLC

Posted on

Extracting Dominant Colors From Images With K-Means Clustering in the Browser

Extracting Dominant Colors From Images With K-Means Clustering in the Browser

Drop an image, get a 5-color palette. The tool draws the image to a hidden Canvas, samples 5000 pixels, runs k-means with k-means++ initialization, and presents the cluster centroids as a copyable palette. Everything runs in the browser — no upload, no server.

Color palette extraction is one of those tasks that sounds like it needs a server or a Python script, but Canvas getImageData gives you raw pixel access and k-means is simple enough to run client-side in milliseconds.

🔗 Live demo: https://sen.ltd/portfolio/color-from-image/
📦 GitHub: https://github.com/sen-ltd/color-from-image

Screenshot

Features:

  • Drag & drop or file picker
  • K-means clustering (k=3-8 adjustable)
  • K-means++ initialization
  • Hex / RGB / HSL display
  • Export as CSS variables, JSON, SCSS
  • Fully local processing
  • Japanese / English UI
  • Zero dependencies, 35 tests

K-means in RGB space

Each pixel is a point in 3D space (R, G, B). K-means finds k cluster centroids that minimize the total squared distance from each pixel to its nearest centroid.

The algorithm:

  1. Initialize k centroids using k-means++
  2. Assign each pixel to its nearest centroid
  3. Recompute each centroid as the mean of its assigned pixels
  4. Repeat until convergence or max iterations
export function kMeans(pixels, k, maxIterations = 20) {
  let centroids = initKMeansPP(pixels, k);
  for (let iter = 0; iter < maxIterations; iter++) {
    const clusters = assignClusters(pixels, centroids);
    const newCentroids = recomputeCentroids(clusters, centroids);
    if (centroidsConverged(centroids, newCentroids)) break;
    centroids = newCentroids;
  }
  return centroids;
}
Enter fullscreen mode Exit fullscreen mode

K-means++ initialization

Random initialization can produce poor results if multiple centroids start in the same cluster. K-means++ solves this:

  1. Pick the first centroid uniformly at random
  2. For each subsequent centroid, pick a pixel with probability proportional to its squared distance from the nearest existing centroid

This spreads initial centroids across the color space, dramatically improving convergence.

Pixel sampling

A 1000×1000 image has 1 million pixels. Running k-means on all of them is slow. Sampling 5000 pixels evenly is enough to capture the color distribution:

export function samplePixels(imageData, sampleSize = 5000) {
  const step = Math.max(1, Math.floor(total / sampleSize));
  for (let i = 0; i < total; i += step) {
    // skip transparent pixels (alpha < 128)
    pixels.push({ r: data[idx], g: data[idx+1], b: data[idx+2] });
  }
}
Enter fullscreen mode Exit fullscreen mode

Color conversions

export function rgbToHsl(r, g, b) {
  r /= 255; g /= 255; b /= 255;
  const max = Math.max(r, g, b), min = Math.min(r, g, b);
  const l = (max + min) / 2;
  // ... standard HSL formula
}
Enter fullscreen mode Exit fullscreen mode

All three formats (hex, RGB, HSL) are useful in different contexts. Designers think in HSL; CSS uses hex; APIs use RGB arrays.

Series

This is entry #41 in my 100+ public portfolio series.

Top comments (0)