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
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:
- Initialize k centroids using k-means++
- Assign each pixel to its nearest centroid
- Recompute each centroid as the mean of its assigned pixels
- 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;
}
K-means++ initialization
Random initialization can produce poor results if multiple centroids start in the same cluster. K-means++ solves this:
- Pick the first centroid uniformly at random
- 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] });
}
}
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
}
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.
- 📦 Repo: https://github.com/sen-ltd/color-from-image
- 🌐 Live: https://sen.ltd/portfolio/color-from-image/
- 🏢 Company: https://sen.ltd/

Top comments (0)