Skip to main content

icc_probe/
icc_probe.rs

1use stet_graphics::icc::{BpcMode, IccCache, IccCacheOptions};
2
3fn dump_profile_shape(bytes: &[u8]) {
4    use moxcms::{ColorProfile, LutWarehouse};
5    let profile = match ColorProfile::new_from_slice(bytes) {
6        Ok(p) => p,
7        Err(e) => {
8            eprintln!("parse error: {e:?}");
9            return;
10        }
11    };
12    eprintln!(
13        "profile: cs={:?} pcs={:?} version={:?}",
14        profile.color_space,
15        profile.pcs,
16        profile.version()
17    );
18    let names = [
19        (
20            "a_to_b_perceptual (A2B0)",
21            profile.lut_a_to_b_perceptual.as_ref(),
22        ),
23        (
24            "a_to_b_colorimetric (A2B1)",
25            profile.lut_a_to_b_colorimetric.as_ref(),
26        ),
27        (
28            "a_to_b_saturation (A2B2)",
29            profile.lut_a_to_b_saturation.as_ref(),
30        ),
31        (
32            "b_to_a_perceptual (B2A0)",
33            profile.lut_b_to_a_perceptual.as_ref(),
34        ),
35    ];
36    for (name, slot) in names {
37        match slot {
38            None => eprintln!("  {name}: missing"),
39            Some(LutWarehouse::Lut(lut)) => {
40                eprintln!(
41                    "  {name}: Lut(in={} out={} grid={} type={:?})",
42                    lut.num_input_channels,
43                    lut.num_output_channels,
44                    lut.num_clut_grid_points,
45                    lut.lut_type
46                );
47            }
48            Some(LutWarehouse::Multidimensional(m)) => {
49                eprintln!(
50                    "  {name}: mAB(in={} out={} grids={:?})",
51                    m.num_input_channels, m.num_output_channels, m.grid_points
52                );
53            }
54        }
55    }
56}
57
58fn main() {
59    let path = std::env::args()
60        .nth(1)
61        .expect("usage: icc_probe <profile.icc>");
62    let bytes = std::fs::read(&path).expect("read profile");
63    dump_profile_shape(&bytes);
64    let cases: &[(f64, f64, f64, f64)] = &[
65        (0.15, 1.0, 1.0, 0.0),
66        (0.0, 0.6, 1.0, 0.0),
67        (0.0, 0.5, 0.9, 0.0),
68        (0.0, 0.7, 1.0, 0.0),
69        (0.15, 0.7, 1.0, 0.0),
70        (0.0, 1.0, 1.0, 0.0),
71        (0.0, 0.0, 0.0, 1.0),
72        (0.0, 0.0, 0.0, 0.0),
73        // Medium-light green range — covers the 0001056.pdf glove regression.
74        (0.15, 0.05, 0.30, 0.0),
75        (0.25, 0.10, 0.40, 0.0),
76        (0.40, 0.15, 0.50, 0.0),
77        (0.40, 0.0, 1.0, 0.0),
78        (0.60, 0.0, 0.80, 0.0),
79        (0.20, 0.20, 0.20, 0.0),
80        // Process primaries — likely out of sRGB gamut.
81        (1.0, 0.0, 0.0, 0.0),
82        (0.0, 1.0, 0.0, 0.0),
83        (0.0, 0.0, 1.0, 0.0),
84        (1.0, 1.0, 0.0, 0.0),
85    ];
86    for mode in [BpcMode::Off, BpcMode::On] {
87        println!("\n== BPC {:?} ==", mode);
88        let opts = IccCacheOptions {
89            bpc_mode: mode,
90            source_cmyk_profile: Some(bytes.clone()),
91        };
92        let cache = IccCache::new_with_options(opts);
93        for &(c, m, y, k) in cases {
94            let rgb = cache.convert_cmyk_readonly(c, m, y, k).expect("convert");
95            println!(
96                "  CMYK({:.2},{:.2},{:.2},{:.2}) -> ({}, {}, {})",
97                c,
98                m,
99                y,
100                k,
101                (rgb.0 * 255.0).round() as u8,
102                (rgb.1 * 255.0).round() as u8,
103                (rgb.2 * 255.0).round() as u8,
104            );
105        }
106    }
107}