Extracts a small representative colour palette from an image.
The pipeline is the one described in Amanda Hinton's
palette post,
adapted for :image:
- Resize the image so its longest side is
:longest_dimpixels (default 300). The cost of every later step is linear in pixel count, so this is the single biggest performance knob. - Drop transparent pixels when the image has alpha and convert the rest to sRGB.
- Sample up to
:max_pixelspixels (default 90 000) uniformly. - Convert the pixel batch to Oklab in one
vectorised pass via
Image.Color.srgb_tensor_to_oklab/1. - Cluster in Oklab using
Scholar.Cluster.KMeanswith the chromatic axes weighted twice as much as lightness (a,bcolumns scaled by√ab_weightbefore fitting). - Merge near-duplicate clusters via
Color.Palette.Cluster.merge_until/3. - Phantom guard: drop low-mass low-chroma clusters
(default
< 2.5%of total mass and centroid chroma< 0.05) so small pockets of near-grey pixels can't claim a palette slot. - Pick a representative sRGB swatch per surviving
cluster via
Color.Palette.Cluster.representative/2. - Sort the result with
Color.Palette.sort/2so the output reads as a perceptual rainbow.
The clustering and rep-selection primitives live in
Color.Palette.Cluster
so the algorithm doesn't drift between this library and
other callers (e.g. Color.Palette.Summarize).
Determinism
Pass :key (any Nx.Random key) to make the K-means
initialisation deterministic; otherwise different runs may
produce slightly different palettes for the same image.
Requires
Image.Palette is only compiled when both
scholar and
nx are present.
Summary
Clusters
@spec extract(image :: Vix.Vips.Image.t(), options :: Keyword.t()) :: {:ok, [Color.SRGB.t()]} | {:error, term()}
Extracts a representative colour palette from an image.
Arguments
imageis anyVix.Vips.Image.t/0.optionsis a keyword list of options.
Options
:finalis the maximum number of swatches in the output. Default5. The output may be shorter after the phantom guard or if the input image has very few distinct colours.:kis the number of K-means clusters used internally before the merge / phantom-guard passes. Default14. Must satisfyk >= final.:longest_dimis the pre-clustering resize target. The image is thumbnailed so its longest side is this many pixels. Default300.:max_pixelscaps the post-resize sample size. Default90000.:ab_weightis the multiplier on the chromatic axes(a, b)in the Oklab distance metric, relative to lightnessL. Default2.0. Used both during K-means (by pre-scaling the input columns) and during merge / rep-selection.:phantom_min_massis the fraction of total pixel mass below which a cluster is "phantom"-eligible. Default0.025.:phantom_max_chromais the Oklch chroma below which a phantom-eligible cluster is dropped. Default0.05.:rep_chroma_thresholdis forwarded toColor.Palette.Cluster.representative/2. Default0.03.:sortselects the post-extraction sort strategy. One of the strategies accepted byColor.Palette.sort/2, orfalseto skip sorting and return clusters in K-means order. Default:hue_lightness.:keyis anNx.Randomkey for deterministic K-means initialisation. Default: a fresh random key per call (so results are stable within a call but may vary between calls).
Returns
{:ok, [%Color.SRGB{}, ...]}on success — a list of at most:finalrepresentative swatches.{:error, reason}if image conversion or sampling fails.
Examples
iex> {:ok, image} = Image.open("./test/support/images/Hong-Kong-2015-07-1998.jpg")
iex> {:ok, palette} = Image.Palette.extract(image, key: Nx.Random.key(42))
iex> length(palette) <= 5
true
iex> Enum.all?(palette, &match?(%Color.SRGB{}, &1))
true
@spec extract!(image :: Vix.Vips.Image.t(), options :: Keyword.t()) :: [ Color.SRGB.t() ]
Same as extract/2 but raises on error.
Examples
iex> image = Image.open!("./test/support/images/Hong-Kong-2015-07-1998.jpg")
iex> palette = Image.Palette.extract!(image, key: Nx.Random.key(42))
iex> length(palette) <= 5
true