diff --git a/crates/algo/src/retouch.rs b/crates/algo/src/retouch.rs index e7d33453d4..00388fe399 100644 --- a/crates/algo/src/retouch.rs +++ b/crates/algo/src/retouch.rs @@ -7,7 +7,6 @@ //! "Protect Tones" works on luminance (scaling RGB so hue and saturation are kept, with gamut //! compression instead of per-channel clipping). -use rayon::prelude::*; use serde::{Deserialize, Serialize}; /// Tonal range targeted by Dodge/Burn. @@ -130,35 +129,43 @@ pub fn local_blur(data: &[f32], w: usize, h: usize, ch: usize, alpha: Option, (ty, dst): (usize, &mut [f32])| { + let y = (y0 as i32 + ty as i32 - r).clamp(0, h as i32 - 1) as usize; + // Clamp and premultiply each source pixel once, rather than once for every kernel tap. + for (rx, px) in row.chunks_exact_mut(ch).enumerate() { + let x = (x0 as i32 + rx as i32 - r).clamp(0, w as i32 - 1) as usize; + let i = (y * w + x) * ch; + px.copy_from_slice(&data[i..i + ch]); + if let Some(a) = alpha { + let av = px[a]; + for (c, v) in px.iter_mut().enumerate() { + if c != a { + *v *= av; } } } - // Contiguous accumulation can be vectorized for any channel layout. Each output still - // sums the same products in the same kernel order as the scalar convolution. - for (i, kv) in k.iter().enumerate() { - for (d, s) in dst.iter_mut().zip(&row[i * ch..i * ch + stride]) { - *d += s * kv; - } + } + // Contiguous accumulation can be vectorized for any channel layout. Each output still + // sums the same products in the same kernel order as the scalar convolution. + for (i, kv) in k.iter().enumerate() { + for (d, s) in dst.iter_mut().zip(&row[i * ch..i * ch + stride]) { + *d += s * kv; } - }, - ); + } + }; + #[cfg(not(target_arch = "wasm32"))] + { + use rayon::prelude::*; + tmp.par_chunks_mut(stride).enumerate().for_each_init(scratch, horizontal); + } + #[cfg(target_arch = "wasm32")] + { + let mut row = scratch(); + tmp.chunks_mut(stride).enumerate().for_each(|item| horizontal(&mut row, item)); + } let mut out = vec![0.0f32; ow * oh * ch]; - out.par_chunks_mut(stride).enumerate().for_each(|(oy, dst)| { + let vertical = |(oy, dst): (usize, &mut [f32])| { for (i, kv) in k.iter().enumerate() { for (d, s) in dst.iter_mut().zip(&tmp[(oy + i) * stride..(oy + i + 1) * stride]) { *d += s * kv; @@ -174,7 +181,16 @@ pub fn local_blur(data: &[f32], w: usize, h: usize, ch: usize, alpha: Option