# `Color.Conversion.Oklab`

Pure math for the Oklab perceptual color space, published by
Björn Ottosson in 2020 (https://bottosson.github.io/posts/oklab/).

Oklab is defined relative to CIE XYZ under the D65 reference white
with `Y` on the `[0, 1]` scale. If you have an `XYZ` tagged with a
different illuminant, chromatically adapt it to D65 before using
these functions — they do not adapt automatically.

Note that Ottosson's published matrices use an implicit D65 of
approximately `(0.9505, 1.0, 1.0883)` — the `Z` component differs
from Lindbloom's published D65 (`1.08883`) by about `5 × 10⁻⁴`.
This is intrinsic to the Oklab matrices and not a numerical bug.
Round-tripping an `XYZ` through Oklab and back preserves the value
to roughly `1 × 10⁻⁷`.

The pipeline is:

    XYZ ──M1──▶ LMS ──∛──▶ LMS' ──M2──▶ Oklab

with `M1` the D65 Hunt-Pointer-Estevez-like matrix and `M2` the final
linear projection. The functions here are the inverse of each other
to double precision.

# `oklab_to_oklch`

Converts Oklab `{L, a, b}` to cylindrical Oklch `{L, C, h}`.

The hue `h` is returned in degrees in the range `[0, 360)`.

### Arguments

* `oklab` is an `{L, a, b}` tuple.

### Returns

* An `{L, C, h}` tuple.

### Examples

    iex> Color.Conversion.Oklab.oklab_to_oklch({0.5, 0.0, 0.0})
    {0.5, 0.0, 0.0}

# `oklab_to_xyz`

Converts an Oklab `{L, a, b}` triple to CIE `XYZ` (D65, `Y ∈ [0, 1]`).

### Arguments

* `oklab` is an `{L, a, b}` tuple.

### Returns

* An `{X, Y, Z}` tuple.

### Examples

    iex> {x, y, z} = Color.Conversion.Oklab.oklab_to_xyz({1.0, 0.0, 0.0})
    iex> {Float.round(x, 4), Float.round(y, 4), Float.round(z, 4)}
    {0.9505, 1.0, 1.0883}

# `oklch_to_oklab`

Converts cylindrical Oklch `{L, C, h}` to Oklab `{L, a, b}`.

### Arguments

* `oklch` is an `{L, C, h}` tuple where `h` is in degrees.

### Returns

* An `{L, a, b}` tuple.

### Examples

    iex> Color.Conversion.Oklab.oklch_to_oklab({0.5, 0.0, 0.0})
    {0.5, 0.0, 0.0}

# `xyz_to_oklab`

Converts a CIE `XYZ` triple (D65, `Y ∈ [0, 1]`) to an Oklab `{L, a, b}`.

### Arguments

* `xyz` is an `{X, Y, Z}` tuple relative to the D65 reference white.

### Returns

* An `{l, a, b}` tuple where `L` is perceptual lightness (0 for black,
  1 for the reference white) and `a`/`b` are green-red / blue-yellow
  opponent axes.

### Examples

    iex> {l, a, b} = Color.Conversion.Oklab.xyz_to_oklab({0.95047, 1.0, 1.08883})
    iex> {Float.round(l, 3), abs(a) < 1.0e-4, abs(b) < 1.0e-4}
    {1.0, true, true}

---

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