mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-12 05:02:51 +00:00
layer.layerStyle.globalLight cast its params straight to f32, so an angle of 1e39 became inf and rem_euclid turned it into NaN, which was stored, recorded as a history step and fed to every effect using global light. Angle and altitude are now read as f64 and rejected with BadParams when non-finite or beyond f32 range, before the edit. In-range wrapping and the altitude clamp are unchanged. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
0fe109f7b2
commit
2f0979f61f
2 files changed
+26
-2
No files matched your search
@@ -612,8 +612,14 @@ fn blend_range(v: &Value) -> Option<BlendRange> {
|
||||
|
||||
fn global_light(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let cur = s.active().ok_or(EngineError::NoDocument)?.doc.global_light;
|
||||
let angle = num(p, "angle", cur.angle);
|
||||
let altitude = num(p, "altitude", cur.altitude).clamp(0.0, 90.0);
|
||||
// Read as f64 and reject what f32 can't hold: 1e39 would become inf, and rem_euclid NaN.
|
||||
let f32_param = |key: &str, default: f32| match p.get(key).and_then(Value::as_f64) {
|
||||
None => Ok(default),
|
||||
Some(v) if v.is_finite() && v.abs() <= f64::from(f32::MAX) => Ok(v as f32),
|
||||
Some(_) => Err(bad("layer.layerStyle.globalLight", format!("{key} must be a finite number"))),
|
||||
};
|
||||
let angle = f32_param("angle", cur.angle)?;
|
||||
let altitude = f32_param("altitude", cur.altitude)?.clamp(0.0, 90.0);
|
||||
let angle = (angle + 180.0).rem_euclid(360.0) - 180.0;
|
||||
s.edit("Global Light", |doc, _| {
|
||||
doc.global_light.angle = angle;
|
||||
|
||||
@@ -140,6 +140,24 @@ fn global_light_blending_options_scale_effects() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn global_light_rejects_numbers_beyond_f32() {
|
||||
let mut s = session(8, "rgb");
|
||||
// In-range values still wrap into -180..=180 and clamp to 0..=90.
|
||||
s.execute("layer.layerStyle.globalLight", json!({"angle": 450, "altitude": 120})).unwrap();
|
||||
assert_eq!((doc(&s).global_light.angle, doc(&s).global_light.altitude), (90.0, 90.0));
|
||||
s.execute("layer.layerStyle.globalLight", json!({"angle": 540, "altitude": 30})).unwrap();
|
||||
assert_eq!((doc(&s).global_light.angle.abs(), doc(&s).global_light.altitude), (180.0, 30.0));
|
||||
let before = doc(&s).global_light;
|
||||
let steps = s.active().unwrap().history.past_len();
|
||||
// 1e39 overflowed f32 to inf, which rem_euclid turned into a stored NaN (#4181).
|
||||
for p in [json!({"angle": 1e39}), json!({"angle": -1e39}), json!({"altitude": 1e39}), json!({"altitude": -1e39})] {
|
||||
assert!(s.execute("layer.layerStyle.globalLight", p.clone()).is_err(), "{p}");
|
||||
assert_eq!(doc(&s).global_light, before, "{p}");
|
||||
assert_eq!(s.active().unwrap().history.past_len(), steps, "{p}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blending_options_blend_if() {
|
||||
for depth in DEPTHS {
|
||||
|
||||
Reference in new issue
Block a user