diff --git a/src/main.rs b/src/main.rs index 2050342..e297c67 100644 --- a/src/main.rs +++ b/src/main.rs @@ -221,7 +221,9 @@ fn capture(mode: Mode, common: Common) -> Result<()> { // Thumbnails from earlier captures must not end up in this one. let mut hide = ipc::HideGuard::new(&ipc::socket_path()); - let (shots, clip, source): Shots = match mode { + // Window shots get their (slow) shadow after the shutter sound. + let mut windowed = false; + let (mut shots, clip, source): Shots = match mode { Mode::Screen { all } => { let focused = focused_output(&outputs); let source = outputs[focused].geom.name.clone(); @@ -246,11 +248,15 @@ fn capture(mode: Mode, common: Common) -> Result<()> { } region::Choice::Window => { drop(frames); - window_shot(&outputs, cursor, config.capture.window_shadow)? + windowed = true; + window_shot(&outputs, cursor)? } } } - Mode::Window => window_shot(&outputs, cursor, config.capture.window_shadow)?, + Mode::Window => { + windowed = true; + window_shot(&outputs, cursor)? + } Mode::Zoom => { let focused = focused_output(&outputs); let frame = wl.capture(&outputs[focused..=focused], cursor)?.remove(0); @@ -267,6 +273,11 @@ fn capture(mode: Mode, common: Common) -> Result<()> { } }; sound::play(&config.sound, sound); + if windowed && config.capture.window_shadow { + for (image, _) in &mut shots { + *image = shadow::add(image); + } + } drop(wl); let target = Target::from_args(common.output, common.clipboard_only); @@ -293,9 +304,8 @@ fn capture(mode: Mode, common: Common) -> Result<()> { /// A window shot, shaped like the other modes' results. The preview goes to /// the window's output, or niri's focused one if that is unknown. -fn window_shot(outputs: &[wayland::Output], cursor: bool, shadow: bool) -> Result { +fn window_shot(outputs: &[wayland::Output], cursor: bool) -> Result { let (image, output) = window::capture(cursor)?; - let image = if shadow { shadow::add(&image) } else { image }; let source = output.unwrap_or_else(|| outputs[focused_output(outputs)].geom.name.clone()); Ok((vec![(image, None)], 0, source)) } diff --git a/src/shadow.rs b/src/shadow.rs index 1bfea3b..3c4e71c 100644 --- a/src/shadow.rs +++ b/src/shadow.rs @@ -19,7 +19,7 @@ pub fn add(image: &RgbaImage) -> RgbaImage { alpha[(sy * cw + sx) as usize] = p.0[3] as f32 / 255.0; } } - // Three box blurs approximate a Gaussian of about BLUR / 2 sigma. + // Three box blurs approximate a Gaussian (visible extent about BLUR px). let r = (BLUR / 3).max(1) as usize; for _ in 0..3 { alpha = box_blur(&alpha, cw as usize, ch as usize, r); @@ -69,17 +69,27 @@ fn box_blur(src: &[f32], w: usize, h: usize, r: usize) -> Vec { } } } + // Vertically with a running sum per column, walking rows so memory is + // read in order. let mut out = vec![0f32; src.len()]; - for x in 0..w { - let at = |y: usize| tmp[y * w + x]; - let mut sum: f32 = (0..=r.min(h - 1)).map(at).sum(); - for y in 0..h { - out[y * w + x] = sum / n; - if y + r + 1 < h { - sum += at(y + r + 1); + let mut sums = vec![0f32; w]; + for y in 0..=r.min(h - 1) { + for (sum, v) in sums.iter_mut().zip(&tmp[y * w..(y + 1) * w]) { + *sum += v; + } + } + for y in 0..h { + for (o, sum) in out[y * w..(y + 1) * w].iter_mut().zip(&sums) { + *o = sum / n; + } + if y + r + 1 < h { + for (sum, v) in sums.iter_mut().zip(&tmp[(y + r + 1) * w..(y + r + 2) * w]) { + *sum += v; } - if y >= r { - sum -= at(y - r); + } + if y >= r { + for (sum, v) in sums.iter_mut().zip(&tmp[(y - r) * w..(y - r + 1) * w]) { + *sum -= v; } } }