Write the Levels and Curves composite channel as Chnl.Cmps in .atn export (#8686)

Photoshop records the composite entry of a Levels or Curves Set as
Chnl/Chnl/Cmps and silently leaves the adjustment unchanged when it is
Chnl/Ordn/Trgt, which is what the export wrote. Import already reads both.

Fixes #8543
This commit is contained in:
Gabriel Luís da Silva
2026-10-23 17:40:11 -04:00
committed by GitHub
parent 159fe710a2
commit 7bc6fd8a4f
2 changed files with 29 additions and 5 deletions
+3 -5
View File
@@ -377,13 +377,11 @@ fn channel_key(d: &Descriptor) -> Option<Option<&'static str>> {
})
}
/// A Levels or Curves entry's channel: `Chnl.Cmps` for the composite, as Photoshop records it.
/// Photoshop silently skips a Set whose composite is `Ordn.Trgt` (#8543).
fn channel_ref(key: Option<&str>) -> D {
let value = key.and_then(|k| lookup_back(CHANNELS, k));
D::Reference(vec![ReferenceItem::Enumerated {
class: class("Chnl"),
type_id: code(if value.is_some() { "Chnl" } else { "Ordn" }),
value: code(value.unwrap_or("Trgt")),
}])
D::Reference(vec![ReferenceItem::Enumerated { class: class("Chnl"), type_id: code("Chnl"), value: code(value.unwrap_or("Cmps")) }])
}
// ------------------------------------------------------------------ field tables
+26
View File
@@ -2383,6 +2383,32 @@ fn defaults_photoshop_plays_differently_are_written() {
assert_eq!(get_list(&balance, "ShdL").map(<[D]>::len), Some(3));
}
/// Photoshop records the composite of a Levels or Curves entry as `Chnl.Cmps` and silently skips
/// a Set that names it `Ordn.Trgt` (#8543). A colour channel keeps its own code.
#[test]
fn levels_and_curves_name_the_composite_channel_as_photoshop_records_it() {
let exported = |id: &str, p: Value| export_item(id, &p, "S").and_then(|i| i.descriptor).unwrap_or_else(|| panic!("{id} does not export"));
let channels = |d: &Descriptor| -> Vec<(String, String)> {
get_list(d, "Adjs")
.unwrap_or_default()
.iter()
.filter_map(|e| match e {
D::Descriptor(e) => match get_ref(e, "Chnl")?.first()? {
ReferenceItem::Enumerated { type_id, value, .. } => Some((trimmed(type_id), trimmed(value))),
_ => None,
},
_ => None,
})
.collect()
};
let composite = ("Chnl".to_string(), "Cmps".to_string());
let red = ("Chnl".to_string(), "Rd".to_string());
let levels = exported("image.adjustments.levels", json!({"inBlack": 31, "red": {"inBlack": 2}}));
assert_eq!(channels(&levels), vec![composite.clone(), red.clone()]);
let curves = exported("image.adjustments.curves", json!({"points": [[0, 0], [128, 140], [255, 255]], "red": [[0, 10], [255, 245]]}));
assert_eq!(channels(&curves), vec![composite, red]);
}
/// Photoshop sets one colour per step: a step that sets both doesn't export rather than drop one.
#[test]
fn set_colors_exports_only_a_single_colour() {