# `Raxol.Core.Colors.Ansi256`
[🔗](https://github.com/DROOdotFOO/raxol/blob/v2.7.0/lib/raxol/core/colors/ansi256.ex#L1)

The xterm 256-color palette's index -> RGB mapping, for indices 16-255.

Lives in `raxol_core` because it is the only package that `raxol`,
`raxol_terminal` and `raxol_liveview` all depend on, and all three need the
same table. Before this module each kept its own copy and all of them were
wrong in the same way.

## The cube

Indices 16-231 are a 6x6x6 cube. xterm's channel levels are
`0, 95, 135, 175, 215, 255` -- i.e. 0 for the first step and `55 + n * 40`
after it, a deliberately non-linear ramp that gives more resolution in the
dark end where human contrast sensitivity is highest.

Three call sites previously used `n * 51`, producing
`0, 51, 102, 153, 204, 255`. Only the two endpoints agree, so 208 of the 216
cube colors rendered wrong -- index 17 came out `{0, 0, 51}` instead of
`{0, 0, 95}`. The same buffer therefore rendered different colors in
LiveView than in the terminal renderer.

`Raxol.UI.Theming.Palette` documents both formulas and names the wrong one
`:naive_linear`, preserving it deliberately for callers that depend on its
output. This module is only the accurate one; anything that genuinely wants
the naive ramp should say so explicitly rather than get it by accident.

## The grayscale ramp

Indices 232-255 are `(index - 232) * 10 + 8`. Every source in the codebase
already agreed on this one.

# `rgb`

```elixir
@type rgb() :: {0..255, 0..255, 0..255}
```

An 8-bit-per-channel RGB triple.

# `cube_index`

```elixir
@spec cube_index(0..255) :: 0..5
```

The cube axis whose level is nearest `value` -- the inverse of `cube_level/1`.

Lives here, next to the ramp it inverts, because the two halves drifting
apart is the defect this replaces: `Raxol.Style.Colors.Formats` decoded with
this module's xterm ramp while still encoding with `div(v * 6, 256)`, the
exact inverse of the OLD `n * 51` ladder, so 208 of the 216 cube colors
stopped round-tripping.

Thresholds are the midpoints between adjacent levels, so this returns the
genuinely nearest axis for every 0..255 input, ties resolving to the darker
level. A `cond` rather than a search: this is on the colour-adaptation path
and allocates nothing.

    iex> Raxol.Core.Colors.Ansi256.cube_index(0)
    0
    iex> Raxol.Core.Colors.Ansi256.cube_index(95)
    1
    iex> Raxol.Core.Colors.Ansi256.cube_index(255)
    5
    iex> Raxol.Core.Colors.Ansi256.cube_index(100)
    1

# `cube_level`

```elixir
@spec cube_level(0..5) :: 0..255
```

The channel level for one axis of the 6x6x6 cube.

    iex> Raxol.Core.Colors.Ansi256.cube_level(0)
    0
    iex> Raxol.Core.Colors.Ansi256.cube_level(1)
    95
    iex> Raxol.Core.Colors.Ansi256.cube_level(5)
    255

# `cube_rgb`

```elixir
@spec cube_rgb(16..231) :: rgb()
```

RGB for a cube index (16-231).

    iex> Raxol.Core.Colors.Ansi256.cube_rgb(16)
    {0, 0, 0}
    iex> Raxol.Core.Colors.Ansi256.cube_rgb(17)
    {0, 0, 95}
    iex> Raxol.Core.Colors.Ansi256.cube_rgb(231)
    {255, 255, 255}

# `grayscale_rgb`

```elixir
@spec grayscale_rgb(232..255) :: rgb()
```

RGB for a grayscale-ramp index (232-255).

    iex> Raxol.Core.Colors.Ansi256.grayscale_rgb(232)
    {8, 8, 8}
    iex> Raxol.Core.Colors.Ansi256.grayscale_rgb(255)
    {238, 238, 238}

# `to_rgb`

```elixir
@spec to_rgb(16..255) :: rgb()
```

RGB for any index at or above 16, dispatching to the cube or the ramp.

Indices 0-15 are NOT handled here: the base 16 are a terminal-theme
convention, not a computed table, and callers disagree about which
convention they want (VGA, xterm, Tango). Each keeps its own base-16 table
and uses this module only for 16-255.

---

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