# `Color.ICC.Profile`

Reader for ICC v2 / v4 **matrix profiles** — the form used by
`sRGB IEC61966-2.1.icc`, `Display P3.icc`, `AdobeRGB1998.icc`,
`Generic Lab Profile.icc`, and most camera and scanner profiles.

This is intentionally a small subset of the ICC specification:

* Only **input** profiles with the **RGB** colour space and the
  **XYZ** profile connection space are supported.

* Only the matrix-TRC tag set is read: `rXYZ`, `gXYZ`, `bXYZ`,
  `rTRC`, `gTRC`, `bTRC`, plus `wtpt` and `desc`.

* `curv` (LUT) and `para` (parametric, types 0–4) tone response
  curves are both supported.

* LUT-based profiles (`mft1`, `mft2`, `mAB ` / `mBA `) are not
  supported. Pass them through `lcms2` via a NIF if you need them.

## Workflow

    {:ok, profile} = Color.ICC.Profile.load("/path/to/sRGB Profile.icc")
    profile.description           # "sRGB IEC61966-2.1"
    profile.white_point           # {0.9642, 1.0, 0.8249}  (PCS, D50)
    profile.matrix                # 3x3 column-XYZ list
    profile.trc.r                 # %{type: :curve, gamma: 2.4} or {:lut, list}

Once loaded, a profile can be applied to encoded RGB values:

    Color.ICC.Profile.to_xyz(profile, {0.5, 0.5, 0.5})
    # => {x, y, z} in the PCS (D50)

    Color.ICC.Profile.from_xyz(profile, {0.9642, 1.0, 0.8249})
    # => {1.0, 1.0, 1.0}

The PCS for matrix profiles is always D50 / 2° observer; this
module emits raw XYZ in that frame and leaves any chromatic
adaptation to the caller (`Color.XYZ.adapt/3`).

# `t`

```elixir
@type t() :: %Color.ICC.Profile{
  class: atom(),
  colour_space: atom(),
  description: String.t(),
  matrix: [list()],
  pcs: atom(),
  trc: %{r: trc(), g: trc(), b: trc()},
  version: String.t(),
  white_point: {float(), float(), float()}
}
```

# `trc`

```elixir
@type trc() ::
  {:gamma, float()} | {:lut, [float()]} | {:parametric, atom(), [float()]}
```

A tone response curve.

# `from_xyz`

```elixir
@spec from_xyz(t(), {number(), number(), number()}) :: {float(), float(), float()}
```

Converts a PCS (D50) XYZ triple back to encoded RGB in the
profile's colour space.

### Arguments

* `profile` is a `Color.ICC.Profile` struct.

* `xyz` is an `{x, y, z}` tuple in the PCS (D50, 2° observer).

### Returns

* An `{r, g, b}` tuple of unit floats. May be outside `[0, 1]`
  for out-of-gamut inputs; clip or gamut-map separately.

# `load`

```elixir
@spec load(Path.t()) :: {:ok, t()} | {:error, Exception.t()}
```

Loads an ICC matrix profile from the filesystem.

### Arguments

* `path` is a binary file path.

### Returns

* `{:ok, %Color.ICC.Profile{}}` on success.

* `{:error, exception}` if the file cannot be read or is not a
  matrix profile.

# `parse`

```elixir
@spec parse(binary()) :: {:ok, t()} | {:error, Exception.t()}
```

Parses an ICC profile from a binary already in memory.

### Arguments

* `binary` is the full profile bytes.

### Returns

* `{:ok, %Color.ICC.Profile{}}` or `{:error, exception}`.

# `to_xyz`

```elixir
@spec to_xyz(t(), {number(), number(), number()}) :: {float(), float(), float()}
```

Converts an encoded RGB triple in the profile's colour space to
PCS XYZ (D50).

### Arguments

* `profile` is a `Color.ICC.Profile` struct.

* `rgb` is an `{r, g, b}` tuple of unit floats `[0, 1]`.

### Returns

* An `{x, y, z}` tuple of floats in the PCS (D50, 2° observer).

---

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