1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Copyright (c) 2021-2025, Harbers Bik LLC
//#![allow(dead_code, unused_imports)]
/*!
This crate provides utilities for working with ICC color profiles
and integrates with the Colorimetry Library.
## Use Cases
<details><summary><strong>Parsing ICC profiles and conversion to TOML format for analysis</strong></summary>
After installing the library, you can parse an ICC profile and convert it to a TOML format using the `cmx` command-line tool:
```bash
cmx profile.icc -o profile.toml
```
Each ICC profile tag is mapped to a key in the TOML file, with the
corresponding values serialized as key-value pairs.
All values are written as single-line entries to ensure the TOML output
remains human-readable and easy to inspect.
Example of a parsed ICC profile in TOML format:
```toml
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)"
[cprt]
text = "Copyright Apple Inc., 2015"
[wtpt]
xyz = [0.894592, 1.0, 0.954422]
[rXYZ]
xyz = [0.48616, 0.226685, -0.000809]
[gXYZ]
xyz = [0.323853, 0.710327, 0.043228]
[bXYZ]
xyz = [0.15419, 0.062988, 0.782471]
[rTRC]
g = 2.60001
[chad]
matrix = [
[1.073822, 0.038803, -0.036896],
[0.055573, 0.963989, -0.014343],
[-0.004272, 0.005295, 0.862778]
]
[bTRC]
g = 2.60001
[gTRC]
g = 2.60001
```
</details>
<details><summary><strong>Generate ICC profiles</strong></summary>
You can also use the `cmx` library to create ICC profiles from scratch, or read existing
profiles and change them, using Rust.
The library provides a builder-style API for constructing, or read and change profiles,
allowing you to set or change various tags and properties.
Here is an example for creating a Display P3 ICC profile:
```rust
use chrono::{DateTime, TimeZone};
use cmx::tag::tags::*;
use cmx::profile::DisplayProfile;
let display_p3_example = DisplayProfile::new()
// set creation date, if omitted, the current date and time are used
.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() // calculate and add profile ID to the profile
;
display_p3_example.write("tmp/display_p3_example.icc").unwrap();
let display_p3_read_back = cmx::profile::Profile::read("tmp/display_p3_example.icc").unwrap();
assert_eq!(
display_p3_read_back.profile_id_as_hex_string(),
"617028e1 e1014e15 91f178a9 fb8efc92"
);
assert_eq!(display_p3_read_back.profile_size(), 524);
```
Not all ICC tag types are supported yet, but please submit a pull request, or an issue, on our
[GitHub CMX repo](https://github.com/harbik/cmx) if you want additional tag types to be supported.
However, you can use the `as_raw` method to set raw data for tags that are not yet supported.
</details>
## Installation
Install the `cmx` tool using Cargo:
```bash
cargo install cmx
```
To use the `cmx` library in your Rust project:
```bash
cargo add cmx
```
Documentation is available at [docs.rs/cmx](https://docs.rs/cmx).
## Roadmap
- [x] Parse full ICC profiles
- [x] Convert to TOML format
- [x] Add builder-style API for constructing ICC profiles
- [x] Support basic ICC Type tags and color models
- [ ] Read TOML Color profiles and convert to binary ICC profiles
- [ ] Utilities for commandline profile conversion and manipulation
- [ ] Calibration and profiling tools
- [ ] X-Rite I1 Profiler support
- [ ] Support all ICC Type tags
- [ ] Enable spectral data and advanced color management
## Overview
Although the ICC specification is broad and complex, this crate aims
to provide a robust foundation for working with ICC profiles in Rust.
It supports parsing, constructing, and changing of the primary ICC-defined tags,
as well as some commonly used non-standard tags.
Even tags that cannot yet be parsed are still preserved when reading
and serializing profiles, ensuring no data loss.
The long-term goal is to fully support advanced ICC color management,
including spectral data and extended color models, while maintaining
compatibility with existing profiles.
*/
use Display;
pub use Error;
use Zero;
/// Rounds a floating-point value to the specified precision (decimal places).
/// Example: round_to_precision(1.23456, 2) -> 1.23
pub
/// Generic zero-check used by serde skip_serializing_if for many numeric fields.
pub
/// Treats the string as "empty" if it is empty or equals "none" (case-sensitive),
/// used to suppress serialization for some optional fields.
pub
/// Convert ICC s15Fixed16Number to f64 by dividing by 65536.0.
/// This is a signed 32-bit fixed-point with 16 fractional bits.
pub
/// Convert ICC u1.15 fixed number (u16) to f64.
/// Range is [0, ~1.99997]. We scale by (65535 / 32768) and round for compact output.
pub
/// 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.
///
/// Example:
/// ```
/// let data = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC];
/// let formatted = cmx::format_hex_with_spaces(&data);
/// assert_eq!(formatted, "12345678 9abc");
/// ```
///
/// Note: The last chunk may be shorter than 8 characters if the data length is not a multiple of 4.
///
/// 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.
///
/// Example:
/// ```
/// let hex_str = "12345678 9abc";
/// let bytes = cmx::parse_hex_string(hex_str).unwrap();
/// assert_eq!(bytes, vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC]);
/// ```
///
/// Notes:
/// * The input string can contain spaces and is case-insensitive.
/// * Non-hex characters and whitespace are ignored.
///
use ;
;
/// A 15.16 fixed-point number, where the first 15 bits are the integer part and the last 16 bits are the fractional part.
/// This is used in ICC profiles to represent color values.
/// The value is stored as a 32-bit signed integer in big-endian format.