Files
photocraft/crates/engine/tests/path_blur_speed_clamp.rs
83cfc3d9ef fix(algo): Path Blur clamps per-path speeds (#708) (#840)
* fix(algo): Path Blur clamps per-path speeds (#708)

filter.blurGallery.pathBlur clamps the scalar speed to 0..500, but
each entry of the documented paths array was deserialized without any
clamp. The halo takes the maximum speed and computes
Radius(v.max(0.0).ceil() as i32 + 1): a large finite speed saturates
the float-to-int cast to i32::MAX and the + 1 overflows, so the command
dies in a caught panic (and wraps to a negative halo with overflow
checks off).

Clamp each entry speed to the same 0..500 the scalar path uses, right
where the params are built.

* Path Blur test: drop an unused before-sample (a flat background can't change under blur)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Fenty <bfenty@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:06:10 -04:00

38 lines
1.7 KiB
Rust

//! Regression tests for issue #708: `filter.blurGallery.pathBlur` clamps the
//! scalar `speed`, but each entry of the documented `paths` array is
//! deserialized without a clamp. A huge finite speed saturates the
//! float-to-i32 cast in the halo and the `+ 1` overflows, which the dispatch
//! turns into an internal-error panic. The command must clamp like the scalar
//! path does and blur normally.
use photocraft_engine::Session;
use serde_json::json;
fn path_blur(speed: f64) -> Result<serde_json::Value, photocraft_engine::EngineError> {
let mut s = Session::new();
s.execute("file.new", json!({"width": 16, "height": 16, "background": "#808080"})).unwrap();
s.execute("filter.blurGallery.pathBlur", json!({"paths": [{"points": [[0.2, 0.5], [0.8, 0.5]], "speed": speed}]}))
}
#[test]
fn path_blur_huge_entry_speed_does_not_panic() {
// On main this comes back as a caught panic ("internal error" style)
// from overflow in the halo radius. After the clamp it must succeed.
let r = path_blur(1.0e20);
assert!(r.is_ok(), "huge entry speed must clamp, got error: {r:?}");
}
#[test]
fn path_blur_negative_entry_speed_does_not_panic() {
let r = path_blur(-1.0e20);
assert!(r.is_ok(), "huge negative entry speed must clamp, got error: {r:?}");
}
#[test]
fn path_blur_normal_entry_speed_still_blurs() {
// Control: a documented in-range speed keeps working.
let mut s = Session::new();
s.execute("file.new", json!({"width": 16, "height": 16, "background": "#808080"})).unwrap();
s.execute("filter.blurGallery.pathBlur", json!({"paths": [{"points": [[0.2, 0.5], [0.8, 0.5]], "speed": 120.0}]})).expect("in-range speed must succeed");
}