Color.Gamut.Diagram (Color v0.11.0)

Copy Markdown

Geometric primitives for drawing chromaticity diagrams — the "horseshoe" plots used to visualise gamuts side-by-side.

This module is pure data. It does no rendering; it returns lists of points and maps of triangles that any renderer (SVG, PNG, a 3D engine) can consume. The companion renderer that ships with the library is the Gamut tab of Color.Palette.Visualizer, which emits inline SVG.

Projections

Two chromaticity projections are supported:

  • :xy — CIE 1931 (x, y). The historical default, the plot everyone recognises. Badly perceptually skewed — the green region dominates visually, the blue corner is crushed.

  • :uv — CIE 1976 (u', v'). Modern default. Same underlying chromaticity data, a more perceptually uniform projection: u' = 4x / (−2x + 12y + 3), v' = 9y / (−2x + 12y + 3).

Primitives

  • spectral_locus/2 — the horseshoe itself, as a list of points tracing monochromatic light from 380 nm (violet) to 700 nm (red).

  • triangle/2 — one working space's primaries and white point, as a four-entry map.

  • planckian_locus/2 — the curve of blackbody chromaticities from 1000 K to 25000 K, with CCT annotations.

  • chromaticity/2 — projects any Color.input() to a single point in the chosen plane.

  • xy_to_uv/1 / uv_to_xy/1 — the projection conversions, exposed in case callers have raw chromaticities already.

Summary

Functions

Returns the chromaticity of any colour input in the requested projection.

Returns points along the Planckian (blackbody) locus from min to max Kelvin at the given step.

Returns the visible-spectrum locus — the outer curve of the chromaticity diagram — as a list of points.

Returns the primaries and white point of a named RGB working space as chromaticities in the requested projection.

Converts a CIE 1976 (u', v') chromaticity back to CIE 1931 (x, y).

Converts a CIE 1931 (x, y) chromaticity to CIE 1976 (u', v').

Types

point()

@type point() :: xy_point() | uv_point()

projection()

@type projection() :: :xy | :uv

spectral_point()

@type spectral_point() ::
  %{wavelength: number(), x: float(), y: float()}
  | %{wavelength: number(), u: float(), v: float()}

triangle()

@type triangle() :: %{red: point(), green: point(), blue: point(), white: point()}

uv_point()

@type uv_point() :: %{u: float(), v: float()}

xy_point()

@type xy_point() :: %{x: float(), y: float()}

Functions

chromaticity(color, projection \\ :xy)

@spec chromaticity(Color.input(), projection()) ::
  {:ok, point()} | {:error, Exception.t()}

Returns the chromaticity of any colour input in the requested projection.

Arguments

  • color is anything accepted by Color.new/1.

  • projection is :xy (default) or :uv.

Returns

  • {:ok, %{x, y}} or {:ok, %{u, v}} on success.

  • {:error, exception} if the colour can't be parsed or converted.

Examples

iex> {:ok, point} = Color.Gamut.Diagram.chromaticity("#ff0000")
iex> {Float.round(point.x, 3), Float.round(point.y, 3)}
{0.64, 0.33}

iex> {:ok, point} = Color.Gamut.Diagram.chromaticity("white")
iex> {Float.round(point.x, 4), Float.round(point.y, 4)}
{0.3127, 0.329}

planckian_locus(range, projection \\ :xy)

@spec planckian_locus(Range.t(), projection()) :: [map()]

Returns points along the Planckian (blackbody) locus from min to max Kelvin at the given step.

Arguments

  • range is a Range.t() of Kelvin values, e.g. 1000..20000//500.

  • projection is :xy (default) or :uv.

Returns

  • A list of maps with :kelvin plus the projection's coordinate keys.

Examples

iex> points = Color.Gamut.Diagram.planckian_locus(2000..10000//1000)
iex> length(points)
9
iex> d65ish = Enum.find(points, &(&1.kelvin == 6000))
iex> Float.round(d65ish.x, 3)
0.322

spectral_locus(projection \\ :xy, options \\ [])

@spec spectral_locus(
  projection(),
  keyword()
) :: [spectral_point()]

Returns the visible-spectrum locus — the outer curve of the chromaticity diagram — as a list of points.

The list traces monochromatic light from the shortest to the longest wavelength in the CIE 1931 2° observer's CMF table. To close the diagram visually, callers typically add a line segment from the last point back to the first (the "line of purples") when rendering.

Arguments

  • projection is :xy (default) or :uv.

Options

  • :observer is 2 (default) or 10 — CIE 1931 2° or CIE 1964 10° standard observer.

  • :step is the wavelength step in nm to subsample the CMF table by. Default 5 nm (every point). Use a larger value (e.g. 10) for faster rendering at small sizes.

Returns

  • A list of maps with :wavelength plus the projection's coordinate keys (:x, :y or :u, :v).

Examples

iex> points = Color.Gamut.Diagram.spectral_locus(:xy)
iex> first = hd(points)
iex> first.wavelength
380.0
iex> Float.round(first.x, 4)
0.1741

iex> points = Color.Gamut.Diagram.spectral_locus(:uv)
iex> point = Enum.find(points, &(&1.wavelength == 520.0))
iex> Float.round(point.u, 4)
0.0231

triangle(working_space, projection \\ :xy)

@spec triangle(atom(), projection()) :: triangle()

Returns the primaries and white point of a named RGB working space as chromaticities in the requested projection.

Arguments

  • working_space is a working-space atom (for example :SRGB, :P3_D65, :AdobeRGB / :Adobe, :Rec2020, :ProPhoto).

  • projection is :xy (default) or :uv.

Returns

  • A map with :red, :green, :blue, :white keys, each a %{x, y} or %{u, v} point.

Examples

iex> t = Color.Gamut.Diagram.triangle(:SRGB)
iex> {Float.round(t.red.x, 2), Float.round(t.red.y, 2)}
{0.64, 0.33}

iex> t = Color.Gamut.Diagram.triangle(:P3_D65)
iex> {Float.round(t.green.x, 3), Float.round(t.green.y, 3)}
{0.265, 0.69}

uv_to_xy(arg)

@spec uv_to_xy({number(), number()}) :: {float(), float()}

Converts a CIE 1976 (u', v') chromaticity back to CIE 1931 (x, y).

Arguments

  • {u, v} is a u'v' chromaticity tuple.

Returns

  • {x, y} — the xy coordinates.

Examples

iex> {x, y} = Color.Gamut.Diagram.uv_to_xy({0.1978, 0.4683})
iex> {Float.round(x, 3), Float.round(y, 3)}
{0.313, 0.329}

xy_to_uv(arg)

@spec xy_to_uv({number(), number()}) :: {float(), float()}

Converts a CIE 1931 (x, y) chromaticity to CIE 1976 (u', v').

Arguments

  • {x, y} is a chromaticity tuple.

Returns

  • {u, v} — the u'v' coordinates.

Examples

iex> {u, v} = Color.Gamut.Diagram.xy_to_uv({0.3127, 0.3290})
iex> {Float.round(u, 4), Float.round(v, 4)}
{0.1978, 0.4683}