A Rust library for color modeling in illumination and engineering projects, with early JavaScript/WebAssembly support. Algorithms follow standards from the CIE, ICC, and IES. It intends to provide a comprehensive framework for spectral colorimetry:
-
Standard
Spectrum
representation- All spectra—illuminants, filters, surface reflectance, or stimuli—are defined on a fixed internal grid of 401 samples spanning 380 nm to 780 nm in 1 nm increments, using the
Spectrum
object. - Leverages the
nalgebra
linear-algebra library for efficient vector and matrix operations on spectral data, providing high-performance processing and access to advanced mathematical routines. - Imports measurements from arbitrary or irregular wavelength grids by resampling onto the internal grid using configurable interpolation methods (linear or Sprague–Karup), with optional smoothing for oversampled datasets.
- All spectra—illuminants, filters, surface reflectance, or stimuli—are defined on a fixed internal grid of 401 samples spanning 380 nm to 780 nm in 1 nm increments, using the
-
Built-in spectral datasets
- Munsell color book (Munsell feature)
- CRI TCS (Test Color Samples, with cri feature)
-
Generate spectral distributions from analytical models
- Planck’s law for blackbody radiators
- Gaussian functions for custom filter shapes
- RGB channel mixtures to approximate display pixel spectra
-
CIE standard Colorimetric Observers each represented in the
Observer
instance. Calculate associated tristimulus valuesXYZ
for spectral entities.- CIE 1931 2º,
- CIE 1964 10º,
- CIE 2015 2º,
- CIE 2015 10º,
-
Spectrally based RGB Color Spaces with transformation matrices between
Rgb
andXYZ
values for all color spaces and observers- sRGB
- Adobe RGB
- DisplayP3
-
Advanced color models
To use this library in a Rust application, run the command:
cargo add colorimetry
or add this line to the dependencies in your Cargo.toml file:
colorimetry = "0.0.6"
The easiest way to use the objects and functions in this library is through its prelude.
use colorimetry::prelude::*;
use colorimetry::illuminant::D65;
// D65 Tristimulus values, using the CIE1931 standard observer by default
let [x, y, z] = D65.xyz(None).set_illuminance(100.0).values();
approx::assert_ulps_eq!(x, 95.04, epsilon = 5E-3);
approx::assert_ulps_eq!(y, 100.0, epsilon = 5E-3);
approx::assert_ulps_eq!(z, 108.86, epsilon = 5E-3);
This library includes a range of spectral data collections, with only a minimal set enabled by default.
Additional functionality can be activated using Cargo feature flags.
Default Features
These features are enabled by default. To disable, use:cargo add colorimetry --no-default-features
-
cie-illuminants
Adds a large collection of standard illuminants (e.g., Fluorescent and LED series) beyond the D50 and D65, which are always included. -
supplemental-observers
Adds the following CIE standard colorimetric observers beyond the CIE 1931 2º Standard Observer:- CIE 1964 10° Standard Observer
- CIE 2015 Cone Fundamental based Standard Observers (2° & 10°)
Optional Features
-
munsell
Include reflection spectra for Munsell colors. -
cct
Included automatically if thecri
feature is enabled. Calculates correlated color temperatures (CCT) for illuminants. Generates a 4096-entry lookup table (each entry containing threef64
values). Memory is reserved at compile time but computed on demand. -
cri
Enables Color Rendering Index (CRI) calculations, providing Ra and R1–R14 values for illuminants.
Loads an additional 14 test color sample spectra.
Enable Features
To enable a feature, such as cri
and munsell
, use
cargo add colorimetry -F cri,munsell
or
cargo add colorimetry --features cri,munsell
Alternatively, configure features manually in your Cargo.toml
:
colorimetry = { version = "0.0.6", features = ["cri", "munsell"] }
- While the core data structure of the library is
Spectrum
, all colorimetric models operate on higher-level abstractions such asIlluminant
,Colorant
, andStimulus
, each encapsulating spectral data in a way that aligns with standard colorimetric principles. - Depending on the spectral type, the library provides specialized methods—for example:
- Correlated color temperature CCT and color rendering index CRI calculations for
Illuminant
s. - CIELAB and CIECAM color appearance model computations for
Colorant
s. - RGB conversions and colorimetric projections for
Stimulus
data.
- Correlated color temperature CCT and color rendering index CRI calculations for
- As a first step in almost any colorimetric model, Colorimetric Observers are used to compute the tristimulus values (X, Y, Z), which indirectly represent the responses of the eye's cone receptors to external optical stimuli.
The library includes multiple CIE standard observers as
Observer
instances. - Advanced color models such as CIELAB and CIECAM build on these tristimulus values to describe our color perceptions—how we see color—taking into account the state of visual adaptation and viewing conditions.
- These models also enable the estimation of perceived color differences between stimuli, making it possible to compare how colors appear across displays, printed materials, and real-world objects.
The Spectrum
struct underpins all spectral calculations in this library. It stores spectral data in a nalgebra::SVector<f64, NS>
vector, with length NS = 401
, over a wavelength domain ranging from 380 nm to 780 nm in 1 nm steps.
To perform colorimetric calculations, use either Illuminant
for light source spectra, or Colorant
for modeling surface colors, such as paints and printed inks.
A Stimulus
is used to model pixels in displays, where a combination of red, green, and blue sub-pixels are controlled to create sensations of color, directly viewed by looking at them.
Intialize from Array
use colorimetry::prelude::*;
use colorimetry::spectrum::NS;
// a black hole stimulus spectrum, using NS = 401 zero values
let black_hole_spectrum = Spectrum::new([0.0; NS]);
// the stimulus, reaching our eyes, when looking at a black hole:
let black_hole_stimulus = Stimulus::new(black_hole_spectrum);
Using data with other wavelength domains
If you have spectral data defined over a wavelength domain different from the 380-780-1 nanometers as used in this library, you can use two interpolation methods converting your data into a Spectrum
:
-
Linear interpolation
TheSpectrum::linear_interpolate
constructor takes a slice of wavelengths and a corresponding slice of spectral values. It returns aSpectrum
if both slices are of equal length and the wavelengths are ordered. -
Sprague interpolation
For smoother interpolation,Spectrum::sprague_interpolate
implements the Sprague-Karup interpolation method, commonly used in color science. This method requires that the input wavelengths be evenly spaced. It takes the domain bounds and a slice of spectral values as input and produces a high-resolutionSpectrum
aligned with the internal wavelength grid.
References
This spectral domain aligns with standards such as:- CIE 15:2004 – Colorimetry
- IES LM-79-08 – Electrical and Photometric Measurements of Solid-State Lighting Products
This 380–780 nm range is also the default domain used by the IES TM-30 Spectral Calculator.
An Illuminant
is a spectral representation of a light which hits an object, which, upon scattering on its way to our eyes, creates the sensations of color we experience.
The spectral composition of an illuminant influences the colors we see.
Illuminants can be created using spectral data, in form of a Spectrum
instance, or can be generated from various spectral models.
Alternatively, the library includes the CIE standard illuminants.
Initialize from Spectrum
To get an `Illuminant` from your spectral data, first create a `Spectrum`, for example by using one of the interpolation methods, or directly using an array. use colorimetry::prelude::*;
// create equal energy spectrum from an array, with values of 1.0.
let spectrum = Spectrum::new([1.0; 401]);
let illuminant = Illuminant::new(spectrum);
// calculate chromaticity coordinates as used in the CIE 1931 chromaticity diagram
// use `None` as argument to used the default CIE 1931 2º standard observer
let chromaticity = illuminant.xyz(None).chromaticity();
// check the values
approx::assert_abs_diff_eq!(chromaticity.x(), 0.3333, epsilon=1E-4);
approx::assert_abs_diff_eq!(chromaticity.y(), 0.3333, epsilon=1E-4);
Factory Functions
-
Planckian illuminant, a pure thermal emission based spectrum. Uses Plank's law, and takes an absolute temperature, in Kelvin, as argument.
use crate::colorimetry::prelude::*; // Plankian illuminant with a temperature of 3000 Kelvin let p3000 = Illuminant::planckian(3000.0); let chromaticity = CIE1931.xyz(&p3000, None).chromaticity(); approx::assert_abs_diff_eq!( chromaticity.x(), 0.436_935, epsilon = 1E-6); approx::assert_abs_diff_eq!( chromaticity.y(), 0.404_083, epsilon = 1E-6);
-
Generic Daylight CIE D-illuminant, generating a daylight spectrum with a characteristic correlated color temperature in the range from 4000 to 25_000 Kelvin.
-
LED illuminant, with a spectral distribution described by an analytical function, as proposed by Yoshi Ohno, as published in Optical Engineering 44(11), 2005.
-
Equal Energy Illuminant, with a uniform spectral distribution with an irradiance of 1 watt per square meter.
CIE Standard Illuminants
The following standard illuminants are available in the library using the cie-illuminants feature, which is enabled by default.
- Daylight (always included):
D65
,D50
- Incandescent Lamps (cie-illuminants):
A
- Fluorescent Lamps (cie-illuminants):
F1
,F2
,F3
,F4
,F5
,F6
,F7
,F8
,F9
,F10
,F11
,F12
- Fluorescent Lamps, F3 Series (cie_illuminants):
F3_1
,F3_2
,F3_3
,F3_4
,F3_5
,F3_6
,F3_7
,F3_8
,F3_9
,F3_10
,F3_11
,F3_12
,F3_13
,F3_14
,F3_15
- LED Lamps (cie_illuminants):
LED_B1
,LED_B2
,LED_B3
,LED_B4
,LED_B5
,LED_BH1
,LED_RGB1
,LED_V1
Correlated Color Temperature (CCT)
Illuminants are typically characterized by their correlated color temperature (CCT), expressed in Kelvin (K), and by their tint, which describes the chromaticity deviation from the Planckian (blackbody) locus.
The CCT is defined as the temperature of the Planckian (ideal blackbody) radiator whose perceived color most closely matches that of the test light source, when viewed under identical conditions. Because many real-world light sources (e.g., fluorescent or LED lamps) do not emit light that exactly matches any blackbody radiator, their color temperature is termed correlated rather than exact.
CCT is not derived directly from spectral data, but is calculated using the chromaticity coordinates by finding the closest point on the Planckian locus—usually by minimizing the Euclidean or perceptual distance in color space1.
In this library, an advanced, high accuracy, iterative Robertson's method is used to calculate both values.
Here we us Plank's law, to create an illuminant spectrum, and check its temperature and tint.
# #[cfg(feature = "cct")]{
// this example requires `cct` feature enabled
use crate::colorimetry::prelude::*;
// Plankian illuminant with a temperature of 3000 Kelvin
let p3000 = Illuminant::planckian(3000.0);
// calculate CCT and Duv for this illuminant
// unwrap OK as we know values should be approximately 3000.0, and 0.0
let [cct, duv] = p3000.cct().unwrap().values();
approx::assert_abs_diff_eq!( cct, 3000.0, epsilon = 1E-4);
approx::assert_abs_diff_eq!( duv, 0.0, epsilon = 1E-6);
# }
Correlated Color Rendering Index (CRI)
The CIE Color Rendering Index (CRI), including the general color rendering index (Rₐ) and the individual special color rendering indices (R₁ through R₁₅), can be calculated using the cri
method, which follows the procedure specified by the CIE2.
# #[cfg(all(feature = "cri", feature = "cie-illuminants"))]{
// this example requires `cri` and `cie-illuminants` features enabled
use crate::colorimetry::prelude::*;
let f3_11 = CieIlluminant::F3_11.illuminant();
let cri = f3_11.cri().unwrap();
let expected_ra = 78.0;
approx::assert_abs_diff_eq!(cri.ra(), expected_ra, epsilon = 1.0);
let expected_values = [
90.0, 86.0, 49.0, 82.0, 81.0, 70.0, 85.0, 79.0, 24.0, 34.0, 64.0, 50.0, 90.0, 67.0,
];
approx::assert_abs_diff_eq!(
cri.values().as_ref(),
expected_values.as_ref(),
epsilon = 1.0
);
# }
A Colorant
represents a color filter or surface (such as a color patch), defined by dimensionless spectral values ranging from 0.0 to 1.0.
Each value corresponds to the proportion of light at a given wavelength that is not absorbed:
0.0
→ Full absorption (no transmission or reflection)1.0
→ Full transmission or reflection (no absorption)
This model is commonly used in color science to describe the spectral behavior of materials and follows conventions used in CIE colorimetry.
In color models, a Colorant
spectrum in not used directly, but is always associated with an illuminant;
without an illuminant, objects appear black.
Initialize from Spectrum
A Colorant
is a wrapper around Spectrum and can be created using its new method, which accepts a Spectrum and ensures that all values lie within the range 0.0 to 1.0.
If any value falls outside this range, the constructor returns an error.
Factory Functions
The library defines different model based factory functions. Here are a couple of examples.
use crate::colorimetry::prelude::*;
// Create a perfect white `Colorant` or color patch, with no absorption.
let white = Colorant::white();
// Create a gray neutral colorant with 30% reflectance at all wavelengths
let gray = Colorant::gray(0.3);
// A perfect absorber absorbing all light.
let black = Colorant::black();
// A `top_hat` colorant or rectangular bandfilter, defined by a center wavelength,
// and a width, both expressed in units of nanometer or meters.
let green_mono = Colorant::top_hat(550.0, 1.0);
// A `gaussian` shaped colorant, defined by a center values, and a standard deviation
// `sigma` value, with a peak value of 1.0.
let red = Colorant::gaussian(610.0, 5.0);
Mixing and Adding
Colorant
s support several operations useful for simulating physical interactions with light:
-
Multiplication models subtractive mixing, such as placing multiple filters in sequence or layering ink and pigment. For example, multiplying two
Colorant
s simulates how their combined spectral transmissions reduce the overall light passing through. -
Addition is helpful in constructing synthetic spectral functions—such as building up a custom filter shape by combining multiple Gaussians. Any resulting values above
1.0
are clipped to1.0
. -
Scalar multiplication adjusts the transmission intensity of a
Colorant
. This can be used to simulate partial transparency or adjust the concentration of a dye. Resulting values are clamped to the valid[0.0, 1.0]
range: values above1.0
become1.0
, and those below0.0
become0.0
.
CIELAB
The [`Colorant::cielab`] method calculates a colorant's CIELAB values. Here is an example calculating the CIELAB coordinates for a perfect white colorant: use crate::colorimetry::prelude::*;
let colorant = Colorant::white();
// use default (None) D65 illuminant and default CIE 1931 standard observer (second None)
let [l, a, b] = colorant.cielab(None, None).values();
approx::assert_abs_diff_eq!(l, 100.0, epsilon = 1E-4); // L* should be 100 for white
approx::assert_abs_diff_eq!(a, 0.0, epsilon = 1E-4); // a* should be 0 for white
approx::assert_abs_diff_eq!(b, 0.0, epsilon = 1E-4); // b* should be 0 for white
A Stimulus
wraps a Spectrum
representing the spectral power distribution of light as it arrives at an observer or sensor. This could be a pixel in a camera sensor, or a set of photoreceptors in the human eye. The spectral data is expressed in physical radiometric terms, with units corresponding to luminance: candelas per square meter (cd/m²), integrated over the visible range.
A Stimulus
may describe:
- Emitted light from a self-luminous source, such as a display pixel
- Reflected light from an object surface element illuminated by a known source
- Transmitted light after passing through a wavelength-selective medium, such as a colored filter element
In all cases, the stimulus encapsulates the final spectral signal available for visual or digital perception, after any combination of emission, reflection, or transmission events.
Initialize from Spectrum
A Stimulus
is a wrapper around Spectrum and can be created using its new method, which accepts a Spectrum and ensures that all values lie within the range 0.0 to 1.0.
If any value falls outside this range, the constructor returns an error.
Factory functions
-
Stimulus::from_srgb
, andStimulus::from_rgb
, create aStimulus
of a set of RGB pixel values. The first takes threeu8
arguments, while the second uses aRgb
object as argument. This function allows calculating the perceived color difference between different observers, from the perspective of a single observer.use colorimetry::prelude::*; let red = Stimulus::from_srgb(255, 0, 0); let red_chromaticity = CIE1931.xyz(&red, None).chromaticity(); approx::assert_abs_diff_eq!( red_chromaticity.to_array().as_ref(), &[0.64, 0.33].as_ref(), epsilon = 1E-5 );
In colorimetry, color perception is modeled as the response of the human visual system to spectral stimuli. Human vision is trichromatic, based on the relative excitations of three cone types (L, M, and S) in the retina. These physiological responses are abstracted in the CIE XYZ color space as X, Y, and Z tristimulus values.
Tristimulus values are computed by integrating a spectral power distribution with a set of three color matching functions (CMFs), which represent the average spectral sensitivity of the human eye. A set of CMFs defines a standard observer.
The primary observer used in most applications is the CIE 1931 2º Standard Observer, derived from color matching experiments with foveal (central) vision. This observer is represented in this library by a static Observer
instance: CIE1931
.
With the supplemental-observers
feature enabled, the library also includes:
CIE1964
and theCIE1964
standard observer for larger visual fields,CIE2015
andCIE2015_10
cone fundamentals–based observers (CIE 170-2:2015).
All content ©2025 Harbers Bik LLC, and licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license LICENSE-MIT or http://opensource.org/licenses/MIT
at your option.
Unless you explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.