# `Image.Plug.Pipeline.Normaliser`
[🔗](https://github.com/elixir-image/image_plug/blob/v0.1.0/lib/image/plug/pipeline/normaliser.ex#L1)

Reorders, folds, and validates a pipeline so that two requests with
the same semantic effect produce identical fingerprints — and so
that order-sensitive libvips operations land in a position where
they actually work.

### Canonical operation order

Mirrors the order
[Sharp](https://github.com/lovell/sharp/blob/main/src/pipeline.cc)
applies operations internally (Sharp wraps the same libvips
primitives this library uses). The order encodes years of
experience with libvips' constraints — most importantly that
resize must run early enough to benefit from libvips'
shrink-on-load fast path, and that operations like blur, sharpen,
and modulate must run in a specific bracket around resize.

The ordering this module enforces:

1. `%Trim{}` — trim has to run before resize so the resize sees
   only the meaningful pixels.

2. `%Background{}` — flatten alpha against the chosen background
   before any colour-shifting op runs.

3. `%Resize{}` — runs as early as possible so libvips can
   shrink-on-load.

4. `%Rotate{}` and `%Flip{}` — Cloudflare's rotate is free-angle
   and runs post-resize.

5. `%Border{}` — embedded after resize because it grows the canvas.

6. `%Adjust{}` — Sharp's "modulate" stage. Runs after geometry
   so the new pixels participate in tone shifts.

7. `%Colorspace{}` — runs after `%Adjust{}` (whose multipliers
   expect RGB) and before `%ReplaceColor{}` (whose chroma-key
   match operates on the post-conversion bytes).

8. `%ReplaceColor{}` — colour-substitution. Runs after Adjust so
   tone shifts on the source colour have already settled, and
   before the sigma-based ops so blur/sharpen operate on the
   post-replace pixels.

9. `%Blur{}` — Sharp explicitly runs blur before sharpen.

10. `%Sharpen{}`.

11. `%Draw{}` — composite layers go on top of the finished base.

12. `%Pipeline.Ops.Segment{}` — placeholder; ordered last for now.

### Cardinality

These ops must appear at most once per pipeline. The provider's
options parser already de-duplicates per request, but the
normaliser is the source of truth so any future programmatic
pipeline-builder gets the same guarantee:

* `Resize`, `Trim`, `Flip`, `Rotate`, `Background`, `Border`,
  `Adjust`, `Colorspace`, `ReplaceColor`, `Sharpen`, `Blur`,
  `Segment`.

`Draw` may appear at most once but holds an arbitrary list of
layers internally, so multiple overlay requests collapse onto
layers of one Draw op.

### No-op folding

Drops ops whose fields make them semantically inert:

* `%Resize{width: nil, height: nil}`

* `%Rotate{angle: 0}`

* `%Flip{direction: nil}`

* `%Adjust{}` with every multiplier `1.0`

* `%Sharpen{sigma: 0}`, `%Blur{sigma: 0}`

* `%Border{}` with every side `0`

* `%Trim{mode: :explicit}` with every side `0`

### Idempotence

`normalise(normalise(p)) == normalise(p)` for every input.

### Errors

Returns `{:error, %Image.Plug.Error{tag: :invalid_option}}` when
the pipeline contains more than one of an op kind that must be
unique.

# `normalise`

```elixir
@spec normalise(Image.Plug.Pipeline.t()) ::
  {:ok, Image.Plug.Pipeline.t()} | {:error, Image.Plug.Error.t()}
```

Normalises a pipeline. See the moduledoc for the rules applied.

### Arguments

* `pipeline` is an `Image.Plug.Pipeline` struct.

### Returns

* `{:ok, pipeline}` on success.

* `{:error, %Image.Plug.Error{tag: :invalid_option}}` on a
  cardinality violation.

### Examples

    iex> alias Image.Plug.Pipeline
    iex> alias Image.Plug.Pipeline.Ops.{Resize, Rotate, Sharpen}
    iex> p =
    ...>   Pipeline.new()
    ...>   |> Pipeline.append(%Sharpen{sigma: 1.0})
    ...>   |> Pipeline.append(%Resize{width: 200})
    ...>   |> Pipeline.append(%Rotate{angle: 90})
    iex> {:ok, normalised} = Image.Plug.Pipeline.Normaliser.normalise(p)
    iex> Enum.map(normalised.ops, & &1.__struct__)
    [Image.Plug.Pipeline.Ops.Resize,
     Image.Plug.Pipeline.Ops.Rotate,
     Image.Plug.Pipeline.Ops.Sharpen]

---

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