utilime

Small tools that finish the job.

Extract a palette from an image

Drop in a picture and get a set of colours back, each one carrying the share of the frame it stands for.

Drop a file hereor click to choose · it stays on your device

How it works

A picture does not contain a dominant colour waiting to be read off it. It contains a few million pixels, and a palette is the result of a decision about how to group them: how many groups to allow, what counts as near, and which pixels are given a vote. Dominant colour is not a measurement, it is the output of that decision. Two extractors run over the same photograph will disagree, and neither of them has made an error.

Two ways to divide the colours up

k-means places a fixed number of centres in colour space and moves each one to the mean of the pixels nearest it, over and over, until they stop moving. It has to be told how many centres to place, and what it minimises is the total squared distance from every pixel to its own centre, so the centres settle where pixels are dense. A long gradient, a sky or a wall falling into shadow, collapses into a single centre, because that placement is what makes the distance small.

Median cut works the other way round. Every pixel starts in one box; the box is measured on each of its three axes, sorted along the widest one, and cut at the median. Repeat on whichever box holds the most pixels. Heckbert published the method in 1980 for palette quantisation, and cutting at the median rather than the midpoint is the whole character of it: the boxes come out with roughly equal populations instead of roughly equal volumes, so a picture with one huge smooth region gets that region subdivided rather than represented once.

The two do not merely suit different pictures, they disagree about what a colour is. Measured on synthetic images where the right answer is known in advance: three equal flat bands of #e63946, #457b9d and #f4a261 come back from k-means as those exact three values, every swatch sitting zero distance from its own pixels.

Median cut on the same bands recovers #457b9d, gives half the frame to #ef8a59 (an average of the red band and the orange band), and fills its third slot with a dusty rose mixed out of the blue and the red. Its cut lands at the population median rather than at a colour boundary, so a box straddles two bands and hands back a colour that is in neither.

That gap holds on photographic material too. On a five-region synthetic photograph the widest distance between a swatch and a pixel it stands for came to 0.080 under k-means and 0.319 under median cut, and the same figure is reported for your own file.

Median cut earns its option where equal occupancy is the point. Asked for four colours from a black-to-white ramp of one pixel per level, it returns #242424, #616161, #a0a0a0 and #e0e0e0, each standing for exactly a quarter of the pixels; k-means splits the same ramp 28.1, 27.3, 24.2 and 20.3 percent, since it is equalising distance rather than population. Neither division is the correct one. They answer different questions, and a set of swatches meant to carry equal weight wants the first. For recovering the colours a picture actually contains, k-means is the default here.

Averaging stored bytes drags a mixture dark

The number in an 8-bit sRGB channel is not proportional to light. It has been through the sRGB transfer curve from IEC 61966-2-1, which spends more of the 256 codes on dark values than light ones, so adding two bytes and halving is arithmetic performed on the wrong quantity. Black and white averaged as bytes gives 128, and 128 decodes to a linear value of 0.216, a grey reflecting about a fifth of the light rather than half of it. Decode first, average, then re-encode and the answer is 188, or #bcbcbc. In OKLab those two greys sit at lightness 0.600 and 0.795.

The same error runs through every cluster average, more quietly. Take a field at #3f7a2a and a sky at #7ab8e8 in equal measure. Averaged as stored bytes the centre lands on #5d9989; averaged in linear light it lands on #629dac. Both are a muted teal that appears nowhere in the picture, which is the useful signal that one cluster is covering two things. The byte average is darker and pulled towards the green, so it also misreports which of the two the mixture is closer to. Every average on this page is taken after decoding.

Distance in sRGB is not perceived difference

Clustering needs a definition of near, and Euclidean distance across three sRGB channels gives all three the same weight. Light does not. Moving the green channel by 16 of 255, from #00cc00 to #00dc00, is an OKLab difference of 0.045; the same 16 steps on blue, #0000cc to #0000dc, comes to 0.027.

A clusterer measuring in sRGB reads those two moves as identical, so a single threshold is too coarse for green and too fine for blue: it holds together a green range a person sees as two colours, then spends a whole swatch on a blue variation nobody would call a second colour. Median cut inherits the same bias when it picks a box's widest axis. The k-means option runs in OKLab instead, the perceptual space the palette builder page sets out.

The backdrop wins on count

On product photography the most common colour in the frame is the paper sweep behind the product, and on a screenshot it is the page background. Counting pixels answers a question nobody asked. The filter drops any sampled pixel whose OKLab chroma is under 0.04 and whose lightness is above 0.92 or below 0.25, which for a neutral grey means #e5e5e5 and lighter, or #212121 and darker.

The chroma clause is what keeps saturated colours in: pure yellow sits at lightness 0.968, well over the ceiling, and stays in the vote on its chroma of 0.211. The pixel count the filter removed is reported, so the cost of turning it on is visible.

Nothing here is random. Pixels are read on a fixed grid at a stride that brings the long edge to about 200, capping the vote at 40,000 pixels however large the file. k-means is seeded by k-means++ from a fixed constant rather than from Math.random, and stops once no centre has moved further than 0.0001 in OKLab, or after 40 passes.

The same file with the same settings returns the same palette. Two reported figures say whether to believe it: the share each colour holds, and the widest gap between a swatch and the pixels standing behind it. A wide gap means the picture carries more variety than the number of colours you asked for.

Questions

Why does asking for one more colour change all the others?

Because k-means repartitions from scratch. The six-colour answer is not the five-colour answer with an extra entry: every pixel is reassigned to whichever of the six centres is now nearest, so a centre that had been straddling two regions splits and both halves move away from where they were. Median cut behaves more like nesting, since each extra colour cuts one existing box and leaves every other box alone.

Does it look at every pixel in my image?

No. It walks a fixed grid, taking every nth pixel in both directions with n chosen to bring the long edge to about 200, so at most 40,000 pixels vote no matter how big the file is. The sampled count and the full resolution are both reported. The shares are estimates from that sample, and a region smaller than one grid cell can be missed altogether, which is worth knowing before you trust a palette to find a small accent colour.

Why is the largest swatch a colour I never noticed?

Because area and attention are different things. A sky, a tablecloth or an out-of-focus wall can hold half the frame while carrying none of the subject. The near-white and near-black filter removes neutral backdrops; a coloured backdrop is outside its remit, since the filter only touches near-neutral extremes. For that case read down the share column and take the swatch you wanted rather than the first one.

Which method should I choose?

k-means for almost everything. Given three equal flat bands of #e63946, #457b9d and #f4a261 it returns those exact values, and on a synthetic five-region photograph the furthest a pixel sat from its own swatch was 0.080, against 0.319 for median cut on the same image. Switch to median cut when you want every swatch to stand for the same number of pixels: on a black-to-white ramp it gives four steps holding a quarter each, where k-means gives 28.1, 27.3, 24.2 and 20.3 percent. Flipping between the two on your own file shows how much of the palette was the method rather than the picture.

Will I get the same palette twice from the same file?

Yes, byte for byte. The sampling grid is fixed, k-means++ draws from a local generator seeded with a constant, and every sort has a tiebreak on the hex value, so no step can depend on timing or on Math.random. A palette that shifted between runs could not be matched to a design token, and a user could not report a bad result in a way anyone could reproduce.