Document-aware color management for Rust — pure Rust ICC transforms, CMYK, soft proofing, and print-ready color policies.
appthere-color fills a gap in the Rust ecosystem: a color model built for document authoring and print workflows, rather than pixel rendering. It provides named color values, CMYK ink representation, ICC profile transforms via a pure-Rust backend (moxcms), rendering intent selection, soft proofing configuration, and output intent policies — all the primitives a document editor, prepress tool, or print-aware renderer needs.
- Pure Rust ICC transforms via
moxcms— no C toolchain, noliblcms2system dependency, cross-compiles cleanly to Android and WASM targets - CMYK and multi-ink color values with f32 component precision
- Named color spaces — sRGB, Display P3, Adobe RGB (1998), ProPhoto RGB, CMYK (generic and profile-bound)
- Rendering intents — Perceptual, Relative Colorimetric, Saturation, Absolute Colorimetric
- Soft proofing configuration — proof profile, simulation intent, gamut warning, paper simulation
- Document color policy — named output intent, fallback profile, per-document color state
- Profile-to-profile transforms — CMYK↔RGB, RGB↔RGB, Lab↔RGB, Gray↔RGB
no_stdcompatible (withmoxcmsbackend, disable default features)
[dependencies]
appthere-color = "0.1"No system libraries required. Everything is pure Rust.
use appthere_color::{ColorValue, CmykColor, RgbColor};
// RGB color (0.0–1.0 components)
let red = ColorValue::Rgb(RgbColor::new(1.0, 0.0, 0.0));
// CMYK ink values (0.0–1.0 per channel)
let rich_black = ColorValue::Cmyk(CmykColor::new(0.60, 0.50, 0.50, 1.0));
// Lab color (D50 whitepoint)
let lab = ColorValue::Lab { l: 53.39, a: 80.09, b: 67.20 };use appthere_color::{IccProfile, ColorTransform, RenderingIntent};
let fogra39 = IccProfile::from_bytes(include_bytes!("profiles/ISOcoated_v2_300_eci.icc"))?;
let srgb = IccProfile::srgb();
let transform = ColorTransform::new(&fogra39, &srgb, RenderingIntent::RelativeColorimetric)?;
let cmyk_pixel = [0.60f32, 0.50, 0.50, 1.0];
let rgb_pixel = transform.transform_cmyk_to_rgb(&cmyk_pixel)?;use appthere_color::{ProofingConfig, GamutWarning, RenderingIntent};
let config = ProofingConfig::builder()
.output_profile(fogra39)
.simulation_intent(RenderingIntent::RelativeColorimetric)
.display_profile(srgb)
.gamut_warning(GamutWarning::Color([1.0, 0.0, 1.0])) // magenta flag
.paper_simulation(true)
.build()?;
let proofing_transform = config.build_transform()?;use appthere_color::{ColorPolicy, OutputIntent};
let policy = ColorPolicy::builder()
.output_intent(OutputIntent::named("FOGRA39", fogra39_profile))
.default_rendering_intent(RenderingIntent::Perceptual)
.fallback_profile(IccProfile::srgb())
.build();
// Serialize the policy into your document format's color metadata| Type | Channels | Range | Notes |
|---|---|---|---|
RgbColor |
R, G, B | 0.0–1.0 | Linear or gamma-encoded depending on profile |
CmykColor |
C, M, Y, K | 0.0–1.0 | Profile-bound ink percentages |
LabColor |
L, a, b | L: 0–100, a/b: ±128 | D50 whitepoint |
XyzColor |
X, Y, Z | 0.0–1.0 (relative) | D50 |
GrayColor |
K | 0.0–1.0 | |
ColorValue |
— | — | Enum over all of the above |
pub enum RenderingIntent {
Perceptual,
RelativeColorimetric,
Saturation,
AbsoluteColorimetric,
}These map directly to ICC specification intents and are passed through to the moxcms transform engine without modification.
Soft proofing simulates how a document will appear when reproduced on a specific output device (typically a press or printer). appthere-color models proofing as a first-class configuration object rather than a transform flag:
pub struct ProofingConfig {
pub output_profile: IccProfile,
pub simulation_intent: RenderingIntent,
pub display_profile: IccProfile,
pub display_intent: RenderingIntent,
pub gamut_warning: GamutWarning,
pub paper_simulation: bool,
}Soft proofing is implemented as two chained transforms internally: source → output simulation space, then simulation space → display. This is semantically equivalent to LCMS2's proofing transform and produces the same result for standard workflows.
pub enum GamutWarning {
/// Do not flag out-of-gamut colors
None,
/// Replace out-of-gamut colors with a fixed indicator color
Color([f32; 3]),
/// Reduce saturation of out-of-gamut colors by a factor
Desaturate(f32),
}Profiles are loaded from raw ICC bytes and backed by moxcms:
// From bytes (e.g. embedded in a document or file)
let profile = IccProfile::from_bytes(&icc_bytes)?;
// From a file path
let profile = IccProfile::from_file("path/to/profile.icc")?;
// Built-in virtual profiles
let srgb = IccProfile::srgb();
let linear = IccProfile::linear_srgb();
let gray = IccProfile::gray_d50();Supported transform combinations mirror moxcms capabilities: CMYK↔RGB, RGB↔RGB, Lab↔RGB, Gray↔RGB, and any Display Class profiles up to 16 inks.
A ColorPolicy captures the color management configuration for an entire document — the intended output condition, rendering intent, and fallback behavior when profiles are unavailable. This concept appears in PDF/X (OutputIntent), ODF (draw:color-profile), and EPUB (embedded ICC in images), but appthere-color represents it format-agnostically:
pub struct ColorPolicy {
pub output_intent: Option<OutputIntent>,
pub default_rendering_intent: RenderingIntent,
pub fallback_profile: IccProfile,
pub embedded_profiles: HashMap<String, IccProfile>,
}Format-specific serialization (e.g. writing an /OutputIntents array into a PDF or the color profile metadata into an ODF package) is the responsibility of the document format crate that depends on appthere-color.
lcms2 is an excellent crate wrapping the mature Little CMS 2 library. If you need spot color (NamedColorList), DeviceLink profiles, or maximum compatibility with exotic real-world ICC profiles, it may be the right choice.
appthere-color uses moxcms instead because:
- No C toolchain required — clean cross-compilation to Android, WASM, and musl targets without a system
liblcms2 - Pure Rust safety guarantees — no
unsafeFFI surface - Simpler CI — no need to manage native library availability across platforms
The tradeoff: moxcms does not yet support spot/named color lookups or some exotic DeviceLink profile classes. For document authoring workflows targeting standard RGB and CMYK output conditions, this is not a practical limitation.
appthere-color is no_std compatible. Disable the default std feature:
[dependencies]
appthere-color = { version = "0.1", default-features = false }ICC file loading from paths requires std. Profile construction from &[u8] and all transform operations work in no_std environments.
Licensed under the Apache License, Version 2.0.
moxcms is also Apache 2.0. The full dependency tree is permissively licensed.
Issues and pull requests are welcome. The scope of this crate is intentionally narrow — document-authoring color primitives and ICC transforms. Requests for PDF/ODF/EPUB serialization belong in the respective format crates.