# `Image.Components.URL`
[🔗](https://github.com/elixir-image/image_components/blob/v0.1.1/lib/image/components/url.ex#L1)

Per-provider URL builders.

Projects a canonical `t:Image.Plug.Pipeline.t/0` onto the URL
grammar of each supported provider. This is the inverse of the URL
parsers in `image_plug`'s provider modules: those parsers
consume URLs and produce a `Pipeline`; these builders go the
other way — `Pipeline` → provider-specific URL string.

Five providers are supported: four commercial image CDNs
(Cloudflare Images, Cloudinary, imgix, ImageKit) plus
[IIIF Image API 3.0](https://iiif.io/api/image/3.0/), the open
standard implemented by cultural-heritage and academic image
servers (Cantaloupe, Loris, IIPImage, Wellcome, Library of
Congress, …). The same IR drives all five grammars, so an
option set produces five URLs with comparable semantics — modulo
the per-provider feature gaps.

## Coverage

Implements the round-trip subset shared by the five providers:
resize (width/height/fit/gravity/dpr), format/quality,
blur/sharpen, brightness/contrast/saturation/gamma, rotate,
trim, background, plus IIIF-specific `region` and named-quality
(`gray`/`bitonal`) tokens. Operations not natively expressible
in a given provider's URL grammar are dropped silently and
documented in the corresponding `image_plug` provider's
conformance guide.

## Provider semantic differences

The five providers do not all express adjust effects the same
way:

  * **Cloudflare** takes brightness/contrast/saturation/gamma
    as raw multipliers (the same units as the IR; `1.0` = no
    change).

  * **Cloudinary** and **imgix** take centred percentages in
    `-100..100`, where `0` = no change. The builders below
    convert: an IR value of `1.4` becomes `e_contrast:40` for
    Cloudinary and `con=40` for imgix.

  * **ImageKit** has only an unparameterised `e-contrast`
    toggle (auto-contrast). Brightness/contrast/saturation/
    gamma multipliers cannot be faithfully expressed in
    ImageKit URL form, so they are silently dropped — no
    approximation, by design. See
    `guides/imagekit_conformance.md` in `image_plug`.

  * **IIIF** has no parameterised adjust effects at all. Only
    `Adjust{saturation: 0.0}` round-trips, via the `gray`
    quality token in the URL's quality segment; everything else
    (brightness, contrast, gamma, non-zero saturation) is
    silently dropped. See `guides/iiif_conformance.md` in
    `image_plug`.

## Examples

    iex> alias Image.Plug.Pipeline
    iex> alias Image.Plug.Pipeline.Ops
    iex> p = %Pipeline{ops: [%Ops.Resize{width: 600}], output: %Ops.Format{type: :webp, quality: 80}}
    iex> Image.Components.URL.cloudflare(p, source_path: "/sample.jpg")
    "/cdn-cgi/image/width=600,format=webp,quality=80/sample.jpg"

# `options`

```elixir
@type options() :: keyword()
```

Per-builder options, shared across the four projectors.

  * `:source_path` — the URL-relative source path. Cloudflare,
    Cloudinary, and ImageKit append this after the options
    segment; imgix prepends it before the query string.
    Defaults to `"/sample.jpg"`.

  * `:host` — when supplied, prepended verbatim (e.g.
    `"https://playground.example.com"` or just
    `"/img"` to scope under a path). Default `""` — relative
    URL.

  * `:cloudinary_account` — Cloudinary's `<cloud-name>`
    segment. Default `"demo"`.

  * `:imagekit_endpoint` — ImageKit's per-account endpoint
    segment. Default `"demo"`.

# `cloudflare`

```elixir
@spec cloudflare(Image.Plug.Pipeline.t(), options()) :: String.t()
```

Projects the pipeline onto the Cloudflare Images URL grammar: `<host>/cdn-cgi/image/<options>/<source>`.

### Arguments

* `pipeline` is an `Image.Plug.Pipeline.t()`.

* `options` is a keyword list — see the Options section.

### Options

* `:source_path` is the URL-relative source path. Defaults to `"/sample.jpg"`.

* `:host` is prepended verbatim — e.g. `"https://playground.example.com"` or `"/img"` to scope under a path. Defaults to `""` (relative URL).

### Returns

* The projected URL as a string. Cloudflare requires at least one option; when the pipeline projects to nothing, `format=auto` (a no-op) is emitted.

### Examples

    iex> alias Image.Plug.Pipeline
    iex> alias Image.Plug.Pipeline.Ops
    iex> p = %Pipeline{ops: [%Ops.Resize{width: 200}], output: nil}
    iex> Image.Components.URL.cloudflare(p, source_path: "/abc.jpg")
    "/cdn-cgi/image/width=200/abc.jpg"

# `cloudinary`

```elixir
@spec cloudinary(Image.Plug.Pipeline.t(), options()) :: String.t()
```

Projects the pipeline onto the Cloudinary URL grammar: `<host>/<account>/image/upload/<options>/<source>`.

### Arguments

* `pipeline` is an `Image.Plug.Pipeline.t()`.

* `options` is a keyword list — see the Options section.

### Options

* `:source_path` is the URL-relative source path. Defaults to `"/sample.jpg"`.

* `:host` is prepended verbatim. Defaults to `""` (relative URL).

* `:cloudinary_account` is the `<cloud-name>` segment. Defaults to `"demo"`.

### Returns

* The projected URL as a string. When the pipeline projects to no options, the `tr:` segment is omitted and the URL collapses to `<host>/<account>/image/upload/<source>`.

### Examples

    iex> alias Image.Plug.Pipeline
    iex> alias Image.Plug.Pipeline.Ops
    iex> p = %Pipeline{ops: [%Ops.Resize{width: 200}], output: nil}
    iex> Image.Components.URL.cloudinary(p, source_path: "/abc.jpg")
    "/demo/image/upload/w_200/abc.jpg"

# `iiif`

```elixir
@spec iiif(Image.Plug.Pipeline.t(), options()) :: String.t()
```

Projects the pipeline onto the [IIIF Image API 3.0](https://iiif.io/api/image/3.0/) URL grammar: `<host><prefix>/<identifier>/<region>/<size>/<rotation>/<quality>.<format>`.

### Arguments

* `pipeline` is an `Image.Plug.Pipeline.t()`.

* `options` is a keyword list — see the Options section.

### Options

* `:source_path` is the URL-relative source path used as the IIIF identifier. Leading `/` is stripped; embedded `/` characters are percent-encoded as `%2F` per the spec. Defaults to `"/sample.jpg"`.

* `:host` is prepended verbatim — e.g. `"https://iiif.example.org"` or `""` for a relative URL.

* `:iiif_prefix` is the server's IIIF version prefix (the `/{prefix}` segment in the spec). Typical values: `"/iiif/3"` for an Image API 3.0 server, `"/cantaloupe/iiif/3"` for Cantaloupe deployments. Defaults to `"/iiif/3"`.

* `:iiif_format` is the format extension used when the pipeline's output `Format.type` is `:auto` (IIIF requires an explicit format in the URL). Defaults to `:jpeg`.

### Returns

* The projected URL as a string. The five positional segments are always emitted: `region`, `size`, `rotation`, `quality.format`. A pipeline with no transforms produces `<host>/iiif/3/<id>/full/max/0/default.jpg`.

### Conformance gaps

IIIF's URL grammar is narrower than the IR. The following ops project to `default` / `max` / `full` rather than the requested transform — see `guides/iiif.md` (in `image_plug`) for the per-op detail:

  * `Resize{fit: :cover}` — IIIF cannot express "scale-to-fill plus centred crop" in one URL. Drop and use `:contain` or `:squeeze` instead, or supply an explicit `Crop` op for the region you want.
  * `Blur`, `Sharpen`, `Vignette`, `Tint`, `Background`, non-grayscale `Adjust`, `face_zoom`, `gravity` — silently dropped. The IIIF spec scopes to a small set of geometric and quality transforms; effects are out of scope.

### Examples

    iex> alias Image.Plug.Pipeline
    iex> alias Image.Plug.Pipeline.Ops
    iex> p = %Pipeline{ops: [%Ops.Resize{width: 600}], output: %Ops.Format{type: :jpeg, quality: 80}}
    iex> Image.Components.URL.iiif(p, source_path: "/cat.jpg", host: "https://iiif.example.org")
    "https://iiif.example.org/iiif/3/cat.jpg/full/^600,/0/default.jpg"

    iex> alias Image.Plug.Pipeline
    iex> Image.Components.URL.iiif(%Pipeline{ops: [], output: nil}, source_path: "/cat.jpg")
    "/iiif/3/cat.jpg/full/max/0/default.jpg"

# `iiif_info_url`

```elixir
@spec iiif_info_url(options()) :: String.t()
```

Builds the URL of an IIIF identifier's [`info.json` discovery
document](https://iiif.io/api/image/3.0/#5-image-information).

This is the entry point a deep-zoom viewer (OpenSeadragon, Mirador,
Leaflet-IIIF) reads to discover the source dimensions, available
tile sizes, supported features, and rendering profile. It is also
the canonical IIIF identity URL — the same URL appears as `id` in
the `info.json` body.

### Arguments

* `options` is a keyword list — see the Options section.

### Options

* `:source_path` is the URL-relative source path used as the IIIF
  identifier. Default `"/sample.jpg"`.

* `:host` is prepended verbatim. Default `""`.

* `:iiif_prefix` is the server's IIIF version prefix. Default
  `"/iiif/3"`.

### Returns

* The URL of the `info.json` document as a string.

### Examples

    iex> Image.Components.URL.iiif_info_url(source_path: "/cat.jpg", host: "https://iiif.example.org")
    "https://iiif.example.org/iiif/3/cat.jpg/info.json"

    iex> Image.Components.URL.iiif_info_url(source_path: "/sub/cat.jpg")
    "/iiif/3/sub%2Fcat.jpg/info.json"

# `imagekit`

```elixir
@spec imagekit(Image.Plug.Pipeline.t(), options()) :: String.t()
```

Projects the pipeline onto the ImageKit URL grammar: `<host>/<endpoint>/tr:<options>/<source>`.

### Arguments

* `pipeline` is an `Image.Plug.Pipeline.t()`.

* `options` is a keyword list — see the Options section.

### Options

* `:source_path` is the URL-relative source path. Defaults to `"/sample.jpg"`.

* `:host` is prepended verbatim. Defaults to `""` (relative URL).

* `:imagekit_endpoint` is the per-account endpoint segment. Defaults to `"demo"`.

### Returns

* The projected URL as a string. When the pipeline projects to no options, the `tr:` segment is omitted and the URL collapses to `<host>/<endpoint>/<source>`.

### Examples

    iex> alias Image.Plug.Pipeline
    iex> alias Image.Plug.Pipeline.Ops
    iex> p = %Pipeline{ops: [%Ops.Resize{width: 200}], output: nil}
    iex> Image.Components.URL.imagekit(p, source_path: "/abc.jpg")
    "/demo/tr:w-200/abc.jpg"

# `imgix`

```elixir
@spec imgix(Image.Plug.Pipeline.t(), options()) :: String.t()
```

Projects the pipeline onto the imgix URL grammar: `<host>/<source>?<options>`.

### Arguments

* `pipeline` is an `Image.Plug.Pipeline.t()`.

* `options` is a keyword list — see the Options section.

### Options

* `:source_path` is the URL-relative source path. Defaults to `"/sample.jpg"`.

* `:host` is prepended verbatim. Defaults to `""` (relative URL).

### Returns

* The projected URL as a string. When the pipeline projects to no options, the `?…` query string is omitted and only `<host><source>` remains.

### Examples

    iex> alias Image.Plug.Pipeline
    iex> alias Image.Plug.Pipeline.Ops
    iex> p = %Pipeline{ops: [%Ops.Resize{width: 200}], output: nil}
    iex> Image.Components.URL.imgix(p, source_path: "/abc.jpg")
    "/abc.jpg?w=200"

---

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