Preserve document and history when Fade cannot allocate its buffers. (#4593)

This commit is contained in:
Vikram Bhaskaran 2026-10-14 03:46:10 +11:00 committed by GitHub
parent f389d4ee64
commit b52115fc0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 28 additions and 7 deletions

View File

@ -151,14 +151,14 @@ fn surface_of(doc: &Document, id: LayerId, mask: bool) -> Option<&Surface> {
/// Blend `after` back towards `before` over `area`: `before + opacity × (B(before, after) − before)`
/// with blend mode `mode` (premultiplied, so alpha changes fade too).
pub fn fade_surface(before: &Surface, after: &mut Surface, area: Rect, mode: BlendMode, opacity: f32) {
pub fn fade_surface(before: &Surface, after: &mut Surface, area: Rect, mode: BlendMode, opacity: f32) -> Result<()> {
let fmt = after.format();
let n = fmt.channels();
if area.is_empty() || n == 0 {
return;
return Ok(());
}
let b = before.read_region(area);
let mut a = after.read_region(area);
let b = crate::allocation::read_region(before, area, "fading pixels")?;
let mut a = crate::allocation::read_region(after, area, "fading pixels")?;
let k = opacity.clamp(0.0, 1.0);
let lerp_premul = |pb: &[f32], pa: &[f32], out: &mut [f32], alpha: bool| {
let m = out.len();
@ -176,7 +176,7 @@ pub fn fade_surface(before: &Surface, after: &mut Surface, area: Rect, mode: Ble
}
out[m - 1] = ao;
};
let mut tmp = vec![0.0f32; n];
let mut tmp = crate::allocation::filled(n, 0.0f32, "fading pixels")?;
for (pa, pb) in a.chunks_exact_mut(n).zip(b.chunks_exact(n)) {
if mode == BlendMode::Normal || n == 1 {
lerp_premul(pb, pa, &mut tmp, fmt.alpha);
@ -192,7 +192,8 @@ pub fn fade_surface(before: &Surface, after: &mut Surface, area: Rect, mode: Ble
pa.copy_from_slice(&enc[..n]);
}
}
after.write_region(area, &a);
after.try_write_region(area, &a)?;
Ok(())
}
fn fade(s: &mut Session, p: &Value) -> Result<Value> {
@ -212,7 +213,7 @@ fn fade(s: &mut Session, p: &Value) -> Result<Value> {
s.edit(&label, |doc, _| {
let l = doc.layer_mut(src.layer).ok_or(EngineError::NoLayer(src.layer))?;
let surf = if src.mask { l.mask.as_mut().map(|m| &mut m.surface) } else { l.surface_mut() }.ok_or(EngineError::NoLayer(src.layer))?;
fade_surface(&before, surf, area, mode, opacity);
fade_surface(&before, surf, area, mode, opacity)?;
surf.prune();
Ok(())
})?;

View File

@ -544,3 +544,23 @@ fn backward_change_find_moves_past_the_replaced_text() {
assert_eq!((r["changed"]["start"].as_u64(), r["changed"]["end"].as_u64()), (Some(0), Some(3)), "{r}");
assert_eq!((r["found"]["start"].as_u64(), r["found"]["end"].as_u64()), (Some(8), Some(11)), "{r}");
}
#[test]
fn fade_allocation_failure_keeps_document_and_history() {
for depth in [8, 16, 32] {
let mut s = session(depth);
s.execute("edit.fill", json!({"color": "#000000"})).unwrap();
let before = s.active().unwrap().doc.clone();
let history = s.active().unwrap().history.entries();
crate::allocation::fail_next_for_test();
let result = s.execute("edit.fade", json!({"opacity": 25}));
let _ = crate::allocation::capacity::<u8>(0, "reset failpoint");
assert!(result.is_err());
assert_eq!(*s.active().unwrap().doc, *before);
assert_eq!(s.active().unwrap().history.entries(), history);
assert!(s.is_enabled("edit.fade"));
s.execute("edit.fade", json!({"opacity": 25})).unwrap();
assert!(s.undo());
assert_eq!(*s.active().unwrap().doc, *before);
}
}