Color (Color v0.4.0)
Copy MarkdownTop-level color conversion dispatch.
Every color struct in this library implements to_xyz/1 and
from_xyz/1. convert/2 and convert/3 use Color.XYZ as the hub:
any source color is converted to XYZ, then converted to the
requested target color.
The color spaces currently supported are:
Color.XYZ— CIE 1931 tristimulus.Color.XyY— CIE xyY (chromaticity + luminance).Color.Lab— CIE 1976L*a*b*.Color.LCHab— cylindricalL*a*b*.Color.Luv— CIE 1976L*u*v*.Color.LCHuv— cylindricalL*u*v*.Color.Oklab— Oklab perceptual color space (Björn Ottosson, 2020), defined against D65.Color.Oklch— cylindrical Oklab.Color.HSLuv/Color.HPLuv— perceptually-uniform HSL variants built on CIELUV (Boronine 2015).Color.JzAzBz— HDR/wide-gamut perceptual space (Safdar et al. 2017).Color.ICtCp— Rec. 2100 HDR (PQ or HLG).Color.IPT— Ebner & Fairchild 1998, ancestor of Oklab.Color.CAM16UCS— CAM16 Color Appearance Model uniform space.Color.CMYK— simple subtractive (no ICC).Color.YCbCr— digital video with BT.601/709/2020 variants.Color.SRGB— companded sRGB working space (D65).Color.AdobeRGB— companded Adobe RGB (1998) working space (D65).Color.RGB— linear RGB in any named working space.Color.HSL,Color.HSV— non-linear reparameterisations of sRGB.
All struct modules also expose their own to_xyz/1 and from_xyz/1
functions directly if you want to skip dispatch.
Building colors with new/1 and new/2
new/1 and new/2 accept any of:
A color struct (passed through unchanged, second argument ignored).
A hex string (
"#ff0000","#f80","#ff000080", with or without the leading#) or a CSS named color ("rebeccapurple").A bare list of 3, 4, or 5 numbers, interpreted as the color space given by the optional second argument (default
:srgb).
convert/2 and convert/3 accept the same inputs, so you can write
Color.convert([1.0, 0.0, 0.0], Color.Lab) without first wrapping
the list in an SRGB struct.
Color space argument
The second argument to new/2 (and the space implicitly passed
through convert/2,3) is either a short atom or the module for a
supported color space. The recognised aliases are:
| Short atom | Module | Notes |
|---|---|---|
:srgb | Color.SRGB | default |
:adobe_rgb / :adobe | Color.AdobeRGB | |
:cmyk | Color.CMYK | 4 or 5 channels |
:hsl | Color.HSL | hue in [0, 1] |
:hsv | Color.HSV | hue in [0, 1] |
:hsluv | Color.HSLuv | hue in degrees, s/l in [0, 100] |
:hpluv | Color.HPLuv | hue in degrees, s/l in [0, 100] |
:lab | Color.Lab | D65 |
:lch / :lchab | Color.LCHab | D65 |
:luv | Color.Luv | D65 |
:lchuv | Color.LCHuv | D65 |
:oklab | Color.Oklab | D65 |
:oklch | Color.Oklch | D65 |
:xyz | Color.XYZ | D65 / 2° |
:xyy / :xyY | Color.XyY | D65 / 2° |
:jzazbz | Color.JzAzBz | |
:ictcp | Color.ICtCp | defaults to transfer: :pq |
:ipt | Color.IPT | |
:ycbcr | Color.YCbCr | defaults to variant: :bt709 |
:cam16 / :cam16_ucs | Color.CAM16UCS | default viewing conditions |
Color.RGB (linear, any working space) is intentionally not
list-constructible via new/2 — use
Color.convert([1.0, 1.0, 1.0], Color.RGB, :Rec2020) instead.
Validation rules for list inputs
Validation depends on the space. The table below is the complete specification:
| Category | Spaces | Integer form? | Range check |
|---|---|---|---|
| Strict display | :srgb, :adobe_rgb, :cmyk | Yes — 0..255, scaled to [0.0, 1.0] | Strict — each channel must be in its exact range |
| Strict unit cylindrical | :hsl, :hsv | No | Strict — all channels in [0.0, 1.0], hue wraps |
| Strict deg/percent cylindrical | :hsluv, :hpluv | No | Strict — h wraps, s and l must be in [0, 100] |
| Permissive 3-channel | :lab, :luv, :oklab, :jzazbz, :ipt, :xyz, :xyy, :ictcp, :ycbcr, :cam16_ucs | No | None — accepts wide-gamut / HDR values; rejects NaN and ±∞ |
| Permissive cylindrical | :lch, :lchuv, :oklch | No | None — hue wraps to [0, 360), other channels unrestricted |
Additional rules that apply across the board:
The list is always either 3 or 4 numbers, with 4 meaning "plus alpha".
:cmykadditionally accepts 5 numbers (c, m, y, k, alpha).In the strict display category, the list must be uniform: either all integers (assumed
0..255) or all floats (assumed[0.0, 1.0]). Mixing integers and floats is an error. Integer alpha is also assumed to be0..255.Integer form is only accepted for the three strict display RGB spaces. Every other space rejects integer lists with a clear error pointing to the float form.
Permissive validation accepts out-of-nominal-range values because wide-gamut and HDR sources legitimately exceed the textbook ranges (e.g. wide-gamut Lab can exceed ±128, HDR Oklab can exceed ±0.4, HDR XYZ can exceed
Y = 1.0). It still rejectsNaNand infinity.Cylindrical hues are normalised, not errored.
oklch [0.7, 0.2, 390.0]becomesh = 30.0, andoklch [0.7, 0.2, -45.0]becomesh = 315.0. The same applies to HSL/HSV hue in[0, 1].CIE-tagged spaces default to D65 / 2° observer. If you need a different illuminant, construct the struct directly or use
Color.XYZ.adapt/3after the fact.
Alpha
Every color struct has an :alpha field. Alpha is passed straight
through every conversion — it is never touched by the color math
itself. The library's API assumes straight (unassociated) alpha.
If you are working with pre-multiplied pixel data, un-premultiply
before converting (see the "Pre-multiplied alpha" section below and
Color.unpremultiply/1 / Color.premultiply/1) and re-multiply
afterwards.
Pre-multiplied alpha
Pre-multiplied alpha matters only when a color space's transfer
function is non-linear with respect to the RGB channels. In a
pre-multiplied pixel (R·a, G·a, B·a, a), the stored channels are
no longer the pixel's true color — they are the color scaled by its
own coverage. Applying a non-linear operation like sRGB companding
to R·a does not give you sRGB(R)·a because
(R·a)^γ ≠ R^γ · a^γ.
Since this library's forward pipeline is always
source → linear RGB → XYZ → target, and several stages contain
non-linear steps (sRGB companding, Adobe RGB gamma, the PQ / HLG
transfer functions, Lab's f(x), Oklab's cube root, CAM16's
post-adaptation compression, etc.), converting a pre-multiplied
color directly is incorrect and will produce subtly wrong
output.
The rule is:
If your color is straight-alpha: just call
convert/2. The alpha value is carried through untouched.If your color is pre-multiplied: call
Color.unpremultiply/1, thenconvert/2, thenColor.premultiply/1on the result if you still need pre-multiplied output.
premultiply/1 and unpremultiply/1 are only defined for
Color.SRGB, Color.AdobeRGB and Color.RGB (linear), since
pre-multiplication is only meaningful in spaces with an RGB tuple.
Summary
Types
Anything Color.new/1,2 accepts: a colour struct, a list of 3, 4
or 5 numbers, a hex string, a CSS named-colour string, or an
atom naming a CSS colour. See Color.new/2 for the full set of
rules.
A {:ok, color} or {:error, exception_struct} result. The error
side is always one of the structured Color.*Error exceptions in
lib/color/exceptions/.
Any colour struct supported by the library. Used as the parameter
type of convert/2,3,4, to_xyz/1, premultiply/1,
unpremultiply/1, luminance/1, and similar.
A target colour space module — anything that can appear as the
second argument to convert/2,3,4.
Functions
Returns true when value can be built into a color by
Color.new/1, and false otherwise.
Converts a color to a different color space.
Converts a color to Color.RGB (linear) in the given working space
with rendering-intent options.
Converts a list (or stream) of colors to the same target space.
Converts a list of colors to a Color.RGB target in the named
working space, with rendering-intent options.
Returns true when value is actually a known CSS named color.
Accepts atoms or strings; ignores underscores, hyphens, whitespace
and case, so :misty_rose, "Misty Rose" and "MistyRose" all
resolve to the same entry.
Compile-time guard that returns true when value has a shape
that might be a color. The guard does a cheap structural check
Compile-time guard that returns true when value looks like it
could be a CSS named color — that is, an atom or a binary. The
guard does not check that the name is actually in the lookup table;
for that use Color.css_name?/1.
Returns the WCAG 2.x relative luminance of a color — the CIE Y
component of its linear sRGB, on the [0, 1] scale.
Builds a color struct from a variety of inputs.
Pre-multiplies a color's channels by its alpha.
Alias for luminance/1. Returns the WCAG 2.x relative luminance of
a color, the same value Color.Contrast.relative_luminance/1
computes. The longer name is the canonical one because the bare
luminance is ambiguous between absolute photometric luminance,
perceived lightness, and the WCAG definition.
Sorts a list of colors by a perceptual criterion.
Serialises a colour as an ANSI SGR escape sequence for terminal output.
Serialises a color to a CSS Color Module Level 4 string.
Serialises a color to a hex string, converting it to sRGB first if necessary.
Converts any supported color to Color.XYZ.
Inverts premultiply/1. A color with nil alpha is returned
unchanged; a color with alpha = 0.0 is returned unchanged (the
original channels are unrecoverable and the alpha is authoritative).
Validates a transparency value from the union of forms used by
Image.Color and its callers, returning an alpha as a float in
[0.0, 1.0].
Types
Anything Color.new/1,2 accepts: a colour struct, a list of 3, 4
or 5 numbers, a hex string, a CSS named-colour string, or an
atom naming a CSS colour. See Color.new/2 for the full set of
rules.
@type result() :: {:ok, t()} | {:error, Exception.t()}
A {:ok, color} or {:error, exception_struct} result. The error
side is always one of the structured Color.*Error exceptions in
lib/color/exceptions/.
@type t() :: Color.SRGB.t() | Color.AdobeRGB.t() | Color.RGB.t() | Color.Lab.t() | Color.LCHab.t() | Color.Luv.t() | Color.LCHuv.t() | Color.Oklab.t() | Color.Oklch.t() | Color.HSL.t() | Color.HSV.t() | Color.HSLuv.t() | Color.HPLuv.t() | Color.CMYK.t() | Color.YCbCr.t() | Color.JzAzBz.t() | Color.ICtCp.t() | Color.IPT.t() | Color.CAM16UCS.t() | Color.XYZ.t() | Color.XyY.t()
Any colour struct supported by the library. Used as the parameter
type of convert/2,3,4, to_xyz/1, premultiply/1,
unpremultiply/1, luminance/1, and similar.
@type target() ::
Color.SRGB
| Color.AdobeRGB
| Color.RGB
| Color.Lab
| Color.LCHab
| Color.Luv
| Color.LCHuv
| Color.Oklab
| Color.Oklch
| Color.HSL
| Color.HSV
| Color.HSLuv
| Color.HPLuv
| Color.CMYK
| Color.YCbCr
| Color.JzAzBz
| Color.ICtCp
| Color.IPT
| Color.CAM16UCS
| Color.XYZ
| Color.XyY
A target colour space module — anything that can appear as the
second argument to convert/2,3,4.
Functions
Returns true when value can be built into a color by
Color.new/1, and false otherwise.
This is a stricter, runtime version of is_color/1. It fully
parses hex strings and looks up CSS names, so an unknown name
returns false.
Arguments
valueis anything accepted bynew/1.
Examples
iex> Color.color?("red")
true
iex> Color.color?("#ff0000")
true
iex> Color.color?([255, 128, 0])
true
iex> Color.color?([1.0, 0, 0]) # mixed integers and floats
false
iex> Color.color?("notacolor")
false
iex> Color.color?(42)
false
Converts a color to a different color space.
color may be anything accepted by new/1, including a bare list
[r, g, b] or [r, g, b, a] (interpreted as sRGB), a hex string, a
CSS named color, or a color struct.
For Color.RGB (linear, any working space) use convert/3 and pass
the working-space atom as the third argument.
Arguments
coloris any input accepted bynew/1.targetis the target module (for exampleColor.Lab,Color.SRGB).optionsis a keyword list — see below.
Options
:intent— the ICC rendering intent. One of::relative_colorimetric(default) — chromatically adapt the source white to the target white using Bradford. Out-of-gamut colors are not altered; any clipping is left to the caller or deferred to a later step. This matches the current default behaviour.:absolute_colorimetric— no chromatic adaptation. The source XYZ is handed to the target'sfrom_xyz/1verbatim. Use this when you want to preserve the exact XYZ values regardless of reference-white mismatch.:perceptual— chromatically adapt and gamut-map so the result is inside the target's gamut (when the target has a gamut, i.e. an RGB working space). The gamut-mapping algorithm is CSS Color 4's Oklch binary search (same asColor.Gamut.to_gamut/3withmethod: :oklch).:saturation— currently an alias for:perceptual. Treated as a gamut-compressing intent. (A true "saturation" intent that preserves chroma at the cost of hue shift is deferred to a future version.)
:bpc—trueto apply black point compensation after chromatic adaptation,false(default) to skip it. SeeColor.XYZ.apply_bpc/3.:adaptation— the chromatic adaptation method used by:relative_colorimetricand:perceptual. One of:bradford(default),:xyz_scaling,:von_kries,:sharp,:cmccat2000,:cat02.
Returns
{:ok, %target{}}on success.{:error, reason}if the conversion can't be performed.
Examples
iex> {:ok, lab} = Color.convert(%Color.SRGB{r: 1.0, g: 0.0, b: 0.0}, Color.Lab)
iex> {Float.round(lab.l, 2), Float.round(lab.a, 2), Float.round(lab.b, 2)}
{53.24, 80.09, 67.2}
iex> {:ok, lab} = Color.convert([1.0, 0.0, 0.0], Color.Lab)
iex> Float.round(lab.l, 2)
53.24
iex> {:ok, srgb} = Color.convert("#ff0000", Color.SRGB)
iex> {Float.round(srgb.r, 6), Float.round(srgb.g, 6), Float.round(srgb.b, 6)}
{1.0, 0.0, 0.0}
iex> {:ok, srgb} = Color.convert(%Color.Lab{l: 53.2408, a: 80.0925, b: 67.2032}, Color.SRGB)
iex> {Float.round(srgb.r, 3), Float.round(srgb.g, 3), Float.round(srgb.b, 3)}
{1.0, 0.0, 0.0}
iex> {:ok, c} = Color.convert([1.0, 0.0, 0.0, 0.5], Color.Lab)
iex> c.alpha
0.5Wide-gamut Display P3 red gamut-mapped into sRGB via the
:perceptual intent:
iex> p3_red = %Color.RGB{r: 1.0, g: 0.0, b: 0.0, working_space: :P3_D65}
iex> {:ok, mapped} = Color.convert(p3_red, Color.SRGB, intent: :perceptual)
iex> Color.Gamut.in_gamut?(mapped, :SRGB)
true
@spec convert(input(), target(), Color.Types.working_space() | keyword()) :: result()
@spec convert(input(), Color.RGB, Color.Types.working_space(), keyword()) :: result()
Converts a color to Color.RGB (linear) in the given working space
with rendering-intent options.
Arguments
coloris any supported color struct or input accepted bynew/1.targetmust beColor.RGB.working_spaceis an atom naming an RGB working space (for example:SRGB,:Adobe,:ProPhoto).optionsis the same keyword list asconvert/3, supporting:intent,:bpc, and:adaptation.
Returns
{:ok, %Color.RGB{}}on success.
Examples
iex> {:ok, rgb} = Color.convert(%Color.SRGB{r: 1.0, g: 1.0, b: 1.0}, Color.RGB, :SRGB)
iex> {Float.round(rgb.r, 3), Float.round(rgb.g, 3), Float.round(rgb.b, 3)}
{1.0, 1.0, 1.0}Equivalent option-list form, which composes more cleanly with the rendering-intent options:
iex> {:ok, rgb} = Color.convert(%Color.SRGB{r: 1.0, g: 1.0, b: 1.0},
...> Color.RGB, working_space: :SRGB)
iex> {Float.round(rgb.r, 3), Float.round(rgb.g, 3), Float.round(rgb.b, 3)}
{1.0, 1.0, 1.0}
@spec convert_many(Enumerable.t(), target(), keyword()) :: {:ok, [t()]} | {:error, Exception.t()}
Converts a list (or stream) of colors to the same target space.
This is the batch equivalent of convert/2,3,4. It is useful when
you have many colors heading to the same destination — for example
every pixel in a row, or every entry in a palette — and you want to
amortise the per-call setup over the whole list.
The implementation:
Looks up the target's working-space matrix and chromatic adaptation matrix once.
Iterates the input list with a single fold, calling the underlying space's
from_xyz/1for each element.Halts at the first
{:error, _}and returns it.
Arguments
colorsis a list, stream, or any other enumerable of inputs accepted bynew/1.targetis the target color space module (orColor.RGB).optionsis the same keyword list asconvert/3. ForColor.RGBtargets passworking_space:(or use theconvert_many/4form).
Returns
{:ok, [color, ...]}with one entry per input.{:error, exception}on the first failure.
Examples
iex> {:ok, [a, b, c]} = Color.convert_many(["red", "green", "blue"], Color.Lab)
iex> {Float.round(a.l, 1), Float.round(b.l, 1), Float.round(c.l, 1)}
{53.2, 46.2, 32.3}
iex> {:ok, list} = Color.convert_many([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
...> Color.Oklab)
iex> length(list)
3
iex> {:ok, []} = Color.convert_many([], Color.Lab)
@spec convert_many(Enumerable.t(), Color.RGB, Color.Types.working_space(), keyword()) :: {:ok, [Color.RGB.t()]} | {:error, Exception.t()}
Converts a list of colors to a Color.RGB target in the named
working space, with rendering-intent options.
Arguments
colorsis an enumerable of inputs accepted bynew/1.targetmust beColor.RGB.working_spaceis the working-space atom.optionsis the same keyword list asconvert/4.
Returns
{:ok, [%Color.RGB{}, ...]}on success.
Examples
iex> {:ok, list} = Color.convert_many(["red", "green", "blue"], Color.RGB, :SRGB)
iex> Enum.all?(list, &match?(%Color.RGB{working_space: :SRGB}, &1))
true
Returns true when value is actually a known CSS named color.
Accepts atoms or strings; ignores underscores, hyphens, whitespace
and case, so :misty_rose, "Misty Rose" and "MistyRose" all
resolve to the same entry.
Arguments
valueis an atom or string.
Examples
iex> Color.css_name?("rebeccapurple")
true
iex> Color.css_name?(:misty_rose)
true
iex> Color.css_name?("notacolor")
false
iex> Color.css_name?(nil)
false
Compile-time guard that returns true when value has a shape
that might be a color. The guard does a cheap structural check:
any struct (tight coupling to the
@color_structslist is not possible in adefguardbecauseis_struct/2only accepts a literal module).a list of 3, 4 or 5 numbers — matches bare sRGB / CMYK lists.
a binary (hex or CSS name).
a non-boolean atom (CSS name).
For a strict check that the value is actually recognised, call
Color.color?/1.
Arguments
valueis anything.
Examples
iex> Color.is_color([1.0, 0.5, 0.0])
true
iex> Color.is_color("#ff0000")
true
iex> Color.is_color(:red)
true
iex> Color.is_color(42)
false
Compile-time guard that returns true when value looks like it
could be a CSS named color — that is, an atom or a binary. The
guard does not check that the name is actually in the lookup table;
for that use Color.css_name?/1.
Arguments
valueis anything.
Examples
iex> Color.is_css_name("red")
true
iex> Color.is_css_name(:misty_rose)
true
iex> Color.is_css_name(123)
false
Returns the WCAG 2.x relative luminance of a color — the CIE Y
component of its linear sRGB, on the [0, 1] scale.
This is a convenience delegate to
Color.Contrast.relative_luminance/1, exposed at the top level for
discoverability since it's used all over the place (perceptual
sort, accessibility checks, threshold-based luminance picking,
HDR tone mapping, etc.).
Arguments
coloris anything accepted bynew/1.
Returns
- A float in
[0, 1].
Examples
iex> Color.luminance("white")
1.0
iex> Color.luminance("black")
0.0
iex> Float.round(Color.luminance("red"), 4)
0.2126
@spec new(input(), Color.Types.space() | target()) :: result()
@spec new(input(), atom() | module()) :: result()
Builds a color struct from a variety of inputs.
Arguments
inputis one of:A color struct — returned unchanged inside
{:ok, struct}.A bare list of numbers, interpreted as the color space selected by
space(default:srgb). See the "List inputs" section below for the detailed rules.A hex string (
"#ff0000","#ff000080","#f80", or the same without the leading#).A CSS named color string or atom (
"rebeccapurple",:misty_rose,"Red").
spaceis the color space the list is in. One of the short atoms (:srgb,:adobe_rgb,:cmyk,:hsl,:hsv,:hsluv,:hpluv,:lab,:lch/:lchab,:luv,:lchuv,:oklab,:oklch,:xyz,:xyy,:jzazbz,:ictcp,:ipt,:ycbcr,:cam16_ucs) or the equivalent module (Color.SRGB,Color.Lab, …). Defaults to:srgb. Ignored for non-list inputs.
List inputs
Lists are always either 3 or 4 numbers (4 is a sRGB alpha
channel, or an HSL/Lab/… alpha channel). :cmyk additionally
accepts 4 or 5 numbers (c, m, y, k, and optionally alpha).
Display spaces (:srgb, :adobe_rgb, :cmyk, :hsl, :hsv,
:hsluv, :hpluv) are strict:
All elements must be the same numeric type — either all integers or all floats. Mixing is an error.
Integer form is only accepted for
:srgb,:adobe_rgband:cmyk, where each channel is assumed to be in0..255and is normalised to[0.0, 1.0]internally.Each channel is range-checked and a value outside the expected range is an error.
CIE / perceptual / HDR spaces (:lab, :lch, :luv,
:lchuv, :oklab, :oklch, :xyz, :xyy, :jzazbz, :ictcp,
:ipt, :ycbcr, :cam16_ucs) are permissive:
Floats only — integers are rejected with a clear error.
NaN and infinity are rejected.
Values outside the nominal range are not rejected, because wide-gamut and HDR inputs legitimately exceed the "textbook" ranges for these spaces.
For any cylindrical space (:lch, :lchuv, :oklch,
:hsluv, :hpluv, :hsl, :hsv), hue values are normalised
into the conventional range (not errored) — 370° becomes 10°,
-45° becomes 315°, and so on.
Returns
{:ok, struct}.{:error, reason}if the input can't be interpreted.
Examples
iex> {:ok, c} = Color.new([1.0, 0.5, 0.0])
iex> {c.r, c.g, c.b}
{1.0, 0.5, 0.0}
iex> {:ok, c} = Color.new([255, 128, 0])
iex> {c.r, Float.round(c.g, 4), c.b}
{1.0, 0.502, 0.0}
iex> {:ok, c} = Color.new([53.24, 80.09, 67.2], :lab)
iex> c.illuminant
:D65
iex> {:ok, c} = Color.new([0.63, 0.22, 0.13], :oklab)
iex> c.__struct__
Color.Oklab
iex> {:ok, c} = Color.new([0.7, 0.2, 30.0], :oklch)
iex> c.h
30.0
iex> {:ok, c} = Color.new([0.7, 0.2, -45.0], :oklch)
iex> c.h
315.0
iex> {:ok, c} = Color.new([0.0, 0.5, 1.0, 0.0], :cmyk)
iex> c.c
0.0
iex> {:error, %Color.InvalidComponentError{reason: :mixed_types}} = Color.new([1.0, 0, 0])
iex> {:error, %Color.InvalidComponentError{reason: :out_of_range, range: {0, 255}}} =
...> Color.new([300, 0, 0])
iex> {:error, %Color.InvalidComponentError{reason: :integers_not_allowed, space: "Lab"}} =
...> Color.new([1, 2, 3], :lab)
@spec premultiply(Color.SRGB.t() | Color.AdobeRGB.t() | Color.RGB.t()) :: Color.SRGB.t() | Color.AdobeRGB.t() | Color.RGB.t()
Pre-multiplies a color's channels by its alpha.
Only supported for RGB-tuple color spaces (Color.SRGB,
Color.AdobeRGB, Color.RGB) since pre-multiplication is not
meaningful for opponent or cylindrical spaces. A color with nil
alpha is treated as fully opaque (alpha = 1.0) and is returned
unchanged.
Arguments
coloris an RGB-tuple color struct.
Returns
- A new color struct of the same type with pre-multiplied channels.
Examples
iex> Color.premultiply(%Color.SRGB{r: 1.0, g: 0.5, b: 0.25, alpha: 0.5})
%Color.SRGB{r: 0.5, g: 0.25, b: 0.125, alpha: 0.5}
iex> Color.premultiply(%Color.SRGB{r: 1.0, g: 0.5, b: 0.25})
%Color.SRGB{r: 1.0, g: 0.5, b: 0.25, alpha: nil}
Alias for luminance/1. Returns the WCAG 2.x relative luminance of
a color, the same value Color.Contrast.relative_luminance/1
computes. The longer name is the canonical one because the bare
luminance is ambiguous between absolute photometric luminance,
perceived lightness, and the WCAG definition.
Examples
iex> Color.relative_luminance("white")
1.0
iex> Color.relative_luminance("black")
0.0
@spec sort( [input()], keyword() ) :: {:ok, [Color.SRGB.t()]} | {:error, Exception.t()}
Sorts a list of colors by a perceptual criterion.
Arguments
colorsis a list of anything accepted bynew/1.optionsis a keyword list.
Options
:byselects the sort key. One of::luminance— WCAG relative luminance (default, dark → light).:lightness— CIE LabL*(dark → light).:oklab_l— OklabL(dark → light).:chroma— CIELChC*(grey → saturated).:oklch_c— OklchC(grey → saturated).:hue— CIELCh hue in degrees (red → red, around the wheel).:oklch_h— Oklch hue in degrees.:hlv— the HLV hue/luminance/value bucketing from https://www.alanzucconi.com/2015/09/30/colour-sorting/ (good for palette display).A 1-arity function that maps a color struct to a comparable sort key.
:order—:asc(default) or:desc.
Returns
{:ok, sorted_colors}with each element as aColor.SRGBstruct.{:error, reason}if any input can't be parsed.
Examples
iex> {:ok, sorted} = Color.sort(["white", "black", "#888"], by: :luminance)
iex> Enum.map(sorted, &Color.SRGB.to_hex/1)
["#000000", "#888888", "#ffffff"]
iex> {:ok, sorted} = Color.sort(["red", "green", "blue"], by: :hue)
iex> Enum.map(sorted, &Color.SRGB.to_hex/1) |> length()
3
Serialises a colour as an ANSI SGR escape sequence for terminal output.
Accepts any input Color.new/1 accepts and delegates to
Color.ANSI.to_string/2.
Arguments
coloris any input accepted bynew/1.optionsis a keyword list. SeeColor.ANSI.to_string/2for the full set of options::mode—:truecolor(default),:ansi256, or:ansi16.:layer—:foreground(default) or:background.
Returns
- A binary string containing the escape sequence.
Examples
iex> Color.to_ansi("red") == "\e[38;2;255;0;0m"
true
iex> Color.to_ansi("red", mode: :ansi256) == "\e[38;5;196m"
true
iex> Color.to_ansi("red", mode: :ansi16) == "\e[91m"
true
iex> Color.to_ansi("red", layer: :background) == "\e[48;2;255;0;0m"
true
iex> Color.to_ansi(%Color.Lab{l: 53.2408, a: 80.0925, b: 67.2032}) == "\e[38;2;255;0;0m"
true
Serialises a color to a CSS Color Module Level 4 string.
Accepts any input that Color.new/1 accepts — a color struct, a
bare list, a hex string, a CSS named color, or an atom. String and
list inputs are normalised to the appropriate colour space first.
The default serialiser form follows the resulting struct type:
Color.SRGB→rgb(r g b / a)Color.HSL→hsl(h s% l% / a)Color.Lab→lab(L% a b / a)Color.LCHab→lch(L% C h / a)Color.Oklab→oklab(L% a b / a)Color.Oklch→oklch(L% C h / a)Color.XYZ→color(xyz-d65 X Y Z / a)(orxyz-d50for a D50-tagged struct)Color.AdobeRGB→color(a98-rgb r g b / a)Color.RGB→color(<working-space> r g b / a)when the working space has a CSS Color 4 name, otherwisecolor(srgb-linear …)Any other supported colour space is converted to
Color.SRGBfirst and emitted asrgb(…).
Arguments
coloris any input accepted bynew/1.optionsis a keyword list.:asoverrides the default form for RGB-family colours (:rgb,:hex,:color).
Returns
- A string.
Examples
iex> Color.to_css("#ff0000")
"rgb(255 0 0)"
iex> Color.to_css(%Color.SRGB{r: 1.0, g: 0.0, b: 0.0, alpha: 0.5})
"rgb(255 0 0 / 0.5)"
iex> Color.to_css("rebeccapurple")
"rgb(102 51 153)"
iex> Color.to_css(%Color.Lab{l: 50.0, a: 40.0, b: 30.0})
"lab(50% 40 30)"
iex> Color.to_css(%Color.Oklch{l: 0.7, c: 0.15, h: 180.0})
"oklch(70% 0.15 180)"
iex> Color.to_css(%Color.SRGB{r: 1.0, g: 0.0, b: 0.0}, as: :hex)
"#ff0000"
Serialises a color to a hex string, converting it to sRGB first if necessary.
Accepts any input that Color.new/1 accepts. The output form
follows the sRGB alpha: opaque colours return #rrggbb, translucent
colours return #rrggbbaa.
Arguments
coloris any input accepted bynew/1.
Returns
- A string starting with
#.
Examples
iex> Color.to_hex(%Color.SRGB{r: 1.0, g: 0.0, b: 0.0})
"#ff0000"
iex> Color.to_hex("rebeccapurple")
"#663399"
iex> Color.to_hex(%Color.Lab{l: 53.2408, a: 80.0925, b: 67.2032})
"#ff0000"
iex> Color.to_hex(%Color.Oklch{l: 0.7, c: 0.15, h: 30.0})
"#ed7664"
iex> Color.to_hex(%Color.SRGB{r: 1.0, g: 0.0, b: 0.0, alpha: 0.5})
"#ff000080"
@spec to_xyz(t()) :: {:ok, Color.XYZ.t()} | {:error, Exception.t()}
Converts any supported color to Color.XYZ.
Arguments
coloris any supported color struct.
Returns
{:ok, %Color.XYZ{}}.
@spec unpremultiply(Color.SRGB.t() | Color.AdobeRGB.t() | Color.RGB.t()) :: Color.SRGB.t() | Color.AdobeRGB.t() | Color.RGB.t()
Inverts premultiply/1. A color with nil alpha is returned
unchanged; a color with alpha = 0.0 is returned unchanged (the
original channels are unrecoverable and the alpha is authoritative).
Arguments
coloris an RGB-tuple color struct.
Returns
- A new color struct of the same type with un-pre-multiplied channels.
Examples
iex> Color.unpremultiply(%Color.SRGB{r: 0.5, g: 0.25, b: 0.125, alpha: 0.5})
%Color.SRGB{r: 1.0, g: 0.5, b: 0.25, alpha: 0.5}
iex> Color.unpremultiply(%Color.SRGB{r: 0.0, g: 0.0, b: 0.0, alpha: 0.0})
%Color.SRGB{r: 0.0, g: 0.0, b: 0.0, alpha: 0.0}
@spec validate_transparency(any()) :: {:ok, float()} | {:error, Exception.t()}
Validates a transparency value from the union of forms used by
Image.Color and its callers, returning an alpha as a float in
[0.0, 1.0].
Accepts:
:transparent/:none→0.0(fully transparent).:opaque→1.0(fully opaque).an integer in
0..255— scaled by1/255.a float in
[0.0, 1.0]— returned unchanged.
Arguments
valueis any of the above.
Returns
{:ok, float}in[0.0, 1.0].{:error, %Color.InvalidComponentError{}}otherwise.
Examples
iex> Color.validate_transparency(:transparent)
{:ok, 0.0}
iex> Color.validate_transparency(:opaque)
{:ok, 1.0}
iex> Color.validate_transparency(128)
{:ok, 0.5019607843137255}
iex> Color.validate_transparency(0.75)
{:ok, 0.75}
iex> {:error, %Color.InvalidComponentError{}} = Color.validate_transparency(:maybe)
iex> {:error, %Color.InvalidComponentError{}} = Color.validate_transparency(300)