# `Storehouse.Variants`
[🔗](https://github.com/hattoriai/storehouse/blob/v0.2.0/lib/storehouse/variants.ex#L1)

A file's other sizes and formats, by preset: a photograph's thumbnail,
its sizes for a `srcset`, its WebP. Only the presets' sizes are ever
made, so no one can have the server render at a size they choose.

    config :storehouse, Storehouse.Variants,
      presets: [
        thumb: [width: 320, height: 320, fit: :cover],
        photo: [widths: [640, 960, 1280, 1920, 2560], format: [:webp, :original]]
      ]

    Storehouse.variant_url(file, :thumb)
    #=> "/variants/2026/10/0b6…/harbour.jpg/thumb-320-5f3a91c2"
    Storehouse.variant_url(file, :photo, width: 1000)
    #=> "/variants/2026/10/0b6…/harbour.jpg/photo-1280-e04b77d1"

What renders them is a `Storehouse.Renderer`: `Storehouse.Renderer.Vix`
(libvips, in the app; the default when Vix is loaded), which stores each
variant beside its original once, served by `Storehouse.Variants.Plug`;
or `Storehouse.Renderer.Imgproxy`, whose URLs an imgproxy behind a CDN
renders; or the app's own. See the Variants guide.

## Configuration

  * `:presets` - a keyword list of presets, by name (an atom of
    lowercase letters, digits and `_`). See "Presets".
  * `:renderer` - a module of `Storehouse.Renderer`, or
    `{module, options}`. The default is `Storehouse.Renderer.Vix`, when
    Vix is loaded.
  * `:url` - where `Storehouse.Variants.Plug` serves variants, for a
    renderer without URLs of its own. The default is `"/variants"`.
  * `:prefix` - the prefix of the stored variants' keys:
    `"<prefix><original key>/<preset>-<width>-<fingerprint>.<extension>"`,
    so deleting an original deletes its variants with one prefix. The
    default is `"variants/"`.
  * `:timeout` - how long a caller waits for a variant another is making,
    in milliseconds. The default is 60 seconds.

Each can be given to a call, for an app with variants of several kinds.

## Presets

  * `:width` - the width, in pixels; or `:widths`, several, a ladder for
    a `srcset`. A width past the original's gives the original's: a
    variant is never enlarged.
  * `:height` - the height of the box, with `:width`; or `:aspect`,
    `{width, height}`, the box's proportions at every width. The default
    is any height.
  * `:fit` - `:inside` (the default: scaled to fit the box) or `:cover`
    (cropped to fill it, which needs a height).
  * `:focus` - what a crop keeps: `:centre` (the default), `:attention`,
    `:entropy` or `{x, y}`, a point in fractions from the top left.
  * `:format` - `:original` (the default: the original's, or JPEG for a
    format browsers do not show), `:webp`, `:jpeg`, `:png`, `:avif`,
    `:gif`; or a list, in order, of which the first the browser takes is
    given, by its `Accept`, the last to every browser: `[:webp, :original]`.
  * `:quality` - 1 to 100. The default is the renderer's.
  * `:profile` - `:srgb` (the default: converted to sRGB) or `:keep`
    (the photograph's colour profile kept, for wide-gamut screens).
  * `:metadata` - `:none` (the default: none of the original's EXIF,
    where it was taken least of all) or `:all`.
  * `:background` - `"#rrggbb"`, under transparency a format without it
    cannot keep. The default is white.
  * `:options` - a renderer's own options, by renderer:
    `options: [imgproxy: "sh:0.3"]`.

A preset changed gives its variants new URLs and keys: a CDN's copy, kept
for a year, is never of an older preset. The variants of the old one stay
until their original is deleted, or the prefix is.

# `get`

```elixir
@spec get(Storehouse.File.t() | String.t(), atom(), keyword()) ::
  {:ok, Storehouse.File.t()} | {:error, term()}
```

`file`'s variant of `preset`, found or made: `{:ok, %Storehouse.File{}}`.

Made once, however many ask for it at the same time, across a cluster's
nodes: the first renders it, the others wait for it
(`{:error, :timeout}` past `:timeout`). Made with a renderer that renders
(`c:Storehouse.Renderer.render/4`); one of URLs only gives
`{:error, :not_supported}`. A missing original gives `{:error, :enoent}`.

Its `:width` and `:height` are known when it was made by this call.

## Options

  * `:width` - as for `url/3`.
  * `:accept` - the browser's `Accept` header, for a preset of several
    formats; or `:format`, one of them. The default is the last.
  * `:adapter` - the store, of the original and of its variants.
  * The configuration's, for this call.

# `negotiate`

```elixir
@spec negotiate([atom()], String.t() | nil) :: atom()
```

The format of a preset's `formats` a browser is given, by its `Accept`
header: the first it takes, else the last.

    iex> Storehouse.Variants.negotiate([:avif, :webp, :original], "image/webp,*/*")
    :webp
    iex> Storehouse.Variants.negotiate([:webp, :original], nil)
    :original

# `prefix`

```elixir
@spec prefix(String.t(), keyword()) :: String.t()
```

The prefix of the variants of the file with `key`, or of the files under
a prefix: deleting it deletes them.

# `url`

```elixir
@spec url(Storehouse.File.t() | String.t(), atom(), keyword()) :: String.t()
```

The URL of `file`'s variant of `preset`.

## Options

  * `:width` - the width wanted: the preset's that is as wide, or the
    next one up, or its widest. The default is its widest.
  * `:focus` - what a crop keeps, for this file (a photograph's own
    focus point): only with a renderer of URLs, which signs it.
  * The configuration's (`:presets`, `:renderer`, `:url`), for this call.

---

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