diff --git a/README.md b/README.md index 2c2c67e..34a0fc7 100644 --- a/README.md +++ b/README.md @@ -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 +let pcs = profile.pcs(); // Option +let intent = profile.rendering_intent(); // RenderingIntent +let created = profile.creation_date()?; // DateTime +let platform = profile.primary_platform(); // Option +let cmm = profile.cmm(); // Option +let manufacturer = profile.manufacturer(); // Option +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 @@ -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; @@ -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 | diff --git a/src/lib.rs b/src/lib.rs index 9927361..8ab7f02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,69 @@ println!("Version : {:?}", profile.version()?); # Ok::<(), Box>(()) ``` +## 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 +let pcs = profile.pcs(); // Option +let intent = profile.rendering_intent(); // RenderingIntent +let created = profile.creation_date()?; // DateTime +let platform = profile.primary_platform(); // Option +let cmm = profile.cmm(); // Option +let manufacturer = profile.manufacturer(); // Option +let (embedded, excl) = profile.flags(); // (bool, bool) +let illuminant = profile.pcs_illuminant(); // [f64; 3] +# Ok::<(), Box>(()) +``` + +## 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>(()) +``` + +[`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 @@ -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; @@ -150,6 +213,18 @@ Profile::read("input.icc")? # Ok::<(), Box>(()) ``` +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>(()) +``` + # Modules | Module | Contents | diff --git a/src/profile.rs b/src/profile.rs index 6adab3c..d9c6e2e 100644 --- a/src/profile.rs +++ b/src/profile.rs @@ -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 { + 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 { + self.as_raw_profile_mut().tags_mut() + } + // ----------------------------------------------------------------------- // Consuming builders — forward to the matching device-class wrapper and re-wrap. // ----------------------------------------------------------------------- diff --git a/src/profile/delegate.rs b/src/profile/delegate.rs index 90adfb0..3e47bf7 100644 --- a/src/profile/delegate.rs +++ b/src/profile/delegate.rs @@ -24,6 +24,8 @@ macro_rules! delegate_raw_profile_methods { pub fn primary_platform(&self) -> Option; pub fn profile_size(&self) -> usize; pub fn profile_id(&self) -> [u8; 16]; + pub fn tags(&self) -> &indexmap::IndexMap; + pub fn tags_mut(&mut self) -> &mut indexmap::IndexMap; pub fn version(&self) -> Result<(u8, u8), crate::Error>; pub fn write>(self, path: P) -> Result<(), Box>; } diff --git a/src/profile/raw_profile.rs b/src/profile/raw_profile.rs index 5d6efb7..b24d29d 100644 --- a/src/profile/raw_profile.rs +++ b/src/profile/raw_profile.rs @@ -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 { + &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 { + &mut self.tags + } + pub fn into_class_profile(self) -> Profile { match self.device_class() { DeviceClass::Input => Profile::Input(super::InputProfile(self)),