Expand description
CPU-only, unclipped color conversions. Named RGB spaces and XyzD65 use D65. Custom D50 colorants can be explicitly adapted to D65. Matrices are row-major and multiply column vectors. Alpha is not involved.
§xgpui-color
Color science extension for xgpui, also usable independently. Convert RGB spaces, map colors into gamut, sample gradients and reuse the same color math in WGSL. The runtime uses only Rust’s standard library: no GPUI, window system, GPU or shader compiler is required.
§Dependency
[dependencies]
xgpui-color = "0.2.1"Rust imports use xgpui_color.
§Convert a color
use xgpui_color::{EncodedRgb, RgbSpace};
let encoded = EncodedRgb::new(RgbSpace::Srgb, [0.2, 0.5, 0.8])?;
let p3 = encoded.decode()?.convert(RgbSpace::DisplayP3)?;
let perceptual = p3.to_oklab()?.to_oklch()?;| Type | Represents |
|---|---|
EncodedRgb, LinearRgb | Encoded or linear RGB with explicit primaries. |
RgbSpace | sRGB, Display P3, Rec.2020 or Adobe RGB (1998). |
XyzD65, Oklab, Oklch | Intermediate and perceptual color spaces. |
LinearRgba | Linear color with validated straight alpha. |
CPU calculations use f64, D65 and finite extended RGB/XYZ values. Conversion
does not automatically clip or map gamut. Rec.2020 uses its SDR transfer
function. Adobe RGB (1998) uses the exact 563/256 decoding exponent and
256/563 encoding exponent, including sign-preserving extended channels; it does
not use the sRGB transfer curve. PQ, HLG and white-point adaptation are outside
this API.
§Adobe RGB (1998)
use xgpui_color::{EncodedRgb, GamutMapping, RgbSpace};
let adobe = EncodedRgb::new(RgbSpace::AdobeRgb1998, [0., 1., 1.])?.decode()?;
let extended_p3 = adobe.convert(RgbSpace::DisplayP3)?;
assert!(extended_p3.components()[0] < 0.); // This cyan is outside P3.
let display_p3 = adobe.map_gamut(RgbSpace::DisplayP3, GamutMapping::ReduceChroma)?;Conversion and gradients retain extended values until explicit mapping. Adobe RGB and P3 do not fully contain one another. Native image/shader producers still use bounded linear P3; this math support does not widen those storage or output contracts. See the media supplied-pixel API for explicit mapping into P3.
§Gamut mapping and gradients
LinearRgb::map_gamut explicitly chooses clipping or chroma reduction.
LinearGradient samples two stops in linear RGB, OkLab or OkLCh, with explicit
hue interpolation. Map the sampled color before associating alpha for output.
use xgpui_color::{
ColorInterpolation, GamutMapping, LinearGradient, LinearRgb, LinearRgba, RgbSpace,
};
let red = LinearRgba::new(LinearRgb::new(RgbSpace::DisplayP3, [1., 0., 0.])?, 1.)?;
let blue = LinearRgba::new(LinearRgb::new(RgbSpace::DisplayP3, [0., 0., 1.])?, 0.5)?;
let gradient = LinearGradient::new(
[0., 0.], [320., 0.], [red, blue], RgbSpace::DisplayP3,
ColorInterpolation::Oklab,
)?;
let midpoint = gradient.sample([160., 0.])?;
let output = midpoint
.map_gamut(RgbSpace::DisplayP3, GamutMapping::ReduceChroma)?
.premultiplied();This is a deterministic mapping policy, not ICC relative colorimetric or CSS
local MINDE. Invalid or nonfinite values return ColorError.
§WGSL helpers
WGSL_COLOR_MATH contains resource-free WGSL helpers with the gamut_ prefix.
Prepend it once to your shader source; it declares no entry point or bindings.
The GPU contract uses finite f32 inputs and validated space/policy IDs.
Space IDs are stable: 0 = sRGB, 1 = Display P3, 2 = Rec.2020, 3 = Adobe RGB (1998).
Mapping IDs are 0 = clip and 1 = fixed-lightness/hue chroma reduction.
These functions do not change a renderer’s output color space. Use the explicit
linear-P3 contract when integrating wide output with xgpui-shader or
xgpui-gamut.
§Documentation and validation
The color guide covers types, dependency boundaries and the color/gamut lab. Refer to the source for the public API and numerical contracts.
Conversions follow CSS Color 4 reference conversions and Björn Ottosson’s OkLab definition. Tests cover CPU round trips, Naga WGSL validation/translation and native Metal comparisons. Shader translation alone does not verify native D3D11 execution. Metal and Naga are development dependencies only.
§Adobe RGB numerical regression criteria
Committed oracles use 120-digit Python Decimal arithmetic on exact binary inputs. Primary matrices are independently solved from xy chromaticities and D65 using pivoted Gaussian elimination. Transfer oracles use exact rational exponents; they are independent of the Rust and WGSL implementation constants.
- CPU tests cover all 65,536 unsigned 16-bit transfer codes, signed zero, subnormals, underflow, overflow rejection, adjacent floats at gamut boundaries, and 21,296 scaled primary-conversion vectors.
- Scalar error budgets account for binary64 rounding of the reciprocal exponent,
powfevaluation and underflow. Matrix budgets scale with the matrix norm and input magnitude, including cancellation. - Native Metal checks 1,060 independent transfer cases per compiler mode. Acceptance is relative: eight binary32 epsilons (0.96 ppm) with fast math off, 64 epsilons (7.63 ppm) with fast math on, plus an eight-subnormal rounding floor. Transfer endpoints must be exact. All nonzero GPU reference results are normal binary32 values; GPU subnormal behavior is outside these vectors.
- Media tests check exact binary16 patterns near black and at midtones, including intentional storage underflow and fractional alpha. Adobe primary/mip checks permit at most one binary16 representable step.
Reproduce both CPU/GPU and media FP16 oracle fixtures from the repository root:
python3 scripts/generate_adobe_rgb_references.py --checkThe generator requires Python 3.11+ and rustfmt. Runtime evidence is tracked per backend; numerical test thresholds are acceptance criteria for the covered domains rather than a cross-device bit-identity promise.
§Project
Repository · Release preparation · Website. Licensed under Apache-2.0.
This extension uses its own 0.2.0 version, separate from xgpui framework
1.0.0 and the upstream GPUI baseline.
§Custom RGB metadata
RgbDefinition::new_d65(matrix, curves) validates a row-major linear RGB →
relative XYZ/D65 matrix and three independent RgbCurve values (Linear,
Srgb, or positive Gamma). decode_to_xyz_d65 retains finite extended RGB
and rejects conversion overflow. Matrices must be nonsingular, reasonably
conditioned and normalized to D65 white (Y=1); white admission permits at most
3/65536 absolute error per XYZ channel for quantized colorants. This metadata
admission bound is not the numerical accuracy promised by conversions.
new_d65 accepts already-adapted colorants. Alternatively,
RgbDefinition::new_with_white_point(matrix, curves, WhitePoint::D50) explicitly
Bradford-adapts D50 colorants to D65 once. The matrix must sum to the declared
white within the colorant quantization bound. That bound is propagated through
the adaptation matrix; colorants are never silently renormalized.
BradfordAdaptation::new(source, destination) also adapts relative XYZ or colorant
matrices directly between WhitePoint::D50 and WhitePoint::D65. Same-white
adaptation preserves finite input exactly, including signed zero. Other routes
retain finite extended coordinates and reject overflow. The white chromaticities
and Bradford convention follow CSS Color 4:
D50 xy=(0.3457,0.3585), D65 xy=(0.3127,0.3290), both normalized to Y=1.
This is not ICC rendering-intent handling. A future adapter must resolve profile PCS normalization, encoded illuminant values and existing adaptation tags before using this API; a profile’s media-white label alone is insufficient. These two reference whites do not represent every custom or encoded ICC white. Sampled and parametric curves and optional ICC parsing are described below. Alpha and file decoding stay outside this color API.
§Sampled and parametric decoding
SampledCurve::new(Vec<u16>) accepts 2–65,536 nondecreasing samples. Decoding
interpolates continuously in f64 between normalized entries; it does not round
input to an integer lookup index. Tables are immutable shared allocations.
ParametricCurve::new([g,a,b,c,d,e,f]) evaluates (a*x+b)^g+e for x>=d, or
c*x+f below it. It validates finite coefficients, positive exponent/slope,
nonnegative lower slope, power domain and finite endpoints. Quantized branch
locations and discontinuities are retained. Output is bounded to 0–1; nonfinite
arithmetic is rejected before clipping. It never substitutes an approximate
named curve. These two curve types require bounded 0–1 inputs; existing named
linear/sRGB/gamma curves retain their signed-extension behavior.
RgbCurve and RgbDefinition are now Clone, with borrowed evaluation/accessors.
Sample tables remain shared when metadata is cloned.
§Optional ICC parsing adapter
Enable xgpui-color’s icc feature to use icc::IccRgbProfile. Default builds
still have no runtime dependencies. The optional moxcms dependency only parses;
all curve evaluation, adaptation and conversion use this crate’s f64 core.
use xgpui_color::icc::IccRgbProfile;
// Profile bytes are extracted by the application; parse on its admitted worker.
let profile = IccRgbProfile::parse(&profile_bytes)?;
let rgb = profile.into_rgb_definition();
// Supply rgb as custom metadata alongside decoded pixels to xgpui-media.The initial subset is deliberately strict: ICC v2/v4 RGB input/display profiles, XYZ PCS with the standard encoded D50 illuminant, matrix colorants, monotone u16 curves (including linear/gamma encodings), and parametric function types 0, 3 and 4. Parametric coefficients must have magnitude below 128 so their s15Fixed16 values remain exact through the parser’s f32 representation. The adapter rejects LUTs, CMYK/gray, device links/output profiles, CICP, unrecognized tags and unsupported curve semantics. Informational description/copyright/device text and chromaticity tags are permitted. This is not general ICC support.
Admission caps: 512 KiB profile bytes, 32 tags, 65,536 entries per curve and 4 KiB per informational tag. Preflight checks header size, tag bounds, duplicates, partial overlap and curve counts before calling the parser. Exact shared tag ranges are permitted. The dependency’s own parser limits are also configured. Parsing is synchronous and bounded, with no implicit thread creation; applications own worker admission and missing-profile policy. This is not a fuzzing/security certification.
Matrix tags already describe PCS colorants. The adapter uses the encoded header
PCS white, adapts once to D65 and does not reapply chad or substitute media
white. It provides no rendering-intent selection, black-point compensation,
profile-name inference or file decoding. decode_to_pcs exposes the original
relative XYZ PCS for validation/integration; rgb_definition supplies the
adapted custom definition used by media ingestion.
Structs§
- Bradford
Adaptation - Linear chromatic adaptation only. No clipping, transfer curves or alpha. D50/D65 agree with CSS Color 4’s Bradford conversion convention.
- Color
Error - Invalid input or a conversion whose result cannot be represented as finite f64.
- Encoded
Rgb - Finite encoded RGB, possibly outside [0,1]; no implicit gamut mapping.
- Linear
Gradient - Two-stop gradient reference. Coordinates are in caller-defined common units. Stops are fixed at 0 and 1; values beyond them extend the endpoint colors. Coincident endpoints select the second stop. No gamut mapping is implicit.
- Linear
Rgb - Finite linear RGB, possibly outside [0,1]; no implicit gamut mapping.
- Linear
Rgba - Straight-alpha CPU color; RGB may be extended, alpha must be in [0,1].
- Oklab
- OkLab [L, a, b], with nominal SDR L in 0..1; extended coordinates are retained.
- Oklch
- OkLCh: lightness, nonnegative chroma, hue in degrees normalized to [0,360). Exact zero chroma has canonical hue zero. Near-neutral hues are retained.
- Parametric
Curve - Bounded piecewise curve: (ax+b)^g+e for x>=d, cx+f otherwise. Coefficients are [g,a,b,c,d,e,f]. Serialized discontinuities are retained; no substitution of ideal named curves. Finite output is clipped to 0..1.
- RgbDefinition
- Validated custom RGB definition with relative XYZ/D65 colorants.
Matrix rows multiply column RGB vectors. The XYZ reference white has Y=1.
A profile matrix may encode a neutral ramp that differs from that reference.
Use
new_with_white_pointto explicitly adapt D50 colorants;new_d65accepts already-adapted colorants. Adaptation must not be applied twice. This is neither an ICC profile nor a display calibration descriptor. - Sampled
Curve - Owned monotone normalized u16 samples, interpolated continuously in f64.
- XyzD65
- Relative XYZ with D65 white; components are unclipped.
Enums§
- Color
Interpolation - Gamut
Mapping - Explicit policies for producing bounded SDR RGB. Neither is an ICC intent.
- HueInterpolation
- RgbCurve
- Independent channel decoding curve. Named curves preserve signed extensions; sampled and parametric curves explicitly use bounded input/output 0..1.
- RgbSpace
- SDR RGB primaries and transfer function. Display P3 uses the sRGB transfer.
- White
Point - White of the supplied XYZ/colorant matrix, not a monitor mode or ICC label. D50 uses xy=(0.3457,0.3585); D65 uses xy=(0.3127,0.3290).
Constants§
- WGSL_
COLOR_ MATH - Resource-free WGSL helpers; see the crate guide for IDs and input contracts.