From 6540c9ab447131724647e7838e1090ea82cf449a Mon Sep 17 00:00:00 2001 From: arnav-hiwarkar Date: Wed, 11 Nov 2026 17:44:01 +0530 Subject: [PATCH] #3454: Apply Color LUT loads files whose path contains LUT_ load_lut took any source containing "LUT_" for inline .cube text, so a path like /luts/LUT_Rec709.cube was parsed as text, failed, and the effect passed the image through. A .cube can only parse with a size header and data rows on separate lines, so a newline (after trimming, as the path already is) is what tells text from a path. --- crates/effects/src/utility.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/effects/src/utility.rs b/crates/effects/src/utility.rs index fca9d607e..f0275eab1 100644 --- a/crates/effects/src/utility.rs +++ b/crates/effects/src/utility.rs @@ -344,7 +344,9 @@ pub fn load_lut(src: &str) -> Option> { { return v.clone(); } - let inline = src.contains('\n') || src.contains("LUT_"); + // `.cube` text always spans lines (a size header, then data rows); a path never does, even one + // whose file name contains `LUT_` (#3454). Trimmed, as the path is, so a trailing newline is a path. + let inline = src.trim().contains('\n'); let parsed = if inline { parse_cube(src) } else { std::fs::read_to_string(src.trim()).ok().and_then(|t| parse_cube(&t)) }.map(Arc::new); if let Ok(mut m) = cache.lock() { m.insert(src.to_string(), parsed.clone()); @@ -484,6 +486,20 @@ mod tests { assert_eq!(out.img, img); } + /// #3454: a path containing `LUT_` was taken for `.cube` text and the file never loaded. + #[test] + fn a_path_whose_name_contains_lut_loads_the_file() { + let dir = std::env::temp_dir().join(format!("ec-fx-3454-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let path = dir.join("LUT_x.cube"); + std::fs::write(&path, identity_cube(2)).unwrap(); + let path = path.to_string_lossy().into_owned(); + assert!(load_lut(&path).is_some(), "{path}"); + assert!(load_lut(&format!("{path}\r\n")).is_some(), "{path} with a trailing newline"); + assert!(load_lut(&identity_cube(2)).is_some(), "inline text"); + std::fs::remove_dir_all(&dir).unwrap(); + } + #[test] fn one_d_lut_with_domain() { let lut = parse_cube("LUT_1D_SIZE 2\nDOMAIN_MIN 0 0 0\nDOMAIN_MAX 2 2 2\n1 1 1\n0 0 0\n").unwrap();