Skip to main content

Crate colr

Crate colr 

Source
Expand description

§colr

Latest Version docs Minimum Supported Rust Version

A general purpose, extensible color type unifying storage, channel layouts, and color spaces at the type level.

Color spaces, transfer functions, chromatic adaptation, and channel layouts are encoded in the type system. Mixing an sRGB color with a Display P3 color is a compile error unless you convert explicitly. All colorimetric matrices are derived at compile time. Colors are generic over their storage representation, which is itself generic over its channel layout.

§Usage

use colr::{Color, LinearSrgb, Oklab, Oklch, Srgb, Xyz};

// Construct an sRGB color. Defaults to RGBA layout.
let srgb: Color<[f32; 4], Srgb> = Color::new([0.8, 0.4, 0.2, 1.0]);

// Decode to linear light before converting. The transfer function must be
// removed before the RGB-to-XYZ matrix is applied.
let linear: Color<[f32; 4], LinearSrgb> = srgb.decode();

// Convert to Oklab for perceptual work. Each step is an explicit From.
// All matrices are compile-time constants. Xyz defaults to D65.
// Alpha is carried through each conversion unchanged.
let xyz: Color<[f32; 4], Xyz> = linear.into();
let lab: Color<[f32; 4], Oklab> = xyz.into();

// Pull out chroma and hue, reduce chroma, put it back.
let lch: Color<[f32; 4], Oklch> = lab.into();
let [l, c, h, a] = lch.inner();
let muted: Color<[f32; 4], Oklch> = Color::new([l, c * 0.5, h, a]);

// Convert back. XYZ-to-RGB may produce out-of-range values for wide-gamut
// colors; clamp if targeting a display, or pass through for tone mapping.
let oklab_out: Color<[f32; 4], Oklab> = muted.into();
let xyz_out: Color<[f32; 4], Xyz> = oklab_out.into();
let linear_out: Color<[f32; 4], LinearSrgb> = xyz_out.into();
let final_color: Color<[f32; 4], Srgb> = linear_out.encode();

§Color models and spaces

§Perceptual spaces

TypeDescription
LabCIE 1976 L*a*b* parameterized by reference white W
LChPolar form of L*a*b*
OklabOklab (Ottosson 2020); improved hue linearity over Lab
OklchPolar form of Oklab

Lab and LCh are parameterized by illuminant: Lab<D65>, Lab<D50>, and so on, with D65 as the default. Oklab and Oklch have D65 baked into the specification.

All four types accept a const OFFSET parameter that controls where the color channels begin within a four-channel array. The default OFFSET = 0 places color channels at indices 0, 1, 2 and alpha at 3. OFFSET = 1 places alpha at 0 and color channels at 1, 2, 3. This allows in-place processing of ARGB-layout buffers without swizzling: reinterpret the buffer as Oklab<1>, operate, reinterpret back.

§RGB spaces

AliasPrimariesTransfer functionWhite
SrgbsRGB/Rec709sRGBD65
LinearSrgbsRGB/Rec709LinearD65
Rec709sRGB/Rec709Rec. 709D65
DisplayP3P3sRGBD65
LinearP3P3LinearD65
Hdr10Rec. 2020PQ (ST 2084)D65
HlgRec. 2020HLG (BT.2100)D65
LinearRec2020Rec. 2020LinearD65
AcesCgAP1LinearACES
Aces2065AP0LinearACES
AcesCcAP1ACEScc logACES
AcesCctAP1ACEScct log+toeACES
ProPhotoProPhotogamma 1.8D50
LinearProPhotoProPhotoLinearD50
DciP3DCI-P3gamma 2.6DCI

Each alias defaults to RGBA layout. Alternate layouts are selected with the type parameter: Srgb<Rgb>, Srgb<Bgra>, and so on.

§XYZ

Xyz<W> is the CIE XYZ tristimulus space parameterized by illuminant W, defaulting to Xyz<D65>. It serves as the connection space for all RGB and perceptual conversions.

Conversions between Xyz<W1> and Xyz<W2> apply Bradford chromatic adaptation by default, computed at compile time. The adaptation method can be overridden with .adapt::<W2, CAT>().

§Luma

Luma<P, TF> is a single-channel luminance model derived from primary set P with transfer function TF. LumaAlpha<P, TF, A> adds an alpha channel with alpha state A (defaults to Straight). The luma weights are the Y row of the primaries’ RGB-to-XYZ matrix, so they vary by primary set.

§YCbCr

YCbCr<P, TF, L> is Y’CbCr derived from RGB primaries P and transfer function TF. The conversion matrix is derived from the primaries’ luma weights. Used in JPEG, H.264, H.265, and broadcast video. Layout defaults to Ycbcr.

§Spectral

Spectral<N, G, K> carries N spectral samples over wavelength grid G with physical kind K. Three kinds are defined:

Kind markerPhysical quantityConversion to XYZ
IsRadianceSpectral radianceSPD dot CMF
IsReflectanceSpectral reflectance [0,1](illuminant * reflectance) dot CMF
IsTransmittanceSpectral transmittance [0,1]same as reflectance

IsBispectral is defined as a kind marker to close the taxonomy but does not implement BackingStore; bispectral (fluorescent) storage is reserved for a future explicit design.

Standard grids include Grid380_780_10nm (41 bands), Grid400_700_10nm (31 band ICC standard), and Grid380_780_5nm (81 bands), all evaluated against the CIE 1931 observer.

Blanket alias traits Radiance, Reflectance, Transmittance, and Bispectral are provided for use in bounds.

§Device-dependent

TypeDescription
CmykDevice CMYK. Profile required for XYZ conversion
DeviceRgbDevice RGB. Profile required for XYZ conversion

Conversions for these types use FromDevice rather than From. See the Device-dependent colors section.

If a color space or model from an open standard is not listed above, please open an issue.

§Conversions

All conversions use From/Into. XYZ-to-RGB is infallible and may return out-of-range channel values for wide-gamut colors. Clamp with .clamp(0.0, 1.0) for display output, or pass the values directly to a tone mapper.

let linear: Color<[f32; 4], LinearSrgb> = src.into();
let display_ready: Color<[f32; 4], LinearSrgb> = linear.clamp(0.0, 1.0);

Encode and decode transfer functions with .encode() and .decode(). Adapt a color between XYZ white points with .adapt::<W2, CAT>() on a Color<_, Xyz<W1>>.

§Device-dependent colors

Cmyk and DeviceRgb<L> are device-dependent color models whose relationship to XYZ is defined by a runtime ICC profile. Because no compile-time matrix describes them, they do not implement From. Conversions use FromDevice from colr::device:

pub trait FromDevice<Src>: Sized {
    type Profile;
    fn from_device(src: Src, profile: &Self::Profile) -> Self;
}

The Profile associated type is set by each impl. A profile type defined externally can add FromDevice impls for Cmyk or DeviceRgb without changes to this crate.

§Channel layouts

Layout is a type parameter, not a runtime tag. The compiler distinguishes RGBA from BGRA and will not accept one where the other is expected.

TypeOrder
RgbaR G B A
BgraB G R A
ArgbA R G B
AbgrA B G R
RgbR G B
BgrB G R

Layout reorders are infallible and compile to a shuffle with no arithmetic:

let bgra: Color<[f32; 4], Srgb<Bgra>> = rgba.swizzle();

§Chromatic adaptation

Four methods are provided. Bradford is used automatically by all built-in conversions and is the correct default for standard RGB work.

TypeStandard
BradfordICC, ACES, CSS Color Level 4
Cat02CIECAM02, ICC v4 appearance models
Cat16CAM16
VonKriesLegacy and reference

To adapt a color explicitly:

let xyz_d50: Color<[f32; 3], Xyz<D50>> = xyz_d65.adapt::<D50, Bradford>();

Custom adaptation matrices can be computed at compile time with operations::adapt::adapt.

§Color difference

MethodSpaceFormula
delta_e76LabCIE 1976 (Euclidean)
delta_e2000LabCIEDE2000 (CIE 142-2001)
delta_e_okOklabEuclidean in Oklab
let delta: f32 = a.delta_e_ok(b);

§Tone mapping

Tone mapping compresses unbounded scene-linear light [0, inf) down to bounded display-linear light [0, 1].

TypeDescription
AcesNarkowiczACES filmic curve approximation (per-channel)
KhronosPbrNeutralglTF 3D Commerce standard, hue-preserving (RGB-coupled)
ReinhardClassic x / (x + 1) curve (per-channel)
let display_linear = scene_linear.tonemap::<KhronosPbrNeutral>();

§Quantization and dithering

Converting continuous f32 signals to 8-bit u8 integers without dithering causes visible banding. colr provides a way to convert with a dithering offset as well as straight. The to_u8 method performs naive quantization while to_u8_dithered method dithers the color channels leaving the alpha channel untouched.

// Generate two independent uniform random floats in [0, 1)
let dither = (u1 - u2) / 255.0;
let pixel: Color<[u8; 4], Srgb> = color.to_u8_dithered(dither);

§Alpha compositing

Blend operations are defined on Color<[f32; 4], Rgb<P, Linear, L>> and require premultiplied alpha. Compositing in non-linear space is incorrect.

let result = src.premultiply().over(dst.premultiply()).unpremultiply();
MethodDescription
premultiplyMultiply color channels by alpha
unpremultiplyDivide color channels by alpha
overPorter-Duff over (both premultiplied)

§no_std support

no_std is available with the libm feature. Either std or libm must be enabled.

[dependencies]
colr = { version = "0.2", default-features = false, features = ["libm"] }

To support both std and no_std builds in a library crate:

[features]
default = ["std"]
std     = ["colr/std"]
libm    = ["colr/libm"]

[dependencies]
colr = { version = "0.2", default-features = false }

§Optional features

std is enabled by default and selects standard library math functions. The remaining features are opt-in.

FeatureDescription
glamAdds VecN types as valid color storage
libmUses libm math functions for no_std targets

§Adding a custom primary set

Implement Primaries for your type. All matrices are derived at compile time from the chromaticity coordinates and white point you supply.

use colr::primaries::{Primaries, derive_rgb_to_xyz};
use colr::illuminant::{D65, Illuminant};
use colr::math::Mat3;

pub struct CustomPrimaries;

const CUSTOM_TO_XYZ: Mat3 = derive_rgb_to_xyz(
    [0.680, 0.320],  // red
    [0.265, 0.690],  // green
    [0.150, 0.060],  // blue
    <D65 as Illuminant>::WHITE_POINT_XYZ,
);

impl Primaries for CustomPrimaries {
    type Native = D65;
    const R: [f32; 2] = [0.680, 0.320];
    const G: [f32; 2] = [0.265, 0.690];
    const B: [f32; 2] = [0.150, 0.060];
    const TO_XYZ_NATIVE:  Mat3 = CUSTOM_TO_XYZ;
    const FROM_XYZ_NATIVE: Mat3 = Mat3::invert(&CUSTOM_TO_XYZ);
}

§colr-types

The colr-types crate provides the color model ZSTs and marker traits without Color<S, M> or its operations. Depend on it directly when you need to parameterize on color model at the type level without pulling in the full operation set, for example in a GPU backend that manages its own storage.

§Design notes

All colorimetric matrices are derived in const fn at compile time, including RGB-to-XYZ, chromatic adaptation, and composed primaries-to-primaries transforms. There is no matrix inversion or multiplication at runtime.

Color<S, M> is #[repr(transparent)] over its storage type. The color model is carried only as PhantomData. There is no runtime memory overhead from the type-level bookkeeping.

§Minimum Supported Rust Version

The minimum supported version of Rust is 1.85. Edition 2024 requires Rust 1.85. Inline const {} blocks, used to evaluate transform matrices at compile time in generic contexts, stabilized in Rust 1.82.

§Contributing

This crate is early in development. Consider raising an issue if it doesn’t fit your needs, is missing functionality, or is unergonomic in any way.

Contributions are welcome for any color space or transfer function defined in an open standard. If a standard space is missing, please open an issue before implementing it so the approach can be agreed on first. Spaces from proprietary or closed formats belong in a separate crate.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work, as defined in the Apache-2.0 license, shall be dual licensed as below, without any additional terms or conditions.

§License

Licensed under either of

at your option.

Modules§

chromatic_adaptation
Chromatic adaptation transforms XYZ tristimulus values from one illuminant to another.
decode
Signal decoding (EOTF): encoded signal to linear light.
device
Device-dependent color conversion trait.
encode
Signal encoding from linear light to a stored signal value.
illuminant
CIE standard illuminants and reference white points.
layout
Channel layout types for RGB, luma, and YCbCr color models.
math
Compile-time matrix arithmetic (Mat3) and floating-point math abstraction (Float).
model
Color model type definitions.
observer
Standard colorimetric observers.
primaries
RGB primary sets.
tonemap
Display Rendering Transforms, also known as tone mapping operators. The latter was chosen for simplicity.
transfer
Transfer functions.

Structs§

Color
A color value in color model M backed by raw type S.
LCh
CIE LCh*, the polar form of CIELab.
Lab
CIE 1976 Lab* color space under reference white W.
Oklab
Oklab perceptual color space by Björn Ottosson, 2020.
Oklch
Oklch, the polar form of Oklab.
Premultiplied
Color channels have been multiplied by alpha.
Straight
Alpha channel is independent of the color channels.
Xyz
CIE XYZ tristimulus space normalized to Y = 1 under illuminant W.

Traits§

AlphaState
Marks how the alpha channel relates to the color channels.
BackingStore
A marker trait which implies S can act as storage for a model.
ChannelMap
Maps all N channels to their storage indices.

Type Aliases§

Aces2065
ACES 2065-1 archival space, linear AP0 under the ACES white point. Layout defaults to Rgba.
AcesCc
ACEScc logarithmic encoding of AP1. Layout defaults to Rgba.
AcesCct
ACEScct quasi-logarithmic encoding of AP1. Layout defaults to Rgba.
AcesCg
ACEScg working space, linear AP1 under the ACES white point. Layout defaults to Rgba.
DciP3
DCI-P3 for theatrical projection. Layout defaults to Rgba.
DisplayP3
Display P3 with sRGB transfer function. Layout defaults to Rgba.
Hdr10
Rec. 2020 with PQ transfer function for HDR10. Layout defaults to Rgba.
Hlg
Rec. 2020 with HLG transfer function. Layout defaults to Rgba.
LinearP3
Linear-light Display P3. Layout defaults to Rgba.
LinearProPhoto
Linear-light ProPhoto. Layout defaults to Rgba.
LinearRec2020
Linear-light Rec. 2020. Layout defaults to Rgba.
LinearSrgb
Linear-light sRGB. Layout defaults to Rgba.
ProPhoto
ProPhoto ROMM RGB. Layout defaults to Rgba.
Rec709
Rec. 709 color space per ITU-R BT.709-6. Layout defaults to Rgba.
SpectralColor31Radiance
Spectral radiance on the 31-band ICC grid (400-700 nm, 10 nm).
SpectralColor31Reflectance
Spectral reflectance on the 31-band ICC grid (400-700 nm, 10 nm).
SpectralColor31Transmittance
Spectral transmittance on the 31-band ICC grid (400-700 nm, 10 nm).
SpectralColor36Radiance
Spectral radiance on the 36-band Stam rendering grid (380-730 nm, 10 nm).
SpectralColor36Reflectance
Spectral reflectance on the 36-band Stam rendering grid (380-730 nm, 10 nm).
SpectralColor36Transmittance
Spectral transmittance on the 36-band Stam rendering grid (380-730 nm, 10 nm).
SpectralColor41Radiance
Spectral radiance on the 41-band full-visible grid (380-780 nm, 10 nm).
SpectralColor41Reflectance
Spectral reflectance on the 41-band full-visible grid (380-780 nm, 10 nm).
SpectralColor41Transmittance
Spectral transmittance on the 41-band full-visible grid (380-780 nm, 10 nm).
SpectralColor81Radiance
Spectral radiance on the 81-band CIE standard grid (380-780 nm, 5 nm).
SpectralColor81Reflectance
Spectral reflectance on the 81-band CIE standard grid (380-780 nm, 5 nm).
SpectralColor81Transmittance
Spectral transmittance on the 81-band CIE standard grid (380-780 nm, 5 nm).
Srgb
sRGB color space per IEC 61966-2-1:1999. Layout defaults to Rgba.