# `Macaw.Layers.Color`
[🔗](https://github.com/dcoai/macaw_layers/blob/v0.2.2/lib/macaw/layers/color.ex#L1)

Colors (spec/04 §1): parsing, construction in RGB, HSL, Lab, HCL, OKLab and OKLCH,
conversion, manipulation and interpolation.

    color = Macaw.Layers.Color.parse("steelblue")
    color |> Macaw.Layers.Color.darker() |> Macaw.Layers.Color.to_hex()
    #=> "#044279"

`parse/1` reads untrusted text and answers `nil` for anything it does not read; it never
raises. Lab is CIE L\*a\*b\* against D50 with d3-color's constants, so `lab/4` of
`to_lab/1` is the color again; OKLab is Ottosson's, and a color built from OKLab or
OKLCH is brought into sRGB by CSS Color 4's gamut mapping (`Macaw.Layers.Color.Ok`).
`Macaw.Layers.Color.Vision` simulates the dichromacies.

Ported from visualize's `Visualize.Color` and `Visualize.Color.OK`.

# `t`

```elixir
@type t() :: %Macaw.Layers.Color{
  b: number(),
  g: number(),
  opacity: number(),
  r: number()
}
```

# `value`

```elixir
@type value() ::
  String.t()
  | {:color, atom()}
  | {:series, pos_integer()}
  | {:ramp, atom(), number()}
  | {:intensity | :alpha | :lighten | :darken | :saturate | :shade, value(),
     number()}
  | {:blend, [value(), ...]}
  | {:contrast, value() | :under}
```

A color as a design names it (spec/04 §1.3): a name of the theme's color table, a series
or a ramp position, a literal string, or a transform of colors.

# `alpha`

```elixir
@spec alpha(value(), number()) :: value()
```

The color `c` at opacity `a` in `0..1`, whatever its opacity was (spec/04 §1.3).

    iex> Macaw.Layers.Color.color(:red) |> Macaw.Layers.Color.alpha(0.5)
    {:alpha, {:color, :red}, 0.5}

# `blend`

```elixir
@spec blend([value(), ...]) :: value()
```

The colors mixed in OKLab, each weighted by its `intensity/2` (1 without one), as CSS's
`color-mix` mixes them: weights summing below 1 leave the remainder transparent, and
above 1 are scaled to sum to 1 (spec/04 §1.3).

    iex> import Macaw.Layers.Color, only: [color: 1, intensity: 2, blend: 1, alpha: 2]
    iex> [color(:blue) |> intensity(0.65), color(:red) |> intensity(0.3)] |> blend() |> alpha(0.65)
    {:alpha, {:blend, [{:intensity, {:color, :blue}, 0.65}, {:intensity, {:color, :red}, 0.3}]}, 0.65}

# `brighter`

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

Returns a brighter copy of the color.

k=1 is one "unit" brighter (default). k=2 is two units, etc.

# `color`

```elixir
@spec color(atom() | String.t()) :: value()
```

The color `name` of the theme's color table (spec/04 §1.3, §2.2): the library's names are
atoms, and a color a theme declares may be named by its string.

    iex> Macaw.Layers.Color.color(:red)
    {:color, :red}

# `contrast`

```elixir
@spec contrast() :: value()
```

The readable ink over whatever the text is drawn on (spec/04 §3.2): a mark's inline label
inks against its element's fill, any other text against the theme's background.

    iex> Macaw.Layers.Color.contrast()
    {:contrast, :under}

# `contrast`

```elixir
@spec contrast(value()) :: value()
```

The readable ink over `c`: the theme's text or background, else black or white,
whichever contrasts more (spec/04 §1.4, §3.2).

    iex> Macaw.Layers.Color.color(:bad) |> Macaw.Layers.Color.contrast()
    {:contrast, {:color, :bad}}

# `contrast`

```elixir
@spec contrast(t(), t()) :: float()
```

Returns the contrast ratio between two colors (1-21)

# `darken`

```elixir
@spec darken(value(), number()) :: value()
```

The color `c` moved toward black by `t` in `0..1`, in OKLCH lightness (spec/04 §1.3).

    iex> Macaw.Layers.Color.color(:red) |> Macaw.Layers.Color.darken(0.3)
    {:darken, {:color, :red}, 0.3}

# `darker`

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

Returns a darker copy of the color.

k=1 is one "unit" darker (default). k=2 is two units, etc.

# `delta_eok`

```elixir
@spec delta_eok(t(), t()) :: float()
```

ΔEOK, the distance between two colors in OKLab, their opacities aside.

    iex> Macaw.Layers.Color.delta_eok(Macaw.Layers.Color.parse("#fff"), Macaw.Layers.Color.parse("#000"))
    ...> |> Float.round(6)
    1.0

# `desaturate`

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

Returns a desaturated copy (in HSL space)

# `grayscale`

```elixir
@spec grayscale(t()) :: t()
```

Returns a grayscale version of the color

# `hcl`

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

Creates a color from HCL values (h: 0-360, c: 0-~230, l: 0-100)

# `hsl`

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

Creates a color from HSL values (h: 0-360, s: 0-100, l: 0-100)

# `intensity`

```elixir
@spec intensity(value(), number()) :: value()
```

How much of the color `c` is present, `t` in `0..1`; the rest is transparent. On its own
it is `c` at `t` of its alpha; in `blend/1` it is the color's weight (spec/04 §1.3).

    iex> Macaw.Layers.Color.color(:blue) |> Macaw.Layers.Color.intensity(0.65)
    {:intensity, {:color, :blue}, 0.65}

# `interpolate_hcl`

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

Interpolates between two colors in HCL space (perceptually uniform)

# `interpolate_hsl`

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

Interpolates between two colors in HSL space

# `interpolate_lab`

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

Interpolates between two colors in Lab space

# `interpolate_rgb`

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

Interpolates between two colors in RGB space

# `invert`

```elixir
@spec invert(t()) :: t()
```

Inverts the color

# `lab`

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

Creates a color from Lab values (l: 0-100, a: -128 to 127, b: -128 to 127)

# `lighten`

```elixir
@spec lighten(value(), number()) :: value()
```

The color `c` moved toward white by `t` in `0..1`, in OKLCH lightness (spec/04 §1.3).

    iex> Macaw.Layers.Color.color(:red) |> Macaw.Layers.Color.lighten(0.3)
    {:lighten, {:color, :red}, 0.3}

# `luminance`

```elixir
@spec luminance(t()) :: float()
```

Returns the relative luminance (0-1)

# `mix`

```elixir
@spec mix(t(), t()) :: t()
```

Mixes two colors equally (in RGB space)

# `named`

```elixir
@spec named(String.t()) :: String.t() | nil
```

Gets a named color's hex value

# `named_colors`

```elixir
@spec named_colors() :: [String.t()]
```

Returns all named color names

# `oklab`

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

A color from OKLab, brought into sRGB by CSS Color 4's gamut mapping (spec/04 §1.2).
Channels are left unrounded.

# `oklch`

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

A color from OKLCH (`h` in degrees), gamut-mapped as `oklab/4` is.

# `parse`

```elixir
@spec parse(term()) :: t() | nil
```

Parses a color string into a Color struct.

Supports:
- Hex: "#rgb", "#rrggbb", "#rgba", "#rrggbbaa"
- RGB: "rgb(r, g, b)", "rgba(r, g, b, a)"
- HSL: "hsl(h, s%, l%)", "hsla(h, s%, l%, a)"
- Named colors: "steelblue", "red", etc.

# `ramp`

```elixir
@spec ramp(atom(), number()) :: value()
```

The color at `t` in `0..1` along the theme's ramp `name` (spec/04 §2.3).

    iex> Macaw.Layers.Color.ramp(:thermal, 0.7)
    {:ramp, :thermal, 0.7}

# `resolve`

```elixir
@spec resolve(value() | atom(), Macaw.Layers.Theme.t(), Macaw.Layers.Theme.mode()) ::
  String.t()
```

A color value resolved against a theme in `mode` (spec/04 §1.4, §3.2): a name, a series
or a ramp position exactly as `Macaw.Layers.Theme.resolve/3` resolves the slot, a literal as
written, and a transform as the literal it computes to, `#rrggbb` or `#rrggbbaa`.

    iex> theme = Macaw.Layers.Theme.default()
    iex> Macaw.Layers.Color.resolve(Macaw.Layers.Color.color(:axis), theme, :css)
    "var(--mw-axis, #666666)"
    iex> Macaw.Layers.Color.resolve({:intensity, "#ff0000", 0.5}, theme, :literal)
    "#ff000080"

# `rgb`

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

Creates a color from RGB values (0-255)

# `rotate`

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

Rotates the hue by the specified degrees

# `saturate`

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

On a `%Macaw.Layers.Color{}`, a saturated copy: HSL saturation multiplied by `amount`
(default `1.2`).

On a color value (spec/04 §1.3), the transform `{:saturate, c, t}`: chroma moved toward
full in OKLCH by `t` in `0..1`, resolved against the theme.

    iex> Macaw.Layers.Color.color(:red) |> Macaw.Layers.Color.saturate(0.2)
    {:saturate, {:color, :red}, 0.2}

# `shade`

```elixir
@spec shade(value(), number()) :: value()
```

The step `n` of `c`'s scale, `n` in `0..1000` as designers number them (spec/04 §1.3):
500 is `c`, 0 near white and 1000 near black, on one OKLCH curve that keeps the hue and
eases the chroma toward the ends. A step the theme pins of a name is the pinned color.

    iex> Macaw.Layers.Color.color(:red) |> Macaw.Layers.Color.shade(100)
    {:shade, {:color, :red}, 100}

# `to_hcl`

```elixir
@spec to_hcl(t()) :: %{h: float(), c: float(), l: float(), opacity: float()}
```

Converts to HCL map %{h: h, c: c, l: l, opacity: opacity}

# `to_hex`

```elixir
@spec to_hex(t()) :: String.t()
```

Converts to hex string

# `to_hsl`

```elixir
@spec to_hsl(t()) :: %{h: float(), s: float(), l: float(), opacity: float()}
```

Converts to HSL map %{h: h, s: s, l: l, opacity: a}

# `to_lab`

```elixir
@spec to_lab(t()) :: %{l: float(), a: float(), b: float(), opacity: float()}
```

Converts to Lab map %{l: l, a: a, b: b, opacity: opacity}

# `to_oklab`

```elixir
@spec to_oklab(t()) :: %{l: float(), a: float(), b: float(), opacity: number()}
```

The color in OKLab (spec/04 §1.2): `%{l, a, b, opacity}`, `l` in `0..1`.

    iex> %{l: l} = Macaw.Layers.Color.to_oklab(Macaw.Layers.Color.parse("#ffffff"))
    iex> Float.round(l, 4)
    1.0

# `to_oklch`

```elixir
@spec to_oklch(t()) :: %{l: float(), c: float(), h: float(), opacity: number()}
```

The color in OKLCH (spec/04 §1.2): `%{l, c, h, opacity}`, `h` in degrees `[0, 360)`.

# `to_rgb`

```elixir
@spec to_rgb(t()) :: {integer(), integer(), integer()}
```

Converts to RGB tuple {r, g, b}

# `to_rgba`

```elixir
@spec to_rgba(t()) :: {integer(), integer(), integer(), float()}
```

Converts to RGBA tuple {r, g, b, a}

# `to_string`

```elixir
@spec to_string(t()) :: String.t()
```

Converts to CSS string

# `value?`

```elixir
@spec value?(term()) :: boolean()
```

Whether a term is a color value (spec/04 §1.3), every operand of a transform included.

    iex> Macaw.Layers.Color.value?({:alpha, {:color, :red}, 0.5})
    true
    iex> Macaw.Layers.Color.value?(:red)
    false

# `with_opacity`

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

Returns a copy with the specified opacity

---

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