Merge PR #8502: fix(actions): accept Photoshop stringID blend modes (#8454)

This commit is contained in:
David Mann
2026-10-22 23:40:19 +02:00
2 changed files with 71 additions and 0 deletions
+18
View File
@@ -306,33 +306,51 @@ fn path_value(text: &str) -> D {
/// Photoshop's `BlnM` enumeration ↔ PhotoCraft's blend mode labels.
const BLEND_MODES: &[(&str, &str)] = &[
("Nrml", "Normal"),
("normal", "Normal"),
("Dslv", "Dissolve"),
("dissolve", "Dissolve"),
("Drkn", "Darken"),
("darken", "Darken"),
("Mltp", "Multiply"),
("multiply", "Multiply"),
("CBrn", "Color Burn"),
("colorBurn", "Color Burn"),
("linearBurn", "Linear Burn"),
("darkerColor", "Darker Color"),
("Lghn", "Lighten"),
("lighten", "Lighten"),
("Scrn", "Screen"),
("screen", "Screen"),
("CDdg", "Color Dodge"),
("colorDodge", "Color Dodge"),
("linearDodge", "Linear Dodge (Add)"),
("lighterColor", "Lighter Color"),
("Ovrl", "Overlay"),
("overlay", "Overlay"),
("SftL", "Soft Light"),
("softLight", "Soft Light"),
("HrdL", "Hard Light"),
("hardLight", "Hard Light"),
("vividLight", "Vivid Light"),
("linearLight", "Linear Light"),
("pinLight", "Pin Light"),
("hardMix", "Hard Mix"),
("Dfrn", "Difference"),
("difference", "Difference"),
("Xclu", "Exclusion"),
("exclusion", "Exclusion"),
("blendSubtraction", "Subtract"),
("blendDivide", "Divide"),
("H", "Hue"),
("hue", "Hue"),
("Strt", "Saturation"),
("saturation", "Saturation"),
("Clr", "Color"),
("color", "Color"),
("Lmns", "Luminosity"),
("luminosity", "Luminosity"),
("PsTh", "Pass Through"),
("passThrough", "Pass Through"),
];
fn blend_label(ps: &str) -> Option<&'static str> {
+53
View File
@@ -3,6 +3,59 @@ use photocraft_psd::atn::{AtnAction, AtnEvent, AtnFile, AtnItem};
use photocraft_psd::descriptor::{Descriptor, Id, ReferenceItem, UnicodeString, Value as D};
use serde_json::json;
#[test]
fn blend_modes_accept_photoshop_string_ids_and_export_canonical_char_ids() {
for (string_id, char_id, label) in [
("normal", "Nrml", "Normal"),
("dissolve", "Dslv", "Dissolve"),
("darken", "Drkn", "Darken"),
("multiply", "Mltp", "Multiply"),
("colorBurn", "CBrn", "Color Burn"),
("lighten", "Lghn", "Lighten"),
("screen", "Scrn", "Screen"),
("colorDodge", "CDdg", "Color Dodge"),
("overlay", "Ovrl", "Overlay"),
("softLight", "SftL", "Soft Light"),
("hardLight", "HrdL", "Hard Light"),
("difference", "Dfrn", "Difference"),
("exclusion", "Xclu", "Exclusion"),
("hue", "H", "Hue"),
("saturation", "Strt", "Saturation"),
("color", "Clr", "Color"),
("luminosity", "Lmns", "Luminosity"),
("passThrough", "PsTh", "Pass Through"),
] {
assert_eq!(blend_label(string_id), Some(label), "{string_id}");
assert_eq!(blend_ps(label), Some(char_id), "{label} remains serialized canonically");
}
}
/// #8454: a Set and a Make layer step whose blend mode is stored as a Photoshop stringID import
/// with that mode (a Set was unsupported, a Make silently fell back to Normal).
#[test]
fn layer_steps_with_string_id_blend_modes_import_with_their_mode() {
let set = desc("null").with("null", target_ref("Lyr ")).with("T ", D::Descriptor(desc("Lyr ").with("Md ", enm("BlnM", "softLight"))));
let make = desc("null").with("null", class_ref("Lyr ")).with("Usng", D::Descriptor(desc("Lyr ").with("Md ", enm("BlnM", "luminosity"))));
let file = AtnFile {
name: UnicodeString::new_nul("Blend"),
expanded: false,
actions: vec![AtnAction {
name: UnicodeString::new_nul("A"),
items: vec![item("setd", "Set", Some(set)), item("Mk ", "Make", Some(make))],
..Default::default()
}],
warnings: vec![],
};
let imp = import(&atn::write(&file).unwrap()).unwrap();
assert!(imp.unsupported.is_empty(), "{:?}", imp.unsupported.iter().map(|u| &u.event).collect::<Vec<_>>());
let steps = &imp.actions[0].steps;
assert_eq!(steps.len(), 2);
assert_eq!(steps[0].0, "layer.setProps");
assert_eq!(steps[0].1["blend"], json!("Soft Light"));
assert_eq!(steps[1].0, "layer.new.layer");
assert_eq!(steps[1].1["mode"], json!("Luminosity"));
}
fn item(event: &str, label: &str, d: Option<Descriptor>) -> AtnItem {
let event = match event.as_bytes() {
[a, b, c, dd] => AtnEvent::Code([*a, *b, *c, *dd]),