From 6688e05c5548ed079085821090552b667d1ea02a Mon Sep 17 00:00:00 2001 From: midasmatterhorn <248507632+midasmatterhorn@users.noreply.github.com> Date: Thu, 15 Oct 2026 17:51:42 +0300 Subject: [PATCH] cms: write_cube keeps a non-default input domain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The parser reads and validates DOMAIN_MIN/DOMAIN_MAX and the type documents them as meaningful ('the input domain the table expects'), but the writer never emitted them: a custom-domain .cube re-saved through parse → write_cube came back with the default domain, passed the domain_is_default gate that used to reject it, and every row silently mapped colours over the wrong input range. Emit DOMAIN_MIN/DOMAIN_MAX when the domain is not the default (parse_cube matches keywords line by line and accepts them anywhere; {} on f32 prints the shortest roundtripping form). Regression test: cube_domain_survives_a_write — fails today (the written table loses the domain), round-trips exactly with the emission. --- crates/cms/src/lutfile.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/crates/cms/src/lutfile.rs b/crates/cms/src/lutfile.rs index 880d9a64..1bd0e6e3 100644 --- a/crates/cms/src/lutfile.rs +++ b/crates/cms/src/lutfile.rs @@ -230,6 +230,12 @@ pub fn write_cube(l: &LutFile) -> String { if !l.title.is_empty() { s.push_str(&format!("TITLE \"{}\"\n", l.title.replace('"', "'"))); } + // The domain is what every row means: a table read with a non-default one must not be + // written back as if it expected inputs in 0..1. `parse_cube` accepts these anywhere. + if !l.domain_is_default() { + s.push_str(&format!("DOMAIN_MIN {} {} {}\n", l.domain_min[0], l.domain_min[1], l.domain_min[2])); + s.push_str(&format!("DOMAIN_MAX {} {} {}\n", l.domain_max[0], l.domain_max[1], l.domain_max[2])); + } s.push_str(&format!("LUT_3D_SIZE {}\n", l.size)); for c in l.data.as_chunks::<3>().0 { s.push_str(&format!("{:.6} {:.6} {:.6}\n", c[0], c[1], c[2])); @@ -444,4 +450,25 @@ DOMAIN_MAX 0.5 1 1 } assert!(builtin("nope").is_none()); } + + #[test] + fn cube_domain_survives_a_write() { + let cube = "DOMAIN_MIN 0.25 0.25 0.25 +DOMAIN_MAX 0.75 0.75 0.75 +LUT_3D_SIZE 2 +0 0 0 +0 0 1 +0 1 0 +0 1 1 +1 0 0 +1 0 1 +1 1 0 +1 1 1 +"; + let l = parse_cube(cube).expect("a cube with a non-default domain"); + assert!(!l.domain_is_default()); + let back = parse_cube(&write_cube(&l)).expect("roundtrip"); + assert_eq!(back.domain_min, l.domain_min, "the written table must mean the same"); + assert_eq!(back.domain_max, l.domain_max); + } }