# `Color.Conversion.Lindbloom`

Color space conversion functions based on the formulas published by
Bruce Lindbloom at http://www.brucelindbloom.com/index.html?Math.html.

All functions operate on plain tuples/lists of floats so they can be
composed freely. Reference whites (`wr`) are supplied as `{xr, yr, zr}`
tuples in the same scale as the `XYZ` values (typically `Y = 1.0` or
`Y = 100.0`; both work as long as the inputs are consistent).

The CIE constants `ε` and `κ` are used in their exact rational form as
recommended by Lindbloom, rather than the rounded values found in many
older references.

### Constants

* `ε = 216/24389 ≈ 0.008856`.

* `κ = 24389/27 ≈ 903.2963`.

# `constants`

Returns the CIE constants `ε` and `κ` used by the conversions.

### Returns

* A `{epsilon, kappa}` tuple of floats.

### Examples

    iex> {e, k} = Color.Conversion.Lindbloom.constants()
    iex> Float.round(e, 6)
    0.008856
    iex> Float.round(k, 4)
    903.2963

# `gamma_compand`

Applies simple gamma companding (linear → non-linear).

### Arguments

* `v` is a linear channel value.

* `gamma` is the gamma exponent (for example `2.2`).

# `gamma_inverse_compand`

Inverts simple gamma companding (non-linear → linear).

### Arguments

* `v` is a companded channel value.

* `gamma` is the gamma exponent.

# `hlg_compand`

Applies the Hybrid Log-Gamma inverse EOTF
(linear scene-referred light → HLG signal).

Input and output are both in `[0, 1]`. The HLG curve is gamma at the
dark end and logarithmic at the bright end, meeting at `E = 1/12`.

### Arguments

* `v` is a linear channel value in `[0, 1]`.

# `hlg_inverse_compand`

Applies the HLG EOTF (HLG signal → linear scene-referred light).

### Arguments

* `v` is an HLG-encoded channel value in `[0, 1]`.

# `invert3`

Inverts a 3x3 matrix represented as a list of rows. Used to derive the
XYZ→RGB matrix from the RGB→XYZ matrix.

### Arguments

* `m` is a 3x3 matrix as a list of three three-element rows.

### Returns

* The inverse matrix in the same shape.

# `l_star_compand`

Applies the L* companding function (linear → L*).

### Arguments

* `v` is a linear channel value in `[0, 1]`.

# `l_star_inverse_compand`

Inverts the L* companding function (L* → linear).

### Arguments

* `v` is an L*-companded channel value.

# `lab_to_lchab`

Converts CIE `L*a*b*` to cylindrical `LCHab`.

### Arguments

* `lab` is an `{l, a, b}` tuple.

### Returns

* An `{l, c, h}` tuple where `h` is in degrees in the range `[0, 360)`.

### Examples

    iex> Color.Conversion.Lindbloom.lab_to_lchab({50.0, 0.0, 0.0})
    {50.0, 0.0, 0.0}

# `lab_to_xyz`

Converts CIE `L*a*b*` to a CIE `XYZ` triple.

### Arguments

* `lab` is an `{l, a, b}` tuple.

* `wr` is the `{Xr, Yr, Zr}` reference white tuple.

### Returns

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

### Examples

    iex> {x, y, z} = Color.Conversion.Lindbloom.lab_to_xyz({100.0, 0.0, 0.0}, {0.95047, 1.0, 1.08883})
    iex> {Float.round(x, 5), Float.round(y, 5), Float.round(z, 5)}
    {0.95047, 1.0, 1.08883}

# `lchab_to_lab`

Converts cylindrical `LCHab` to CIE `L*a*b*`.

### Arguments

* `lch` is an `{l, c, h}` tuple with `h` in degrees.

### Returns

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

### Examples

    iex> Color.Conversion.Lindbloom.lchab_to_lab({50.0, 0.0, 0.0})
    {50.0, 0.0, 0.0}

# `lchuv_to_luv`

Converts cylindrical `LCHuv` to CIE `L*u*v*`.

### Arguments

* `lch` is an `{l, c, h}` tuple with `h` in degrees.

### Returns

* An `{l, u, v}` tuple.

### Examples

    iex> Color.Conversion.Lindbloom.lchuv_to_luv({50.0, 0.0, 0.0})
    {50.0, 0.0, 0.0}

# `luv_to_lchuv`

Converts CIE `L*u*v*` to cylindrical `LCHuv`.

### Arguments

* `luv` is an `{l, u, v}` tuple.

### Returns

* An `{l, c, h}` tuple where `h` is in degrees in the range `[0, 360)`.

### Examples

    iex> Color.Conversion.Lindbloom.luv_to_lchuv({50.0, 0.0, 0.0})
    {50.0, 0.0, 0.0}

# `luv_to_xyz`

Converts CIE `L*u*v*` to a CIE `XYZ` triple.

### Arguments

* `luv` is an `{l, u, v}` tuple.

* `wr` is the `{Xr, Yr, Zr}` reference white tuple.

### Returns

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

### Examples

    iex> Color.Conversion.Lindbloom.luv_to_xyz({0.0, 0.0, 0.0}, {0.95047, 1.0, 1.08883})
    {0.0, 0.0, 0.0}

# `matmul3`

Multiplies two 3x3 matrices given as lists of rows.

### Arguments

* `a` is a 3x3 matrix as a list of three three-element rows.

* `b` is a 3x3 matrix in the same shape.

### Returns

* The product `a · b` in the same shape.

# `pq_compand`

Applies the SMPTE ST 2084 / BT.2100 PQ inverse EOTF
(linear luminance → non-linear PQ signal).

Input is absolute luminance in `[0, 1]` where `1.0` represents
10,000 cd/m². Output is the PQ-encoded signal in `[0, 1]`.

### Arguments

* `v` is a linear luminance value in `[0, 1]`.

# `pq_inverse_compand`

Applies the SMPTE ST 2084 / BT.2100 PQ EOTF
(PQ signal → linear luminance in `[0, 1]` where `1.0` = 10,000 cd/m²).

### Arguments

* `v` is a PQ-encoded value in `[0, 1]`.

# `rec709_compand`

Applies the ITU-R BT.709 opto-electronic transfer function
(linear → non-linear).

BT.709 uses the same shape as the Rec. 2020 SDR curve but is defined
at 8/10-bit precision; this implementation matches both.

### Arguments

* `v` is a linear channel value in `[0, 1]`.

# `rec709_inverse_compand`

Inverts the BT.709 OETF (non-linear → linear).

### Arguments

* `v` is a non-linear BT.709 channel value in `[0, 1]`.

# `rec2020_compand`

Applies the ITU-R BT.2020 OETF at 12-bit precision
(linear → non-linear).

BT.2020 refines the BT.709 curve with higher-precision constants for
12-bit systems. At 10-bit precision BT.2020 is identical to BT.709.

### Arguments

* `v` is a linear channel value in `[0, 1]`.

# `rec2020_inverse_compand`

Inverts the BT.2020 12-bit OETF (non-linear → linear).

### Arguments

* `v` is a non-linear BT.2020 channel value in `[0, 1]`.

# `rgb_to_xyz`

Converts linear `RGB` to `XYZ` using a working-space matrix `m`.

Uses plain f64 arithmetic rather than `Nx`: for single-color 3x3 work
the BEAM's float ops are roughly 25x faster than `Nx` on the host
backend, and preserve double precision. `Nx` is still the right choice
for batched color operations elsewhere in this project.

### Arguments

* `rgb` is an `{r, g, b}` tuple of linear (companding-removed) values.

* `m` is the 3x3 RGB→XYZ matrix for the working space, as a list of
  three three-element rows.

### Returns

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

### Examples

    iex> m = [[0.4124564, 0.3575761, 0.1804375],
    ...>      [0.2126729, 0.7151522, 0.0721750],
    ...>      [0.0193339, 0.1191920, 0.9503041]]
    iex> {x, y, z} = Color.Conversion.Lindbloom.rgb_to_xyz({1.0, 1.0, 1.0}, m)
    iex> {Float.round(x, 4), Float.round(y, 4), Float.round(z, 4)}
    {0.9505, 1.0, 1.0888}

# `srgb_compand`

Applies the sRGB companding function (linear → sRGB).

### Arguments

* `v` is a linear channel value in `[0, 1]`.

# `srgb_inverse_compand`

Inverts the sRGB companding function (sRGB → linear).

### Arguments

* `v` is a companded sRGB channel value in `[0, 1]`.

# `working_space_matrix`

Computes the 3x3 RGB→XYZ matrix for an RGB working space from its
primary chromaticities and reference white, per Lindbloom.

Given the primaries `{xr, yr}`, `{xg, yg}`, `{xb, yb}` and the reference
white `{Xw, Yw, Zw}` the matrix is `[Sr·Xr Sg·Xg Sb·Xb; …]` where the
scale factors `S` are found by solving `M · [Sr Sg Sb]ᵀ = W`.

### Arguments

* `primaries` is a `{{xr, yr}, {xg, yg}, {xb, yb}}` tuple of chromaticities.

* `wr` is the `{Xw, Yw, Zw}` reference white.

### Returns

* The RGB→XYZ matrix as a list of three three-element rows.

# `xyy_to_xyz`

Converts `xyY` chromaticity coordinates to a CIE `XYZ` triple.

### Arguments

* `xyy` is an `{x, y, y_big}` tuple.

### Returns

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

### Examples

    iex> {x, y, z} = Color.Conversion.Lindbloom.xyy_to_xyz({0.3127, 0.3290, 1.0})
    iex> {Float.round(x, 5), Float.round(y, 5), Float.round(z, 5)}
    {0.95046, 1.0, 1.08906}

# `xyz_to_lab`

Converts a CIE `XYZ` triple to CIE `L*a*b*`.

### Arguments

* `xyz` is an `{X, Y, Z}` tuple.

* `wr` is the `{Xr, Yr, Zr}` reference white tuple.

### Returns

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

### Examples

    iex> Color.Conversion.Lindbloom.xyz_to_lab({0.95047, 1.0, 1.08883}, {0.95047, 1.0, 1.08883})
    {100.0, 0.0, 0.0}

# `xyz_to_luv`

Converts a CIE `XYZ` triple to CIE `L*u*v*`.

### Arguments

* `xyz` is an `{X, Y, Z}` tuple.

* `wr` is the `{Xr, Yr, Zr}` reference white tuple.

### Returns

* An `{l, u, v}` tuple.

### Examples

    iex> Color.Conversion.Lindbloom.xyz_to_luv({0.0, 0.0, 0.0}, {0.95047, 1.0, 1.08883})
    {0.0, 0.0, 0.0}

# `xyz_to_rgb`

Converts `XYZ` to linear `RGB` using the inverse working-space matrix `mi`.

### Arguments

* `xyz` is an `{X, Y, Z}` tuple.

* `mi` is the 3x3 XYZ→RGB (inverse) matrix, as a list of three
  three-element rows.

### Returns

* An `{r, g, b}` tuple of linear values.

# `xyz_to_xyy`

Converts a CIE `XYZ` triple to `xyY`.

When `X + Y + Z = 0` the chromaticity is taken from the reference white
as prescribed by Lindbloom.

### Arguments

* `xyz` is an `{x, y, z}` tuple.

* `wr` is the `{xr, yr, zr}` reference white used when `X + Y + Z = 0`.

### Returns

* An `{x, y, y_big}` tuple where `x` and `y` are chromaticity coordinates
  and `y_big` is the original `Y`.

### Examples

    iex> Color.Conversion.Lindbloom.xyz_to_xyy({0.5, 0.5, 0.5}, {0.95047, 1.0, 1.08883})
    {0.3333333333333333, 0.3333333333333333, 0.5}

    iex> {x, y, yy} = Color.Conversion.Lindbloom.xyz_to_xyy({0.95047, 1.0, 1.08883}, {0.95047, 1.0, 1.08883})
    iex> {Float.round(x, 5), Float.round(y, 5), Float.round(yy, 4)}
    {0.31273, 0.32902, 1.0}

---

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