Color.Spectral
(Color v0.11.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,:B,:C,:E,:F2,:F7,:F11) as ready-to-use SPDs.blackbody/1— a Planckian (blackbody) SPD at any colour temperature.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 a Planckian (blackbody) SPD at the given colour temperature.
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 a Planckian (blackbody) SPD at the given colour temperature.
The relative spectral power is computed from Planck's law normalised
to 100.0 at 560 nm, matching the convention used by the CIE's
Illuminant A definition (which is itself a Planckian at 2856 K).
Arguments
temperatureis the colour temperature in kelvin. Must be positive. Typical values:2700(warm white LED),3200(tungsten),4000(neutral),5000(horizon),6504(D65 equivalent),10_000(cool).
Returns
- A
Color.Spectralstruct with 81 samples at 5 nm from 380 to 780 nm.
Examples
iex> spd = Color.Spectral.blackbody(2856)
iex> length(spd.values)
81
iex> spd = Color.Spectral.blackbody(6504)
iex> {:ok, xyz} = Color.Spectral.to_xyz(spd)
iex> Float.round(xyz.y, 4)
1.0
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(illuminant_name()) :: t()
Returns a Color.Spectral struct for a named CIE illuminant.
:D65, :D50 and :A are tabulated natively at 5 nm. :B, :C,
:F2, :F7 and :F11 originate from 10 nm published values and
are linearly interpolated onto the 5 nm grid, so they should be
treated as reference-grade but not sub-10 nm accurate — in
particular the F-series emission spikes are under-represented. For
higher precision, pass a user-supplied SPD directly to to_xyz/2.
Arguments
nameis one of:D65,:D50,:A,:B,:C,:E,:F2,:F7,:F11.
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]
iex> spd = Color.Spectral.illuminant(:F7)
iex> length(spd.values)
81
@spec metamerism(t(), t(), illuminant_name(), illuminant_name()) :: {: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(), illuminant_name(), 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 one of the atoms accepted byilluminant/1(:D65,:D50,:A,:B,:C,:E,:F2,:F7,:F11). 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}