Color representation and parsing for terminal styling.
Accepts hex ("#RGB", "#RRGGBB"), RGB ("rgb(r,g,b)", "rgba(r,g,b,a)"),
and HSL ("hsl(h,s%,l%)", "hsla(h,s%,l%,a)") strings, as well as
{r, g, b} and {r, g, b, a} tuples.
parse/1 returns {:ok, t()} or {:error, reason} and handles strings and
tuples only. normalize/1 covers the same inputs plus the named-colour atoms
listed below, converting any of them to a plain {r, g, b} tuple for segment
styles and falling back to white on anything it does not recognise.
Named colours
:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white,
and a :bright_-prefixed variant of each. These are the palette values, not the
terminal's own ANSI colours.
Examples
iex> Drafter.Color.normalize("#f00")
{255, 0, 0}
iex> Drafter.Color.normalize("rgb(10, 20, 30)")
{10, 20, 30}
iex> Drafter.Color.normalize(:black)
{0, 0, 0}
iex> Drafter.Color.normalize("not a color")
{255, 255, 255}
iex> Drafter.Color.parse("nope")
{:error, :invalid_format}
Summary
Types
Anything normalize/1 accepts: a colour string, a tuple, a struct, or a named atom.
A plain RGB triple, as carried on segment styles.
Functions
Build a colour from hue in degrees, saturation and lightness in percent.
Build a colour from channel values.
Convert any supported colour input to a plain {r, g, b} triple.
Normalize a colour to {{r, g, b}, alpha}, keeping the alpha channel.
Parse a colour string or tuple into a t/0.
Hue in degrees, saturation and lightness in percent, for a colour.
The colour's {r, g, b} triple, dropping alpha.
The colour's {r, g, b, a} tuple, keeping alpha.
Types
Anything normalize/1 accepts: a colour string, a tuple, a struct, or a named atom.
@type rgb() :: {0..255, 0..255, 0..255}
A plain RGB triple, as carried on segment styles.
@type t() :: %Drafter.Color{a: float(), b: 0..255, g: 0..255, r: 0..255}
Functions
Build a colour from hue in degrees, saturation and lightness in percent.
Hue wraps, so -60 and 300 are the same. Saturation and lightness are clamped
to 0..100. a defaults to 1.0 and is clamped into 0.0..1.0.
Examples
iex> Drafter.Color.from_hsl(0, 100, 50)
%Drafter.Color{r: 255, g: 0, b: 0, a: 1.0}
iex> Drafter.Color.from_hsl(240, 100, 50)
%Drafter.Color{r: 0, g: 0, b: 255, a: 1.0}
iex> Drafter.Color.from_hsl(-240, 100, 50) == Drafter.Color.from_hsl(120, 100, 50)
true
Build a colour from channel values.
Each of r, g and b must be in 0..255; anything else raises
FunctionClauseError. a is clamped into 0.0..1.0 rather than rejected.
Default: 1.0.
Examples
iex> Drafter.Color.new(10, 20, 30)
%Drafter.Color{r: 10, g: 20, b: 30, a: 1.0}
iex> Drafter.Color.new(10, 20, 30, 2.5)
%Drafter.Color{r: 10, g: 20, b: 30, a: 1.0}
Convert any supported colour input to a plain {r, g, b} triple.
Alpha is dropped. Unrecognised strings, unknown atoms and anything else fall back
to white, {255, 255, 255} — this function never raises and never returns an error
tuple. Use parse/1 when a bad colour should be reported rather than substituted.
Examples
iex> Drafter.Color.normalize("#00ff00")
{0, 255, 0}
iex> Drafter.Color.normalize({1, 2, 3, 0.5})
{1, 2, 3}
iex> Drafter.Color.normalize(:bright_white)
{255, 255, 255}
iex> Drafter.Color.normalize(:no_such_colour)
{255, 255, 255}
Normalize a colour to {{r, g, b}, alpha}, keeping the alpha channel.
Accepts the same inputs as normalize/1. alpha is a float in 0.0..1.0;
a colour with no alpha channel comes back as 1.0. Use this where the alpha
is still needed — the compositor blends against the cell beneath — and
normalize/1 where a plain RGB triple is wanted.
Examples
iex> Drafter.Color.normalize_with_alpha("rgba(1,2,3,0.5)")
{{1, 2, 3}, 0.5}
iex> Drafter.Color.normalize_with_alpha("#f00")
{{255, 0, 0}, 1.0}
iex> Drafter.Color.normalize_with_alpha(:black)
{{0, 0, 0}, 1.0}
iex> Drafter.Color.normalize_with_alpha("not a color")
{{255, 255, 255}, 1.0}
@spec parse(term()) :: {:ok, t()} | {:error, :invalid_hex | :invalid_hex_length | :invalid_rgb | :invalid_hsl | :invalid_tuple | :invalid_format}
Parse a colour string or tuple into a t/0.
Named-colour atoms are not accepted here — use normalize/1 for those.
Error reasons are :invalid_hex, :invalid_hex_length, :invalid_rgb,
:invalid_hsl, :invalid_tuple (a tuple that is not a valid RGB/RGBA tuple),
and :invalid_format for everything else.
Examples
iex> Drafter.Color.parse("#abc")
{:ok, %Drafter.Color{r: 170, g: 187, b: 204, a: 1.0}}
iex> Drafter.Color.parse("rgba(1,2,3,0.5)")
{:ok, %Drafter.Color{r: 1, g: 2, b: 3, a: 0.5}}
iex> Drafter.Color.parse({1, 2, 3})
{:ok, %Drafter.Color{r: 1, g: 2, b: 3, a: 1.0}}
iex> Drafter.Color.parse("#gg00zz")
{:error, :invalid_hex}
iex> Drafter.Color.parse("#12345")
{:error, :invalid_hex_length}
iex> Drafter.Color.parse({1, 2})
{:error, :invalid_tuple}
iex> Drafter.Color.parse(:black)
{:error, :invalid_format}
Hue in degrees, saturation and lightness in percent, for a colour.
Alpha is dropped. For a fully desaturated colour hue and saturation come back as
the integer 0, so compare numerically rather than by pattern match.
Examples
iex> Drafter.Color.to_hsl(Drafter.Color.new(255, 0, 0))
{0.0, 100.0, 50.0}
iex> Drafter.Color.to_hsl(Drafter.Color.new(0, 0, 0))
{0, 0, 0.0}
The colour's {r, g, b} triple, dropping alpha.
Examples
iex> Drafter.Color.to_tuple(Drafter.Color.new(1, 2, 3, 0.5))
{1, 2, 3}
The colour's {r, g, b, a} tuple, keeping alpha.
Examples
iex> Drafter.Color.to_tuple_with_alpha(Drafter.Color.new(1, 2, 3, 0.5))
{1, 2, 3, 0.5}