Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 73 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,67 @@ println!("PCS : {:?}", profile.pcs());
println!("Version : {:?}", profile.version()?);
```

### Read header fields

All header fields are available as typed accessors on every profile type — [`profile::Profile`],
[`profile::RawProfile`], [`profile::DisplayProfile`], and all other device-class wrappers.
They decode the 128-byte ICC header into Rust values:

```rust
use cmx::profile::Profile;

let profile = Profile::read("profile.icc")?;

let (major, minor) = profile.version()?; // e.g. (4, 0)
let color_space = profile.data_color_space(); // Option<ColorSpace>
let pcs = profile.pcs(); // Option<Pcs>
let intent = profile.rendering_intent(); // RenderingIntent
let created = profile.creation_date()?; // DateTime<Utc>
let platform = profile.primary_platform(); // Option<Platform>
let cmm = profile.cmm(); // Option<Cmm>
let manufacturer = profile.manufacturer(); // Option<Signature>
let (embedded, excl) = profile.flags(); // (bool, bool)
let illuminant = profile.pcs_illuminant(); // [f64; 3]
```

### Read tag values

Tag data is accessible via `.tags()` on every profile type — [`profile::Profile`],
[`profile::RawProfile`], [`profile::DisplayProfile`], and all other device-class wrappers.
It returns an `IndexMap` keyed by [`tag::TagSignature`], with tags in the order they appear
in the profile's tag table:

```rust
use cmx::profile::Profile;
use cmx::tag::TagSignature;
use cmx::tag::tagdata::parametric_curve::ParametricCurveType;

let profile = Profile::read("profile.icc")?;

// Iterate every tag in the profile
for (sig, record) in profile.tags() {
println!("{sig}: {} bytes", record.tag.len());
}

// Look up a specific tag and get a serializable representation
if let Some(record) = profile.tags().get(&TagSignature::MediaWhitePoint) {
let parsed = record.tag.to_parsed(); // -> ParsedTag (implements serde::Serialize)
}

// Type-specific read: parametric curve parameters (gamma, a, b, c, d, e, f)
if let Some(record) = profile.tags().get(&TagSignature::RedTRC) {
if let Some(curve_data) = record.tag.data().as_parametric_curve() {
let curve = ParametricCurveType::from(curve_data);
let [g, a, b, c, d, e, f] = curve.values();
println!("gamma = {g}");
}
}
```

[`tag::ParsedTag`] is a `serde::Serialize`-able enum covering all supported tag types.
For the quickest way to inspect all tags at once, serialize the whole profile to TOML
(see [Dump a profile as TOML](#dump-a-profile-as-toml) below).

### Dump a profile as TOML

The [`fmt::Display`](std::fmt::Display) implementation on every profile type serialises it
Expand Down Expand Up @@ -132,7 +193,7 @@ assert_eq!(bytes.len(), 524);

### Modify an existing profile

Read a profile, change a tag, and write it back:
Read a profile, change a tag, and write it back using the builder API:

```rust
use cmx::profile::Profile;
Expand All @@ -144,6 +205,17 @@ Profile::read("input.icc")?
.write("output.icc")?;
```

Use [`profile::Profile::tags_mut`] to remove tags — something the builder API does not support:

```rust
use cmx::profile::Profile;
use cmx::tag::TagSignature;

let mut profile = Profile::read("input.icc")?;
profile.tags_mut().remove(&TagSignature::MakeAndModel);
profile.write("output.icc")?;
```

## Modules

| Module | Contents |
Expand Down
77 changes: 76 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,69 @@ println!("Version : {:?}", profile.version()?);
# Ok::<(), Box<dyn std::error::Error>>(())
```

## Read header fields

All header fields are available as typed accessors on every profile type — [`profile::Profile`],
[`profile::RawProfile`], [`profile::DisplayProfile`], and all other device-class wrappers.
They decode the 128-byte ICC header into Rust values:

```no_run
use cmx::profile::Profile;

let profile = Profile::read("profile.icc")?;

let (major, minor) = profile.version()?; // e.g. (4, 0)
let color_space = profile.data_color_space(); // Option<ColorSpace>
let pcs = profile.pcs(); // Option<Pcs>
let intent = profile.rendering_intent(); // RenderingIntent
let created = profile.creation_date()?; // DateTime<Utc>
let platform = profile.primary_platform(); // Option<Platform>
let cmm = profile.cmm(); // Option<Cmm>
let manufacturer = profile.manufacturer(); // Option<Signature>
let (embedded, excl) = profile.flags(); // (bool, bool)
let illuminant = profile.pcs_illuminant(); // [f64; 3]
# Ok::<(), Box<dyn std::error::Error>>(())
```

## Read tag values

Tag data is accessible via `.tags()` on every profile type — [`profile::Profile`],
[`profile::RawProfile`], [`profile::DisplayProfile`], and all other device-class wrappers.
It returns an `IndexMap` keyed by [`tag::TagSignature`], with tags in the order they appear
in the profile's tag table:

```no_run
use cmx::profile::Profile;
use cmx::tag::TagSignature;
use cmx::tag::tagdata::parametric_curve::ParametricCurveType;

let profile = Profile::read("profile.icc")?;

// Iterate every tag in the profile
for (sig, record) in profile.tags() {
println!("{sig}: {} bytes", record.tag.len());
}

// Look up a specific tag and get a serializable representation
if let Some(record) = profile.tags().get(&TagSignature::MediaWhitePoint) {
let parsed = record.tag.to_parsed(); // -> ParsedTag (implements serde::Serialize)
}

// Type-specific read: parametric curve parameters (gamma, a, b, c, d, e, f)
if let Some(record) = profile.tags().get(&TagSignature::RedTRC) {
if let Some(curve_data) = record.tag.data().as_parametric_curve() {
let curve = ParametricCurveType::from(curve_data);
let [g, a, b, c, d, e, f] = curve.values();
println!("gamma = {g}");
}
}
# Ok::<(), Box<dyn std::error::Error>>(())
```

[`tag::ParsedTag`] is a `serde::Serialize`-able enum covering all supported tag types.
For the quickest way to inspect all tags at once, serialize the whole profile to TOML
(see [Dump a profile as TOML](#dump-a-profile-as-toml) below).

## Dump a profile as TOML

The [`fmt::Display`](std::fmt::Display) implementation on every profile type serialises it
Expand Down Expand Up @@ -137,7 +200,7 @@ assert_eq!(bytes.len(), 524);

## Modify an existing profile

Read a profile, change a tag, and write it back:
Read a profile, change a tag, and write it back using the builder API:

```no_run
use cmx::profile::Profile;
Expand All @@ -150,6 +213,18 @@ Profile::read("input.icc")?
# Ok::<(), Box<dyn std::error::Error>>(())
```

Use [`profile::Profile::tags_mut`] to remove tags — something the builder API does not support:

```no_run
use cmx::profile::Profile;
use cmx::tag::TagSignature;

let mut profile = Profile::read("input.icc")?;
profile.tags_mut().remove(&TagSignature::MakeAndModel);
profile.write("output.icc")?;
# Ok::<(), Box<dyn std::error::Error>>(())
```

# Modules

| Module | Contents |
Expand Down
23 changes: 23 additions & 0 deletions src/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,29 @@ impl Profile {
self.as_raw_profile().cmm()
}

/// Returns a reference to the profile's tag map, keyed by [`crate::tag::TagSignature`].
///
/// Each [`ProfileTagRecord`] holds the tag's offset, size, and typed [`crate::tag::Tag`]
/// value. Use [`crate::tag::Tag::to_parsed`] to obtain the fully parsed
/// [`crate::tag::ParsedTag`] representation when needed.
///
/// Tags are stored in insertion order. For profiles read from disk, this initially
/// matches the order they appear in the original profile's tag table, before any
/// mutation via [`Self::tags_mut`].
pub fn tags(&self) -> &IndexMap<crate::tag::TagSignature, ProfileTagRecord> {
self.as_raw_profile().tags()
}

/// Returns a mutable reference to the profile's tag map, keyed by [`crate::tag::TagSignature`].
///
/// Allows inserting, replacing, or removing tags directly. Removing and
/// reinserting tags may change their iteration order. Note that removing
/// required tags (e.g. `MediaWhitePoint` from a display profile) will produce a
/// profile that does not conform to the ICC specification.
pub fn tags_mut(&mut self) -> &mut IndexMap<crate::tag::TagSignature, ProfileTagRecord> {
self.as_raw_profile_mut().tags_mut()
}

// -----------------------------------------------------------------------
// Consuming builders — forward to the matching device-class wrapper and re-wrap.
// -----------------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions src/profile/delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ macro_rules! delegate_raw_profile_methods {
pub fn primary_platform(&self) -> Option<crate::signatures::Platform>;
pub fn profile_size(&self) -> usize;
pub fn profile_id(&self) -> [u8; 16];
pub fn tags(&self) -> &indexmap::IndexMap<crate::tag::TagSignature, crate::profile::ProfileTagRecord>;
pub fn tags_mut(&mut self) -> &mut indexmap::IndexMap<crate::tag::TagSignature, crate::profile::ProfileTagRecord>;
pub fn version(&self) -> Result<(u8, u8), crate::Error>;
pub fn write<P: AsRef<Path>>(self, path: P) -> Result<(), Box<dyn std::error::Error>>;
}
Expand Down
23 changes: 23 additions & 0 deletions src/profile/raw_profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,29 @@ impl RawProfile {
self.shared_tags
}

/// Returns a reference to the profile's tag map, keyed by [`TagSignature`].
///
/// Each [`ProfileTagRecord`] holds the tag's offset, size, and typed [`Tag`] data.
/// Use [`Tag::to_parsed`] to obtain the fully parsed [`crate::tag::ParsedTag`]
/// representation when needed.
///
/// Tags are stored in insertion order. Immediately after reading a profile from
/// disk, that order matches the order the tags appear in the original profile's
/// tag table. After modifications via [`Self::tags_mut`], the order may differ.
pub fn tags(&self) -> &IndexMap<TagSignature, ProfileTagRecord> {
&self.tags
}

/// Returns a mutable reference to the profile's tag map, keyed by [`TagSignature`].
///
/// Allows inserting, replacing, or removing tags directly. Removing and
/// reinserting tags may change their iteration order. Note that removing
/// required tags (e.g. `MediaWhitePoint` from a display profile) will produce a
/// profile that does not conform to the ICC specification.
pub fn tags_mut(&mut self) -> &mut IndexMap<TagSignature, ProfileTagRecord> {
&mut self.tags
}

pub fn into_class_profile(self) -> Profile {
match self.device_class() {
DeviceClass::Input => Profile::Input(super::InputProfile(self)),
Expand Down
Loading