Vector masks: validate feather like pixel masks (#4201) (#4202)

layer.vectorMask.edit stored `feather` as f.max(0.0) as f32, so 1e39 became
inf (hard edge, null in info, .pcraft save refused) and any huge value was
accepted. It now takes the same finite 0..=1000 px range and error as
layer.layerMask.edit, checked before the edit so a bad value leaves the mask
and history untouched. The params doc says so.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
David Mann
2026-10-12 21:17:14 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 63a0e43e74
commit e4f15eecdf
2 changed files with 37 additions and 3 deletions
+15 -3
View File
@@ -1415,6 +1415,16 @@ fn vector_mask_edit(s: &mut Session, p: &Value) -> Result<Value> {
const CMD: &str = "layer.vectorMask.edit";
let id = layer_id(s, p)?;
let path = p.get("path").map(parse_path).transpose().map_err(|e| bad(CMD, e))?;
// Same range as a pixel mask's feather (`layer.layerMask.edit`), checked before any edit (#4201).
let feather = p
.get("feather")
.map(|v| {
v.as_f64()
.filter(|n| n.is_finite() && (0.0..=1000.0).contains(n))
.map(|n| n as f32)
.ok_or_else(|| bad(CMD, "`feather` must be a finite number within 0..1000"))
})
.transpose()?;
s.edit("Edit Vector Mask", |doc, _| {
let l = doc.layer_mut(id).ok_or(EngineError::NoLayer(id))?;
let vm = l.vector_mask.as_mut().ok_or_else(|| EngineError::Other("layer has no vector mask".into()))?;
@@ -1430,8 +1440,8 @@ fn vector_mask_edit(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(d) = f64p(p, "density") {
vm.density = (d / 100.0).clamp(0.0, 1.0) as f32;
}
if let Some(f) = f64p(p, "feather") {
vm.feather = f.max(0.0) as f32;
if let Some(f) = feather {
vm.feather = f;
}
if let Some(b) = p.get("invert").and_then(Value::as_bool)
&& b
@@ -1821,7 +1831,9 @@ pub fn specs() -> Vec<CommandSpec> {
"layer.vectorMask.edit",
"Edit Vector Mask",
[],
leak(format!(r##"{{"layer":id?,"path":{{…}}?,"enabled":bool?,"linked":bool?,"density":0..100?,"feather":px?,"invert":true?}}. {PATH_FORM}"##)),
leak(format!(
r##"{{"layer":id?,"path":{{…}}?,"enabled":bool?,"linked":bool?,"density":0..100?,"feather":0..1000? (pixels),"invert":true?}}. {PATH_FORM}"##
)),
has_layer,
vector_mask_edit
),