Merge pull request #1302 from vbasky/fix/1194-liquify-offset

Liquify: expose Distortion Mesh Offset in Effect Controls (#1194)
This commit is contained in:
BFlat
2026-10-16 18:10:30 +07:00
committed by GitHub
3 changed files with 46 additions and 2 deletions
+30 -1
View File
@@ -589,7 +589,7 @@ pub fn specs() -> Vec<EffectSpec> {
p("meshColor", "Mesh Color", col(0.5, 0.5, 0.5), ParamUi::Color),
// The strokes (see `LiquifyStroke`); written by the `liquify.stroke` command.
p("distortionMesh", "Distortion Mesh", Value::Str(String::new()), ParamUi::Hidden),
p("distortionMeshOffset", "Distortion Mesh Offset", Value::Vec2([0.0, 0.0]), ParamUi::Hidden),
p("distortionMeshOffset", "Distortion Mesh Offset", Value::Vec2([0.0, 0.0]), ParamUi::Point),
p("distortionPercentage", "Distortion Percentage", num(100.0), slider(0.0, 200.0, 0.0, 200.0, 1)),
p("distortionDisplayQuality", "Distortion Display Quality", Value::Enum(1), popup(&["Draft", "Smooth"])),
],
@@ -672,6 +672,35 @@ mod tests {
assert_eq!(zero.img.data, img.data);
let again = run_fx("ec.distort.liquify", &[("distortionMesh", Value::Str(s.to_line()))], img.clone(), 0.0, EffectEnv::default());
assert_eq!(out.img.data, again.img.data);
// #1194: Distortion Mesh Offset is an exposed, editable control (AE's Distortion ▸
// Offset) and it moves the mesh: the pattern rendered at x renders at x + offset.
let spec = crate::find("ec.distort.liquify").unwrap();
assert!(
spec.params.iter().any(|ps| ps.id == "distortionMeshOffset" && ps.ui == effectcraft_project::ParamUi::Point),
"offset is shown in Effect Controls"
);
let shifted = run_fx(
"ec.distort.liquify",
&[("distortionMesh", Value::Str(s.to_line())), ("distortionMeshOffset", Value::Vec2([20.0, 0.0]))],
img.clone(),
0.0,
EffectEnv::default(),
);
assert_ne!(shifted.img.data, out.img.data, "the offset changes the render");
// The displaced region's centroid moves right by the offset (the mesh is sampled at
// layer minus offset), and the row far left of the shifted mesh is back to untouched.
let centroid = |o: &Image| -> f32 {
let (mut wsum, mut msum) = (0.0f32, 0.0f32);
for (i, (p, q)) in o.data.iter().zip(img.data.iter()).enumerate() {
let d: f32 = (0..4).map(|k| (p[k] - q[k]).abs()).sum();
wsum += d;
msum += d * (i % 64) as f32;
}
if wsum > 1e-6 { msum / wsum } else { 0.0 }
};
let (co, cs) = (centroid(&out.img), centroid(&shifted.img));
assert!((cs - co - 20.0).abs() < 5.0, "centroids {co} -> {cs}");
assert_eq!(shifted.img.get(5, 32), img.get(5, 32), "left of the shifted mesh: untouched");
// Reconstruction undoes the warp; freeze protects.
let mut rec = s.clone();
rec.tool = 9;
+15
View File
@@ -239,6 +239,8 @@ fn refresh(pr: &mut Property, ps: &ParamSpec, spec: &EffectSpec) {
// A path typed as text, or set only by commands, that got a Choose… button (Apply Color
// LUT's LUT, the OCIO files, #514).
(ParamUi::Hidden | ParamUi::Text, ParamUi::File { .. }) => pr.ui = ps.ui.clone(),
// An internal point that became a control (Liquify's Distortion Mesh Offset, #1194).
(ParamUi::Hidden, ParamUi::Point) => pr.ui = ParamUi::Point,
// Same kind of value: take the spec's hints (units, ranges).
(a, b) if std::mem::discriminant(a) == std::mem::discriminant(b) || is_numeric_ui(a) && is_numeric_ui(b) => pr.ui = ps.ui.clone(),
_ => {}
@@ -514,6 +516,19 @@ mod tests {
}
}
/// #1194: Liquify's Distortion Mesh Offset was hidden; old projects show it, keeping its value.
#[test]
fn a_hidden_point_that_became_a_control_is_shown() {
let mut next = 1;
let (spec, mut g) = instance("ec.distort.liquify", &mut next);
let pr = g.get_mut("distortionMeshOffset").unwrap();
pr.ui = ParamUi::Hidden;
pr.value = Value::Vec2([20.0, -5.0]);
assert!(upgrade_instance(spec, &mut g, &mut Ids(&mut next), [100.0, 100.0]));
let pr = g.get("distortionMeshOffset").unwrap();
assert_eq!((&pr.ui, &pr.value), (&ParamUi::Point, &Value::Vec2([20.0, -5.0])));
}
#[test]
fn upgrade_moves_params_into_twirl_down_groups() {
let spec = find(crate::warp_stab::ID).unwrap();
+1 -1
View File
@@ -149,7 +149,7 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
| Corner Pin | `ec.distort.cornerpin` | 4 | GPU | 32 | Implemented |
| Detail-preserving Upscale | `ec.distort.upscale` | 4 | | 32 | Implemented |
| Displacement Map | `ec.distort.displacementmap` | 8 | GPU | 32 | Implemented |
| Liquify | `ec.distort.liquify` | 12 | GPU | 32 | Implemented |
| Liquify | `ec.distort.liquify` | 13 | GPU | 32 | Implemented |
| Magnify | `ec.distort.magnify` | 10 | GPU | 32 | Implemented |
| Mesh Warp | `ec.distort.meshwarp` | 3 | GPU | 32 | Implemented |
| Mirror | `ec.distort.mirror` | 2 | GPU | 32 | Implemented |