# `Image.Palette`
[🔗](https://github.com/elixir-image/image/blob/v0.67.0/lib/image/palette.ex#L2)

Extracts a small representative colour palette from an image.

The pipeline is the one described in Amanda Hinton's
[palette post](https://amandahinton.com/blog/creating-a-color-palette-from-an-image),
adapted for `:image`:

  1. **Resize** the image so its longest side is `:longest_dim`
     pixels (default 300). The cost of every later step is
     linear in pixel count, so this is the single biggest
     performance knob.
  2. **Drop transparent pixels** when the image has alpha and
     convert the rest to sRGB.
  3. **Sample** up to `:max_pixels` pixels (default 90 000)
     uniformly.
  4. **Convert** the pixel batch to **Oklab** in one
     vectorised pass via `Image.Color.srgb_tensor_to_oklab/1`.
  5. **Cluster** in Oklab using
     [`Scholar.Cluster.KMeans`](https://hexdocs.pm/scholar/Scholar.Cluster.KMeans.html)
     with the chromatic axes weighted twice as much as
     lightness (`a`, `b` columns scaled by `√ab_weight`
     before fitting).
  6. **Merge near-duplicate clusters** via
     `Color.Palette.Cluster.merge_until/3`.
  7. **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.
  8. **Pick a representative** sRGB swatch per surviving
     cluster via `Color.Palette.Cluster.representative/2`.
  9. **Sort** the result with `Color.Palette.sort/2` so the
     output reads as a perceptual rainbow.

The clustering and rep-selection primitives live in
[`Color.Palette.Cluster`](https://hexdocs.pm/color/Color.Palette.Cluster.html)
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`](https://hex.pm/packages/scholar) and
[`nx`](https://hex.pm/packages/nx) are present.

# `extract`
*since 0.67.0* 

```elixir
@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

* `image` is any `t:Vix.Vips.Image.t/0`.

* `options` is a keyword list of options.

### Options

* `:final` is the maximum number of swatches in the output.
  Default `5`. The output may be shorter
  after the phantom guard or if the input image has very
  few distinct colours.

* `:k` is the number of K-means clusters used internally
  before the merge / phantom-guard passes. Default
  `14`. Must satisfy `k >= final`.

* `:longest_dim` is the pre-clustering resize target. The
  image is thumbnailed so its longest side is this many
  pixels. Default `300`.

* `:max_pixels` caps the post-resize sample size. Default
  `90000`.

* `:ab_weight` is the multiplier on the chromatic axes
  `(a, b)` in the Oklab distance metric, relative to
  lightness `L`. Default `2.0`. Used both
  during K-means (by pre-scaling the input columns) and
  during merge / rep-selection.

* `:phantom_min_mass` is the fraction of total pixel mass
  below which a cluster is "phantom"-eligible. Default
  `0.025`.

* `:phantom_max_chroma` is the Oklch chroma below which a
  phantom-eligible cluster is dropped. Default
  `0.05`.

* `:rep_chroma_threshold` is forwarded to
  `Color.Palette.Cluster.representative/2`. Default
  `0.03`.

* `:sort` selects the post-extraction sort strategy. One of
  the strategies accepted by `Color.Palette.sort/2`, or
  `false` to skip sorting and return clusters in K-means
  order. Default `:hue_lightness`.

* `:key` is an `Nx.Random` key 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 `:final` representative 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

# `extract!`
*since 0.67.0* 

```elixir
@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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
