How to Extract Dominant Colors from an Image (With Code)
Pulling a color palette from a photo — for a website theme, a brand kit, or matching paint swatches to a picture you like — comes down to one question: which pixels actually represent the image, and which are noise?
The eyedropper tool in Photoshop or GIMP samples exactly one pixel per click, which is a problem: what looks like a solid red patch is usually hundreds of slightly different reds from lighting and compression artifacts. Grab the wrong one and your "dominant" color is off. Algorithmic extraction fixes this by analyzing every pixel and grouping similar colors together mathematically.
How the algorithms actually work
K-Means Clustering is the most common approach. You choose a number of clusters k (e.g., 5 colors), and the algorithm:
- Picks k random starting colors
- Assigns every pixel to its nearest starting color (by distance in RGB space)
- Recalculates each cluster's average color from its assigned pixels
- Repeats steps 2–3 until the clusters stop changing
The final cluster averages are your dominant palette.
Median Cut takes a different route: it repeatedly splits the image's color space in half along whichever axis (R, G, or B) has the widest range, until you have k boxes. Each box's average color is one palette entry. It's faster than k-means and is what most "quantize to N colors" tools (including GIF conversion) use internally.
Neither approach cares what the colors mean — they just measure which RGB values appear most and cluster them. That's why a photo of a red car on green grass reliably gives you red + green as dominant colors, but a subtler image with lots of near-identical midtones can give a less useful palette; the clusters just aren't as distinct.
Code: extract colors yourself
JavaScript (Canvas API — runs in the browser)
function getDominantColors(imageElement, k = 5) {
const canvas = document.createElement('canvas');
canvas.width = imageElement.width;
canvas.height = imageElement.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(imageElement, 0, 0);
const { data } = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = [];
for (let i = 0; i < data.length; i += 4) {
// skip fully transparent pixels — they skew results otherwise
if (data[i + 3] > 0) pixels.push([data[i], data[i + 1], data[i + 2]]);
}
// simple k-means (naive, fine for palette extraction at this scale)
let centroids = pixels.slice(0, k);
for (let iter = 0; iter < 10; iter++) {
const clusters = Array.from({ length: k }, () => []);
for (const p of pixels) {
let best = 0, bestDist = Infinity;
centroids.forEach((c, idx) => {
const dist = (p[0]-c[0])**2 + (p[1]-c[1])**2 + (p[2]-c[2])**2;
if (dist < bestDist) { bestDist = dist; best = idx; }
});
clusters[best].push(p);
}
centroids = clusters.map(cluster =>
cluster.length
? cluster.reduce((a, p) => [a[0]+p[0], a[1]+p[1], a[2]+p[2]], [0,0,0])
.map(v => Math.round(v / cluster.length))
: [0, 0, 0]
);
}
return centroids; // array of [r, g, b]
}
Python (using Pillow + colorthief-style clustering)
from PIL import Image
def get_dominant_colors(path, k=5):
img = Image.open(path).convert("RGB")
img = img.resize((150, 150)) # downsample for speed, doesn't hurt accuracy much
result = img.quantize(colors=k, method=Image.MEDIANCUT)
palette = result.getpalette()[:k * 3]
return [tuple(palette[i:i+3]) for i in range(0, len(palette), 3)]
print(get_dominant_colors("photo.jpg", k=5))
Both approaches give you RGB tuples you can convert to hex (#%02x%02x%02x % (r, g, b) in Python, or .toString(16) per channel in JS).
Three things that throw off your results
1. Transparent or near-transparent pixels. If you don't filter by alpha channel, extraction on PNGs with transparency will often return black or white as a "dominant" color — that's the transparent background, not real image content.
2. Watermarks and UI overlays. A stock photo with a visible watermark, or a screenshot with a solid-color status bar, will pull those flat regions into your palette even though they're not part of the actual photo.
3. Choosing k too low or too high. k=3 on a visually complex image collapses distinct colors into muddy averages. k=10 on a simple two-tone logo gives you near-duplicate colors that aren't meaningfully different. Start at k=5 and adjust based on what you're looking at.
Practical uses once you have the palette
- Web theming: pull colors from a hero image and use them for buttons/accents so the page feels cohesive instead of arbitrary
- Brand consistency: derive supporting graphics from a campaign's key photo
- Matching physical colors: extract from a photo of a room or fabric to shop for paint or textiles that match
Skip the code
If you just need a palette right now rather than running a script, ToolSink's Image Color Extractor does the k-means clustering above directly in your browser — the image never leaves your device, and you get hex and RGB codes ready to copy.