Skip to main content

Crate cmx

Crate cmx 

Source
Expand description

cmx is a Rust library for reading, writing, and constructing ICC color profiles (versions 2.0–5.0). ICC profiles describe how color values produced by a device (camera, display, printer) relate to a standard reference color space, making them essential for accurate color reproduction across devices and applications.

§Quick Start

§Read a profile from disk

use cmx::profile::Profile;

let profile = Profile::read("profile.icc")?;
println!("Color space : {:?}", profile.data_color_space());
println!("PCS         : {:?}", profile.pcs());
println!("Version     : {:?}", profile.version()?);

§Dump a profile as TOML

The fmt::Display implementation on every profile type serialises it to TOML — the same output produced by the cmx CLI tool:

use cmx::profile::Profile;

let profile = Profile::read("profile.icc")?;
println!("{profile}");

The output looks like:

profile_size = 548
cmm = "Apple"
version = "4.0"
device_class = "Display"
color_space = "RGB"
pcs = "XYZ"
creation_datetime = "2015-10-14 13:08:56 UTC"
primary_platform = "Apple"
manufacturer = "APPL"
rendering_intent = "Perceptual"
pcs_illuminant = [0.9642, 1.0, 0.8249]
creator = "appl"
profile_id = "53410ea9facdd9fb57cc74868defc33f"

[desc]
ascii = "SMPTE RP 431-2-2007 DCI (P3)"

[wtpt]
xyz = [0.894592, 1.0, 0.954422]

[rTRC]
g = 2.60001

[chad]
matrix = [
    [1.073822, 0.038803, -0.036896],
    [0.055573, 0.963989, -0.014343],
    [-0.004272, 0.005295, 0.862778]
]

§Build a profile from scratch

The consuming builder API sets tags one by one and computes the profile ID at the end:

use chrono::{DateTime, TimeZone};
use cmx::tag::tags::*;
use cmx::profile::DisplayProfile;

let display_p3_example = DisplayProfile::new()
    // set creation date — current date/time is used if omitted
    .with_creation_date(chrono::Utc.with_ymd_and_hms(2025, 8, 28, 0, 0, 0).unwrap())
    .with_tag(ProfileDescriptionTag)
        .as_text_description(|text| {
            text.set_ascii("Display P3");
        })
    .with_tag(CopyrightTag)
        .as_text(|text| {
            text.set_text("CC0");
        })
    .with_tag(MediaWhitePointTag)
        .as_xyz_array(|xyz| {
            xyz.set([0.950455, 1.00000, 1.08905]);
        })
    .with_tag(RedMatrixColumnTag)
        .as_xyz_array(|xyz| {
            xyz.set([0.515121, 0.241196, -0.001053]);
        })
    .with_tag(GreenMatrixColumnTag)
        .as_xyz_array(|xyz| {
            xyz.set([0.291977, 0.692245, 0.041885]);
        })
    .with_tag(BlueMatrixColumnTag)
        .as_xyz_array(|xyz| {
            xyz.set([0.157104, 0.066574, 0.784073]);
        })
    .with_tag(RedTRCTag)
        .as_parametric_curve(|para| {
            para.set_parameters([2.39999, 0.94786, 0.05214, 0.07739, 0.04045]);
        })
    .with_tag(BlueTRCTag)
        .as_parametric_curve(|para| {
            para.set_parameters([2.39999, 0.94786, 0.05214, 0.07739, 0.04045]);
        })
    .with_tag(GreenTRCTag)
        .as_parametric_curve(|para| {
            para.set_parameters([2.39999, 0.94786, 0.05214, 0.07739, 0.04045]);
        })
    .with_tag(ChromaticAdaptationTag)
        .as_sf15_fixed_16_array(|array| {
            array.set([
                 1.047882, 0.022919, -0.050201,
                 0.029587, 0.990479, -0.017059,
                -0.009232, 0.015076,  0.751678
            ]);
        })
    .with_profile_id(); // compute and embed the MD5 profile ID

// Serialise to bytes without touching the filesystem
let bytes = display_p3_example.to_bytes().unwrap();
assert_eq!(bytes.len(), 524);

§Modify an existing profile

Read a profile, change a tag, and write it back:

use cmx::profile::Profile;
use cmx::tag::tags::CopyrightTag;

Profile::read("input.icc")?
    .with_tag(CopyrightTag)
        .as_text(|t| t.set_text("Copyright 2025 Acme Corp."))
    .write("output.icc")?;

§Modules

ModuleContents
profileProfile enum and per-device-class types (DisplayProfile, InputProfile, …)
tagTag signatures, tag data types, and the TagSetter builder
headerICC 128-byte header fields and accessors
signaturesICC 4-byte signature enums (ColorSpace, DeviceClass, RenderingIntent, …)
errorError type returned by public API functions

§ICC Profile Concepts

An ICC profile is a binary file with three sections:

  1. 128-byte header — fixed fields: device class, color space, PCS, version, creation date, etc.
  2. Tag table — a list of (signature, offset, size) entries pointing into the data block.
  3. Tag data — the actual payload for each tag (matrices, curves, look-up tables, text, …).

§Device Classes

The ICC specification defines eight device classes. This crate provides a dedicated type for each:

TypeICC class codeTypical use
profile::DisplayProfilemntrMonitors, projectors
profile::InputProfilescnrCameras, scanners
profile::OutputProfileprtrPrinters
profile::DeviceLinkProfilelinkDirect device-to-device transforms
profile::AbstractProfileabstAbstract color transforms
profile::ColorSpaceProfilespacColor space definitions
profile::NamedColorProfilenmclNamed color palettes
profile::SpectralProfilecencSpectral data (ICC v5)

All types wrap a profile::RawProfile which holds the raw binary data and preserves unknown tags verbatim, guaranteeing lossless round-trips.

§Profile Connection Space (PCS)

Profiles connect device-specific color values to a common reference color space called the Profile Connection Space (PCS). Two PCS values are defined by the ICC specification:

  • XYZ — CIE 1931 XYZ, used by most display and output profiles.
  • Lab — CIELAB (L*a*b*), used by some output and abstract profiles.

§Rendering Intents

The rendering intent controls how out-of-gamut colors are handled during color conversion. Four intents are defined:

IntentTypical use
PerceptualPhotographic images — compresses the gamut smoothly
Relative ColorimetricGraphics — clips and maps the source white point
SaturationBusiness graphics — maximises saturation
Absolute ColorimetricProofing — preserves absolute colorimetric values

§Tags

Tags are identified by a 4-byte signature (e.g. rXYZ, rTRC, desc). Each tag carries a payload of a specific ICC type — an XYZ triplet, a tone-reproduction curve, a text string, etc. All well-known tag signatures are re-exported from tag::tags.

Tag types supported for reading and writing include:

ICC typeRust typeCommon tags
XYZXYZArrayDatarXYZ, gXYZ, bXYZ, wtpt
paraParametricCurveDatarTRC, gTRC, bTRC
curvCurveDatarTRC, gTRC, bTRC
mlucMultiLocalizedUnicodeDatadesc
descTextDescriptionDatadesc
sf32S15Fixed16ArrayDatachad
sigSignatureDatatech
textTextDatacprt
mft1Lut8DataA2B0, B2A0
mft2Lut16DataA2B0, B2A0

Tags not yet parsed are stored as RawData and written back verbatim — no data is lost.

§Key Types

TypeDescription
S15Fixed16ICC s15Fixed16 fixed-point number used in matrices and XYZ values
profile::ProfileParsed profile, dispatched to one of eight device-class variants
profile::TagSetterConsuming builder returned by with_tag(…)
tag::TagSignature4-byte tag identifier; 70+ known signatures plus Unknown(u32)
error::ErrorTop-level error type

§Lossless Round-trips

Any tag not recognised by this crate is preserved as raw bytes and written back verbatim. Reading a profile and re-serialising it produces byte-identical output.

§CLI Tool

The cmx binary prints any ICC profile as TOML:

cargo install cmx
cmx profile.icc               # print TOML to stdout
cmx profile.icc -o out.toml   # write TOML to a file

§Installation

Add the library to your project:

cargo add cmx

Full API documentation is on docs.rs/cmx.

§Roadmap

  • Parse full ICC profiles (versions 2.x–5.0)
  • Lossless round-trips — unknown tags preserved verbatim
  • Conversion to human-readable TOML format
  • Builder-style API for constructing ICC profiles
  • Support for the primary ICC tag types
  • Read TOML color profiles and convert back to binary ICC
  • Support all ICC tag types
  • Spectral data and ICC v5 color management

Re-exports§

pub use error::Error;

Modules§

error
header
profile
signatures
tag

Structs§

S15Fixed16
An ICC s15Fixed16Number: a signed 32-bit fixed-point value with 15 integer bits and 16 fractional bits, stored in big-endian byte order.

Functions§

format_hex_with_spaces
Render bytes as uppercase hex grouped into 4-byte (8-hex) chunks separated by spaces. Example: [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC] -> “12345678 9abc” This is used for displaying binary data in a human-readable format.
is_printable_ascii_bytes
parse_hex_string
Parse a hex string with optional spaces into a byte vector. Example: “12345678 9abc” -> [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC] This is used for converting human-readable hex strings back into binary data.