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.
Summary
Functions
The cube axis whose level is nearest value -- the inverse of cube_level/1.
The channel level for one axis of the 6x6x6 cube.
RGB for a cube index (16-231).
RGB for a grayscale-ramp index (232-255).
RGB for any index at or above 16, dispatching to the cube or the ramp.
Types
Functions
@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
@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
@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}
@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}
@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.