# `Scales.ColorOperations`
[🔗](https://github.com/giggleton/scales/blob/v0.1.0/lib/scales/color_operations.ex#L1)

Colours as a user writes them and as R's palettes make them: a colour read as CSS reads one
(`parse/1`); one made from a hue, chroma and luminance (`from_hcl/3`) or from CIE L*a*b*
(`from_lab/2`), and a colour's L*a*b* (`to_lab/1`) and alpha (`opacity/1`), each exactly as R's
farver makes it; two mixed (`mix/3`), R's `col_mix()`; one changed, R's colour helpers: its
alpha set (`alpha/2`), its hue, chroma or luminance set (`with_hcl/2`, `muted/2`), made
lighter, darker or more saturated (`lighter/2`, `darker/2`, `saturate/2`) or its hue turned
(`shift/2`); and the viridis palettes' colours (`viridis/2`), R's `viridisLite::viridis()`.

A colour is uppercase `#RRGGBB`, or `#RRGGBBAA` with an alpha of its own, an alpha of `FF`
dropped as opaque, so a colour has one spelling; or `:none`, R's `NA`, no colour. A helper
takes one colour, as `mix/3` takes two, and gives no colour back for none, as R gives `NA`.

# `t`

```elixir
@type t() :: String.t() | :none
```

A colour: uppercase `#RRGGBB`, or `#RRGGBBAA` with an alpha of its own, or `:none`.

# `alpha`

```elixir
@spec alpha(t(), number() | nil) :: t()
```

The colour with its alpha set to `alpha`, from 0 to 1, R's `alpha()`: one below 0 is 0 and one
above 1 is 1, as farver writes the byte; `nil`, R's `NA`, keeps the colour's own; no colour
stays none. An alpha whose byte is 1 is written `01`, as R writes it for several colours, where
R writes one colour opaque.

## Examples

    iex> Scales.ColorOperations.alpha("#4682B4", 0.5)
    "#4682B480"

    iex> Scales.ColorOperations.alpha("#4682B480", nil)
    "#4682B480"

# `darker`

```elixir
@spec darker(t(), number()) :: t()
```

The colour `amount` darker, R's `col_darker()`: `lighter/2` by minus `amount`.

## Examples

    iex> Scales.ColorOperations.darker("#4682B4")
    "#38678F"

# `from_hcl`

```elixir
@spec from_hcl(number(), number(), number()) :: String.t()
```

The colour, uppercase `#RRGGBB`, that R's farver encodes from `hue` in degrees, `chroma` and
`luminance`, CIE LCh(uv), as R's `pal_hue()` makes its colours: each first taken into farver's
bounds as farver caps it, a hue outside 0 to 360 and a luminance outside 0 to 100 at the nearer
end, a chroma below 0 at 0.

## Examples

    iex> Scales.ColorOperations.from_hcl(15.0, 100.0, 65.0)
    "#F8766D"

    iex> Scales.ColorOperations.from_hcl(15.0, -40.0, 65.0)
    "#9E9E9E"

# `from_lab`

```elixir
@spec from_lab({number(), number(), number()}, number()) :: String.t()
```

The colour that R's farver encodes from CIE L*, a* and b* and an `alpha` from 0 to 1, as R's
gradients blend their colours in L*a*b*: uppercase `#RRGGBB`, its alpha's byte appended unless
it is opaque, `alpha` times 255 rounded half to even.

## Examples

    iex> Scales.ColorOperations.from_lab({52.4664, -4.0763, -32.1991})
    "#4682B4"

    iex> Scales.ColorOperations.from_lab({52.4664, -4.0763, -32.1991}, 0.5)
    "#4682B480"

# `lighter`

```elixir
@spec lighter(t(), number()) :: t()
```

The colour `amount` lighter, R's `col_lighter()`: its HSL lightness, from 0 to 100, raised by
`amount` and taken into 0 to 100, its alpha kept; no colour stays none. The library's HSL,
which R's farver works out in another order, lands a byte away from R's where a channel falls
exactly between two, as the module's opening comment says.

## Examples

    iex> Scales.ColorOperations.lighter("#4682B4")
    "#699BC4"

    iex> Scales.ColorOperations.lighter("#4682B480", 200)
    "#FFFFFF80"

# `mix`

```elixir
@spec mix(String.t(), String.t(), number()) :: String.t()
```

The colour `amount` of the way from `from` to `to`, each in its one spelling, R's `col_mix()`:
each of red, green, blue and alpha that share of the way from the one to the other, rounded to
a byte; 0 is `from` and 1 is `to`.

## Examples

    iex> Scales.ColorOperations.mix("#000000", "#FFFFFF", 0.5)
    "#808080"

# `muted`

```elixir
@spec muted(t(), keyword()) :: t()
```

The colour of the same hue at another chroma and luminance, R's `muted()`: `with_hcl/2` with
the chroma and luminance given, its own alpha dropped, as R drops it; no colour stays none.

## Options

* `:luminance` - The luminance, from 0, black, to 100, white; one outside them is the nearer. The default value is `30`.

* `:chroma` - The chroma, from 0, grey; one below 0 is 0. The default value is `70`.

## Examples

    iex> Scales.ColorOperations.muted("#FF0000")
    "#832424"

    iex> Scales.ColorOperations.muted("#4682B480", luminance: 60, chroma: 40)
    "#7094BA"

# `opacity`

```elixir
@spec opacity(String.t()) :: float()
```

A colour's own alpha, from 0 to 1, its byte over 255 as R's farver decodes it; 1 for a colour
without one.

## Examples

    iex> Scales.ColorOperations.opacity("#4682B4")
    1.0

    iex> Scales.ColorOperations.opacity("#FFFFFF00")
    0.0

# `parse`

```elixir
@spec parse(term()) :: {:ok, t()} | :error
```

The colour `color` names, in its one spelling, or `:error` where CSS names none: a CSS colour
name in any case; a hex colour in one of CSS's four forms, `#RGB`, `#RGBA`, `#RRGGBB` and
`#RRGGBBAA`, its digits in either case; `transparent`, R's white with no alpha, `#FFFFFF00`,
where CSS's is black; or `:none`.
R's own names that CSS lacks (`grey50`), CSS's functions (`rgb()`) and hex without its `#` are
refused.

## Examples

    iex> Scales.ColorOperations.parse("SteelBlue")
    {:ok, "#4682B4"}

    iex> Scales.ColorOperations.parse("#abc")
    {:ok, "#AABBCC"}

    iex> Scales.ColorOperations.parse("grey50")
    :error

# `saturate`

```elixir
@spec saturate(t(), number()) :: t()
```

The colour `amount` more saturated, R's `col_saturate()`: its HSL saturation, from 0 to 100,
raised by `amount` and taken into 0 to 100, its alpha kept; no colour stays none. A byte may
land away from R's as `lighter/2` says.

## Examples

    iex> Scales.ColorOperations.saturate("#4682B4")
    "#3983C1"

    iex> Scales.ColorOperations.saturate("#4682B4", -200)
    "#7D7D7D"

# `shift`

```elixir
@spec shift(t(), number() | nil) :: t()
```

The colour with its hue turned `amount` degrees round the circle, R's `col_shift()`: its hue,
as farver decodes it, plus `amount`, taken modulo 360 as R's `%%` takes it, its chroma,
luminance and alpha kept; `nil`, R's `NA`, gives none, and no colour stays none.

## Examples

    iex> Scales.ColorOperations.shift("#4682B4")
    "#5E7DB7"

    iex> Scales.ColorOperations.shift("#4682B4", -30)
    "#008CA2"

# `to_lab`

```elixir
@spec to_lab(String.t()) :: {float(), float(), float()}
```

The CIE L*a*b* of a `#RRGGBB` or `#RRGGBBAA` colour, as R's farver decodes it; an alpha is not
part of it (`opacity/1`).

## Examples

    iex> Scales.ColorOperations.to_lab("#4682B4")
    {52.46551718768575, -4.073239774469551, -32.18862480122369}

# `viridis`

```elixir
@spec viridis(non_neg_integer(), %{
  option: atom(),
  begin: number(),
  end: number(),
  direction: 1 | -1,
  alpha: number()
}) :: [t()]
```

`count` colours of the viridis palette `option`, one of `viridis_options/0`, as R's
`viridisLite::viridis()` makes them: spaced from `begin` to `end`, each from 0 to 1, in that
order for a `direction` of 1 and the other for -1, with an alpha from 0 to 1.

## Examples

    iex> Scales.ColorOperations.viridis(3, %{option: :viridis, begin: 0.0, end: 1.0, direction: 1, alpha: 1.0})
    ["#440154", "#21908C", "#FDE725"]

# `viridis_options`

```elixir
@spec viridis_options() :: [atom(), ...]
```

viridisLite's palettes by name, in the order of its options A to H.

## Examples

    iex> Scales.ColorOperations.viridis_options()
    [:magma, :inferno, :plasma, :viridis, :cividis, :rocket, :mako, :turbo]

# `with_hcl`

```elixir
@spec with_hcl(t(), keyword()) :: t()
```

The colour with its hue, chroma or luminance set, R's `col2hcl()`: the colour's own, as farver
decodes it, where an option is not given; each taken into farver's bounds as `from_hcl/3`
says. The colour's own alpha is dropped, as R drops it, unless `alpha:` sets one, from 0 to 1,
as `alpha/2` does; no colour stays none.

Black's hue is 180 degrees, as farver decodes it.

## Options

* `:hue` - The hue in degrees; one outside 0 to 360 is the nearer end.

* `:chroma` - The chroma, from 0, grey; one below 0 is 0.

* `:luminance` - The luminance, from 0, black, to 100, white; one outside them is the nearer.

* `:alpha` - An alpha from 0 to 1, as `alpha/2` takes it; `nil`, or none given, writes the colour opaque.

## Examples

    iex> Scales.ColorOperations.with_hcl("#4682B4", hue: 320)
    "#B162A0"

    iex> Scales.ColorOperations.with_hcl("#4682B480", [])
    "#4682B4"

    iex> Scales.ColorOperations.with_hcl("#4682B4", luminance: 120)
    "#D4FFFF"

---

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