Color.Spectral
(Color v0.4.0)
Copy Markdown
Spectral power distributions, spectral reflectances, and spectrum → XYZ integration.
A Color.Spectral struct holds a list of wavelengths (in nm) and a
matching list of values. For emissive sources the values are
absolute or relative spectral power. For reflective samples the
values are the per-wavelength reflectance in [0.0, 1.0].
The module provides:
illuminant/1— well-known CIE illuminants (:D65,:D50,:A,:E) as ready-to-use SPDs.cmf/1— the CIE 1931 2° (default) and CIE 1964 10° standard observer colour matching functions.to_xyz/1,2— integrates an SPD against a CMF to produce aColor.XYZstruct. TheYcomponent is normalised to1.0for the chosen illuminant's reference white (so this matches the rest of the library's convention).reflectance_to_xyz/3— multiplies a reflectance SPD by an illuminant SPD, then integrates. This is what you use for paint samples, fabric, printed swatches, etc.metamerism/4— compares two spectral samples under two illuminants to detect metamers: pairs that match under one light and diverge under another.
The standard tables are at 5 nm intervals from 380 nm to 780 nm
(81 samples). All the built-in data lives in
Color.Spectral.Tables. If you load a sample with a different
wavelength grid, to_xyz/2 will linearly interpolate it onto the
5 nm grid before integrating.
Summary
Functions
Returns the CIE standard observer colour matching functions as three
Color.Spectral structs ({x_bar, y_bar, z_bar}).
Returns a Color.Spectral struct for a named CIE illuminant.
Computes a metamerism index between two spectral reflectance samples under two different illuminants.
Converts a spectral reflectance sample under a specific
illuminant to a CIE XYZ tristimulus.
Resamples a spectral struct onto a new wavelength grid via linear interpolation. Samples outside the source range are extrapolated as zero.
Converts a spectral power distribution to a CIE XYZ tristimulus.
Types
Functions
Returns the CIE standard observer colour matching functions as three
Color.Spectral structs ({x_bar, y_bar, z_bar}).
Arguments
observer_angleis2(CIE 1931) or10(CIE 1964). Defaults to2.
Returns
- A
{x_bar, y_bar, z_bar}tuple ofColor.Spectralstructs.
Examples
iex> {x, y, z} = Color.Spectral.cmf()
iex> {length(x.values), length(y.values), length(z.values)}
{81, 81, 81}
@spec illuminant(:D65 | :D50 | :A | :E) :: t()
Returns a Color.Spectral struct for a named CIE illuminant.
Arguments
nameis one of:D65,:D50,:A,:E.
Returns
- A
Color.Spectralstruct with 81 samples at 5 nm from 380 to 780 nm.
Examples
iex> spd = Color.Spectral.illuminant(:D65)
iex> length(spd.wavelengths)
81
iex> spd = Color.Spectral.illuminant(:E)
iex> Enum.uniq(spd.values)
[100.0]
@spec metamerism(t(), t(), :D65 | :D50 | :A | :E, :D65 | :D50 | :A | :E) :: {:ok, float()} | {:error, Exception.t()}
Computes a metamerism index between two spectral reflectance samples under two different illuminants.
Two samples that match under illuminant a (same XYZ or very
close) may diverge visibly under illuminant b. The returned
value is the CIEDE2000 ΔE between their appearances under the
second illuminant — larger is "more metameric".
Arguments
sample_ais aColor.Spectralreflectance.sample_bis aColor.Spectralreflectance.referenceis the illuminant under which the samples match (for example:D65).testis the illuminant under which to measure divergence (for example:A— tungsten light — is the classic test illuminant).
Returns
{:ok, delta_e}withdelta_eas a non-negative float (CIEDE2000).
Examples
iex> a = %Color.Spectral{
...> wavelengths: Color.Spectral.Tables.wavelengths(),
...> values: List.duplicate(0.5, 81)
...> }
iex> {:ok, de} = Color.Spectral.metamerism(a, a, :D65, :A)
iex> de
0.0
@spec reflectance_to_xyz(t(), :D65 | :D50 | :A | :E, keyword()) :: {:ok, Color.XYZ.t()}
Converts a spectral reflectance sample under a specific
illuminant to a CIE XYZ tristimulus.
This is the formula used for paint chips, fabric swatches, printed samples, and anything else that reflects rather than emits light:
X = k · Σ R(λ) · I(λ) · x̄(λ)
Y = k · Σ R(λ) · I(λ) · ȳ(λ)
Z = k · Σ R(λ) · I(λ) · z̄(λ)where the normalising constant k = 1 / Σ I(λ) · ȳ(λ) so Y = 1.0
for a perfect (100% reflective) diffuser under the same illuminant.
Arguments
reflectanceis aColor.Spectralstruct whose values are in[0.0, 1.0].illuminant_nameis:D65,:D50,:A, or:E. Defaults to:D65.optionsis a keyword list. Supports:observer(2or10, default2).
Returns
{:ok, %Color.XYZ{}}tagged with the chosen illuminant.
Examples
iex> perfect_diffuser = %Color.Spectral{
...> wavelengths: Color.Spectral.Tables.wavelengths(),
...> values: List.duplicate(1.0, 81)
...> }
iex> {:ok, xyz} = Color.Spectral.reflectance_to_xyz(perfect_diffuser, :D65)
iex> {Float.round(xyz.x, 4), Float.round(xyz.y, 4), Float.round(xyz.z, 4)}
{0.9504, 1.0, 1.0888}
iex> {:ok, xyz} = Color.Spectral.reflectance_to_xyz(%Color.Spectral{
...> wavelengths: Color.Spectral.Tables.wavelengths(),
...> values: List.duplicate(1.0, 81)
...> }, :D50)
iex> {Float.round(xyz.x, 4), Float.round(xyz.y, 4), Float.round(xyz.z, 4)}
{0.9642, 1.0, 0.8251}
Resamples a spectral struct onto a new wavelength grid via linear interpolation. Samples outside the source range are extrapolated as zero.
Arguments
spdis aColor.Spectralstruct.gridis a list of wavelengths in nm.
Returns
- A list of values corresponding to each grid point.
@spec to_xyz( t(), keyword() ) :: {:ok, Color.XYZ.t()}
Converts a spectral power distribution to a CIE XYZ tristimulus.
This is the general formula used for emissive sources (monitors, LEDs, light bulbs):
X = k · Σ S(λ) · x̄(λ)
Y = k · Σ S(λ) · ȳ(λ)
Z = k · Σ S(λ) · z̄(λ)where the normalising constant k = 1 / Σ S(λ) · ȳ(λ) so Y = 1.0
at the source's own white point.
Arguments
spdis aColor.Spectralstruct representing the source's spectral power distribution.optionsis a keyword list.
Options
:observeris2or10. Defaults to2.:illuminanttags the resultingColor.XYZstruct. Defaults to:D65. This does not normalise the result against that illuminant — usereflectance_to_xyz/3for that case.
Returns
{:ok, %Color.XYZ{}}.
Examples
iex> d65 = Color.Spectral.illuminant(:D65)
iex> {:ok, xyz} = Color.Spectral.to_xyz(d65)
iex> {Float.round(xyz.x, 4), Float.round(xyz.y, 4), Float.round(xyz.z, 4)}
{0.9504, 1.0, 1.0888}
iex> a = Color.Spectral.illuminant(:A)
iex> {:ok, xyz} = Color.Spectral.to_xyz(a, illuminant: :A)
iex> {Float.round(xyz.x, 4), Float.round(xyz.y, 4), Float.round(xyz.z, 4)}
{1.0985, 1.0, 0.3558}