Refuse strict ATN export of Select and Mask with brush strokes

Fixes #7539

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-19 23:42:10 +02:00
co-authored by Claude Haiku 5.5
parent 3a896a5efc
commit 8de2db8764
2 changed files with 43 additions and 1 deletions
+20 -1
View File
@@ -2624,6 +2624,15 @@ macro_rules! adjustment_event {
const RIPPLE_SIZE: &[(&str, &str)] = &[("Sml", "small"), ("Mdm", "medium"), ("Lrg", "large")];
/// Select and Mask to Photoshop. The descriptor has no field for the Refine Edge Brush, so a
/// step with `refineStrokes` can't be exported faithfully.
fn refine_edge_to(_: &str, p: &Value) -> Option<Descriptor> {
if !pixel_target(p) || p.get("refineStrokes").is_some_and(|v| !v.is_null() && v.as_array().is_none_or(|a| !a.is_empty())) {
return None;
}
fields_to(p, REFINE_EDGE, desc("null"))
}
/// Select and Mask (`refineSelectionEdge`). Smart Radius, decontamination and the output follow
/// Photoshop's; the edge itself is PhotoCraft's own matting, so results differ in detail.
const REFINE_EDGE: &[F] = &[
@@ -3000,7 +3009,17 @@ const EVENTS: &[Event] = &[
ev!("Brdr", "border", "Border", "select.modify.border", &[F("Wdth", "radius", Kind::Px)]),
ev!("Grow", "grow", "Grow", "select.grow", &[F("Tlrn", "tolerance", Kind::Long)]),
ev!("Smlr", "similar", "Similar", "select.similar", &[F("Tlrn", "tolerance", Kind::Long)]),
ev!("", "refineSelectionEdge", "Select and Mask", "select.refineEdge", REFINE_EDGE),
Event {
code: "",
name: "refineSelectionEdge",
label: "Select and Mask",
ids: &["select.refineEdge"],
from: |d| match fields_from(d, REFINE_EDGE) {
Some(m) => step("select.refineEdge", Value::Object(m)),
None => Mapped::Unsupported { harmless: false },
},
to: refine_edge_to,
},
// Edit.
Event { code: "Fl ", name: "fill", label: "Fill", ids: &["edit.fill", "paint.bucket"], from: fill_from, to: fill_to },
ev!("Strk", "stroke", "Stroke", "edit.stroke", STROKE),
+23
View File
@@ -1387,3 +1387,26 @@ fn function_keys_keep_shift_and_command() {
}
assert_eq!(super::function_key_name(7, false, false), "F7");
}
/// Photoshop's Select and Mask descriptor as mapped has no field for the Refine Edge Brush,
/// so a step with brush strokes must not export as a plain Select and Mask without them.
#[test]
fn refine_edge_with_brush_strokes_is_not_exported_without_them() {
for mode in ["add", "subtract"] {
let brushed = Action {
name: "Probe".into(),
steps: vec![("select.refineEdge".into(), json!({"radius": 1, "refineStrokes": [{"points": [[5, 4]], "size": 4, "mode": mode}]}))],
..Default::default()
};
assert!(export("Audit", &[&brushed], &[], false).is_err(), "{mode}");
let (_, skipped) = export("Audit", &[&brushed], &[], true).unwrap();
assert_eq!(skipped, vec![Skipped { action: "Probe".into(), step: 0, id: "select.refineEdge".into() }]);
}
for params in [json!({"radius": 1}), json!({"radius": 1, "refineStrokes": []}), json!({"radius": 1, "refineStrokes": null})] {
let plain = Action { name: "Plain".into(), steps: vec![("select.refineEdge".into(), params)], ..Default::default() };
let (bytes, skipped) = export("Audit", &[&plain], &[], false).unwrap();
assert!(skipped.is_empty());
let imported = import(&bytes).unwrap();
assert_eq!(imported.actions[0].steps, vec![("select.refineEdge".to_string(), json!({"radius": 1}))]);
}
}