Chromatic Adaptation Transforms #
How to use chromatic adaptation transforms (CATs) in Farg to adapt colors between different illuminants.
What Is a CAT? #
A chromatic adaptation transform models how the human visual system adjusts to changes in illumination. When you move a color from one lighting condition to another (e.g., daylight to tungsten), the CAT adjusts the tristimulus values so the color appears perceptually consistent.
Each CAT is defined by a 3x3 matrix that converts XYZ values into a cone-response-like space. Adaptation scaling is applied in that space, then the result is converted back to XYZ.
Farg provides 10 CATs. The default is Bradford (when the cat-bradford feature is enabled), and
XYZ Scaling is always available as a fallback.
Quick Start #
use farg::{Cat, ColorimetricContext, Illuminant};
use farg::space::Xyz;
// A color under D65 daylight
let color = Xyz::new(0.95047, 1.0, 1.08883);
// Adapt to D50 using the default CAT (Bradford)
let d50_ctx = ColorimetricContext::new()
.with_illuminant(Illuminant::D50);
let adapted = color.adapt_to(d50_ctx);
Available CATs #
Always Available #
| Constant | Transform | Description |
|---|---|---|
Cat::XYZ_SCALING |
XYZ Scaling | Identity matrix -- scales X, Y, Z independently |
Cat::DEFAULT |
(varies) | Highest-priority enabled CAT (see below) |
Feature-Gated #
| Feature | Constant | Transform |
|---|---|---|
cat-bradford |
Cat::BRADFORD |
Bradford |
cat-cat02 |
Cat::CAT02 |
CAT02 |
cat-cat16 |
Cat::CAT16 |
CAT16 |
cat-cmc-cat97 |
Cat::CMC_CAT97 |
CMC CAT97 |
cat-cmc-cat2000 |
Cat::CMC_CAT2000 |
CMC CAT2000 |
cat-fairchild |
Cat::FAIRCHILD |
Fairchild |
cat-hunt-pointer-estevez |
Cat::HUNT_POINTER_ESTEVEZ |
Hunt-Pointer-Estevez |
cat-sharp |
Cat::SHARP |
Sharp |
cat-von-kries |
Cat::VON_KRIES |
Von Kries |
Cat::HPE is a shorthand alias for Cat::HUNT_POINTER_ESTEVEZ. The feature cat-hpe is an alias
for cat-hunt-pointer-estevez.
Enable all CATs at once with the all-cats meta-feature.
# Default features include Bradford
[dependencies]
farg = "0.4"
# Add a specific CAT
[dependencies]
farg = { version = "0.4", features = ["cat-cat16"] }
# All CATs
[dependencies]
farg = { version = "0.4", features = ["all-cats"] }
Default Priority #
Cat::DEFAULT resolves to the highest-priority enabled CAT. The priority order is:
- Bradford (
cat-bradford) -- included in default features - CAT16
- CAT02
- CMC CAT2000
- Von Kries
- Hunt-Pointer-Estevez
- Sharp
- Fairchild
- CMC CAT97
- XYZ Scaling (always available, lowest priority)
With default features enabled, Cat::DEFAULT is Cat::BRADFORD.
Using CATs with ColorimetricContext #
The CAT is one of the three components of a ColorimetricContext (along with an illuminant and an
observer). It determines how adaptation is performed when moving between white points.
use farg::{Cat, ColorimetricContext, Illuminant};
// Default context uses Bradford
let default_ctx = ColorimetricContext::new();
// Use a specific CAT
let ctx = ColorimetricContext::new()
.with_illuminant(Illuminant::D50)
.with_cat(Cat::CAT16); // cat-cat16
adapt_to: Transforming Colors #
The adapt_to method adapts a color from its current context to a target context. The target
context's CAT is used for the transform.
use farg::{Cat, ColorimetricContext, Illuminant};
use farg::space::Xyz;
let color = Xyz::new(0.95047, 1.0, 1.08883);
// Adapt to D50 with Bradford (default)
let d50_bradford = ColorimetricContext::new()
.with_illuminant(Illuminant::D50);
let adapted_bradford = color.adapt_to(d50_bradford);
// Adapt to D50 with CAT16
let d50_cat16 = ColorimetricContext::new()
.with_illuminant(Illuminant::D50)
.with_cat(Cat::CAT16);
let adapted_cat16 = color.adapt_to(d50_cat16);
// Results differ slightly because the transforms use different matrices
adapt_to is available on all CIE and physiological color spaces: Xyz, Lab, Lch, Luv,
and Lms.
with_context: Relabeling Without Transformation #
Use with_context to change a color's context metadata without transforming its values. This is
useful when you know the values are already correct for the target context.
use farg::{ColorimetricContext, Illuminant};
use farg::space::Xyz;
let color = Xyz::new(0.95047, 1.0, 1.08883);
let d50_ctx = ColorimetricContext::new()
.with_illuminant(Illuminant::D50);
let relabeled = color.with_context(d50_ctx); // No transformation
let adapted = color.adapt_to(d50_ctx); // Transforms values
Using the adapt Method Directly #
For lower-level control, you can call adapt directly on a CAT with explicit white points.
use farg::Cat;
use farg::space::Xyz;
let cat = Cat::BRADFORD;
let d65_white = Xyz::new(0.95047, 1.0, 1.08883);
let d50_white = Xyz::new(0.96422, 1.0, 0.82521);
let color = Xyz::new(0.4, 0.2, 0.1);
let adapted = cat.adapt(color, d65_white, d50_white);
Adapting to the same white point returns the original color unchanged:
use farg::Cat;
use farg::space::Xyz;
let cat = Cat::BRADFORD;
let white = Xyz::new(0.95047, 1.0, 1.08883);
let color = Xyz::new(0.4, 0.2, 0.1);
let same = cat.adapt(color, white, white);
// same == color (within floating-point precision)
Inspecting a CAT #
Name and Display #
use farg::Cat;
let cat = Cat::BRADFORD;
println!("{}", cat.name()); // "Bradford"
println!("{cat}"); // name + matrix with default precision
println!("{cat:.2}"); // name + matrix with 2 decimal places
Matrix Access #
Each CAT stores a forward matrix and its precomputed inverse.
use farg::Cat;
let cat = Cat::BRADFORD;
let matrix = cat.matrix(); // 3x3 forward transform
let inverse = cat.inverse(); // 3x3 inverse transform
// Access raw data
let row0 = matrix.data()[0]; // [0.8951, 0.2664, -0.1614]
Creating Custom CATs #
Define a custom CAT from any 3x3 matrix. The inverse is computed automatically.
use farg::Cat;
let my_cat = Cat::new("My Transform", [
[0.8951, 0.2664, -0.1614],
[-0.7502, 1.7135, 0.0367],
[0.0389, -0.0685, 1.0296],
]);
println!("{}", my_cat.name()); // "My Transform"
Using a Custom CAT in a Context #
use farg::{Cat, ColorimetricContext, Illuminant};
use farg::space::Xyz;
let my_cat = Cat::new("Custom", [
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
]);
let ctx = ColorimetricContext::new()
.with_illuminant(Illuminant::D50)
.with_cat(my_cat);
let color = Xyz::new(0.95047, 1.0, 1.08883);
let adapted = color.adapt_to(ctx);
Choosing a CAT #
| Transform | Best for |
|---|---|
| Bradford | General purpose, ICC profiles, most workflows |
| CAT16 | CAM16 color appearance model |
| CAT02 | CIECAM02 color appearance model |
| Von Kries | Simple diagonal adaptation, educational use |
| Hunt-Pointer-Estevez | Physiological cone-response research |
| Sharp | Improved sharpened cone space (Susstrunk et al.) |
| XYZ Scaling | Simplest possible adaptation, testing |
| CMC CAT2000 | CMC 2000 color appearance framework |
| CMC CAT97 | CMC 1997 color appearance framework |
| Fairchild | Fairchild color appearance research |
Bradford is the recommended default for most applications and is used by ICC color management.
Feature Flags #
| Feature | Transform | Default |
|---|---|---|
| (always available) | XYZ Scaling | Yes |
cat-bradford |
Bradford | Yes |
cat-cat02 |
CAT02 | No |
cat-cat16 |
CAT16 | No |
cat-cmc-cat97 |
CMC CAT97 | No |
cat-cmc-cat2000 |
CMC CAT2000 | No |
cat-fairchild |
Fairchild | No |
cat-hunt-pointer-estevez |
Hunt-Pointer-Estevez | No |
cat-sharp |
Sharp | No |
cat-von-kries |
Von Kries | No |
all-cats |
All 10 transforms | No |
# Default features include Bradford
[dependencies]
farg = "0.4"
# Add specific CATs
[dependencies]
farg = { version = "0.4", features = ["cat-cat16", "cat-von-kries"] }
# All CATs
[dependencies]
farg = { version = "0.4", features = ["all-cats"] }