perf: parallelize image-pyramid reduce/expand (multi-band blending)

The Burt-Adelson REDUCE/EXPAND passes write disjoint output rows; both now
use the wasm-safe par_rows helper. Speeds up every multi-band blend
(Auto-Blend Layers, Photomerge, Merge to HDR). pyramid tests + wasm green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas 2026-10-02 21:44:28 -04:00
parent 2b6189fc86
commit fcfcfbddf9
1 changed files with 14 additions and 12 deletions

View File

@ -18,6 +18,8 @@ pub struct Level {
pub px: Vec<f32>,
}
use crate::photo_util::par_rows;
const K: [f32; 5] = [1.0 / 16.0, 4.0 / 16.0, 6.0 / 16.0, 4.0 / 16.0, 1.0 / 16.0];
/// 5-tap binomial filter + decimation by two (Burt & Adelson's REDUCE).
@ -25,34 +27,34 @@ pub fn reduce(l: &Level, ch: usize) -> Level {
let (w, h) = (l.w.div_ceil(2), l.h.div_ceil(2));
// Horizontal pass on even columns, then vertical on even rows.
let mut tmp = vec![0.0f32; w * l.h * ch];
for y in 0..l.h {
par_rows(&mut tmp, w, ch, |y, trow| {
for x in 0..w {
for (k, wt) in K.iter().enumerate() {
let sx = (2 * x as i64 + k as i64 - 2).clamp(0, l.w as i64 - 1) as usize;
for c in 0..ch {
tmp[(y * w + x) * ch + c] += wt * l.px[(y * l.w + sx) * ch + c];
trow[x * ch + c] += wt * l.px[(y * l.w + sx) * ch + c];
}
}
}
}
});
let mut px = vec![0.0f32; w * h * ch];
for y in 0..h {
par_rows(&mut px, w, ch, |y, prow| {
for (k, wt) in K.iter().enumerate() {
let sy = (2 * y as i64 + k as i64 - 2).clamp(0, l.h as i64 - 1) as usize;
for x in 0..w {
for c in 0..ch {
px[(y * w + x) * ch + c] += wt * tmp[(sy * w + x) * ch + c];
prow[x * ch + c] += wt * tmp[(sy * w + x) * ch + c];
}
}
}
}
});
Level { w, h, px }
}
/// Upsample `l` to `w × h` (EXPAND: zero insertion + the same filter, ×4 gain).
pub fn expand(l: &Level, ch: usize, w: usize, h: usize) -> Vec<f32> {
let mut tmp = vec![0.0f32; w * l.h * ch];
for y in 0..l.h {
par_rows(&mut tmp, w, ch, |y, trow| {
for x in 0..w {
for (k, wt) in K.iter().enumerate() {
let t = x as i64 + k as i64 - 2;
@ -61,13 +63,13 @@ pub fn expand(l: &Level, ch: usize, w: usize, h: usize) -> Vec<f32> {
}
let sx = (t / 2).clamp(0, l.w as i64 - 1) as usize;
for c in 0..ch {
tmp[(y * w + x) * ch + c] += 2.0 * wt * l.px[(y * l.w + sx) * ch + c];
trow[x * ch + c] += 2.0 * wt * l.px[(y * l.w + sx) * ch + c];
}
}
}
}
});
let mut out = vec![0.0f32; w * h * ch];
for y in 0..h {
par_rows(&mut out, w, ch, |y, orow| {
for (k, wt) in K.iter().enumerate() {
let t = y as i64 + k as i64 - 2;
if t.rem_euclid(2) != 0 {
@ -76,11 +78,11 @@ pub fn expand(l: &Level, ch: usize, w: usize, h: usize) -> Vec<f32> {
let sy = (t / 2).clamp(0, l.h as i64 - 1) as usize;
for x in 0..w {
for c in 0..ch {
out[(y * w + x) * ch + c] += 2.0 * wt * tmp[(sy * w + x) * ch + c];
orow[x * ch + c] += 2.0 * wt * tmp[(sy * w + x) * ch + c];
}
}
}
}
});
out
}