Files
photocraft/crates/cms
yoriswind 0681361e6e Preserve Batch sources and cancellable action filters
Resolve recorded layer targets per Batch input and refuse destructive EXR Save and Close output. Prepare custom Shape Blur kernels in cancellable jobs, retaining Smart Filter progress and geometry. Preserve supported ATN descriptor metadata, defaults, channel sources and three-value LUT shapers; cover rebuilt naming rules, non-identity Custom kernels and float lookup dither.
2026-11-02 09:37:49 -08:00
..
2026-09-30 11:36:20 -04:00

photocraft-cms

Pure-Rust ICC colour management for PhotoCraft. No workspace dependencies (L0, publishable), no C, no unsafe. Implemented from the public ICC.1:2010 (v4.3) and ICC.1:2001-04 (v2) specifications, CIE 15, and Adobe's published black point compensation paper.

API

use photocraft_cms::{Builtin, Intent, Profile, Transform};

let src = Profile::parse(&icc_bytes)?;                    // v2/v4: matrix/TRC, gray TRC, mft1/mft2/mAB/mBA
let dst = Builtin::CoatedCmyk.profile();
let t = Transform::new(&src, dst, Intent::RelativeColorimetric, /*bpc*/ true)?;
t.convert_u8(&rgba8, 4, &mut cmyka8, 5, /*copy alpha*/ true);   // also convert_u16 / convert_f32 / apply (in place)
let mut out = [0.0; 4];
t.eval(&[1.0, 0.0, 0.0], &mut out);                        // exact single colour
  • Transform::proof(src, proof, display, intent, bpc, simulate_paper): soft-proof chain.
  • GamutCheck::new(src, proof, ΔE): out-of-gamut test (gamut warning).
  • Lut3d::from_transform(&t, 33).to_rgba16f_bytes(): display transform as a 3D texture.
  • Transform::gpu_matrix_shaper(): accurate RGB input curves + matrix + output curves for display shaders. Float tables retain near-black detail; unsupported chains return None for the 3D LUT fallback. No GPU dependency is required by this crate.
  • transform::cached(src, dst, opts): process-wide transform cache.
  • Profile::to_bytes(): original bytes of parsed profiles (byte-exact), or an ICC v4.3 encoding.
  • Profile::same_colors(&other): the same colour space whatever the encoding (every sample of a device grid within ΔE 0.5 through both), e.g. Photoshop's sRGB IEC61966-2.1 and the built-in sRGB. same_bytes(&other) compares the ICC bytes. content_hash() is a 64-bit hash of them that another profile can share: a cache key, confirmed with same_bytes.

Evaluation

  • Exact path: a float stage pipeline (curves, matrices, CLUTs, Lab↔XYZ, PCS encodings, BPC / absolute scaling). Float buffers use it by default (TransformOptions::precise_float).
  • Integer buffers: matrix/TRC chains use per-code input tables + matrix + output tables indexed by x^(1/4) (accurate near black even for pure gamma curves); chains containing a CLUT are sampled into a device link (33³ for 3 inputs, 17⁴ for 4, 4096 for 1) evaluated with tetrahedral interpolation (4D: linear on the first input between two tetrahedra); purely analytic chains (e.g. Lab ↔ RGB) are evaluated exactly. Buffers ≥ 16k pixels run on rayon (native only).
  • GPU matrix/TRC display transforms reuse the 4097-entry input curves, 3×3 matrix plus offsets, and 4096-entry output curves indexed by x^(1/4). Each channel is interpolated separately using float texels; SDR input/output are clamped to [0, 1]. A final RGB 33³ table cannot resolve steep inverse gamma curves near black, even with float texels.
  • Rendering intents: AToB0/1/2 and BToA0/1/2 by intent with AToB0/BToA0 fallback; absolute colorimetric scales by media white (wtpt; v2 display profiles treated as D50; a zero, negative or non-finite white is refused with CmsError::Invalid); BPC uses neutral black points (Y only) mapped linearly in XYZ.

Built-in profiles (all CC0-1.0, generated by this crate)

id description
srgb sRGB IEC61966-2.1 (Rec. 709 primaries, D65 Bradford-adapted, sRGB TRC)
display-p3 Display P3
adobe-rgb-compat Adobe RGB (1998)-compatible (published primaries, gamma 563/256)
prophoto-compat ProPhoto-compatible (ROMM primaries, D50, gamma 1.8)
linear-srgb sRGB primaries, linear
rec2020 Rec. 2020 primaries, Rec. 709 OETF
gray-gamma-2.2, sgray gray gamma 2.2; gray with the sRGB curve (default gray working space)
lab-d50 Lab identity (v4 encoding)
coated-cmyk PhotoCraft Coated CMYK (synthetic, 300% TAC, medium GCR) — default CMYK

The CMYK profile

Adobe's CMYK profiles (U.S. Web Coated SWOP, Coated FOGRA39) are proprietary, and the freely downloadable characterisation-based profiles (ECI/FOGRA, colord, Ghostscript) carry redistribution conditions or unclear licences, so PhotoCraft generates its own (src/synth.rs): a Yule–Nielsen modified Neugebauer model (n = 2) over the 16 overprints with ISO 12647-2 coated-paper Lab aims for paper/C/M/Y/RGB overprints, parabolic dot gain (14 % CMY, 17 % K at 50 %), GCR black generation (starts at 20 % gray component, K max 95 %), total ink limit 300 %, perceptual tables with L* black-point scaling and soft chroma compression against a sampled gamut boundary. profiles/photocraft-coated-cmyk.icc (237 KB, AToB 11⁴ lut16, BToA 21³ lut16) is that output, dedicated to the public domain (CC0-1.0). Regenerate with

PHOTOCRAFT_REGEN_PROFILES=1 cargo test -p photocraft-cms --release --test it -- regen::

It is a plausible coated-offset profile, not a measured characterisation: use your printer's profile for production print.

Not implemented / approximate

  • v4.4 mpet (multiProcessElements, D2Bx/B2Dx) tags, named colour, device link and abstract profiles as transform endpoints, chad-based adaptation of v2 absolute colorimetry.
  • BPC destination black uses the BToA/AToB round trip directly (Adobe's curve fit for ill-behaved LUT profiles is not implemented); v2/v4 perceptual reference black mixing is not modelled beyond the PRMG black for v4 LUT profiles.
  • Profile ID (MD5) is not computed when writing (left zero, which the spec allows).