use image::{GrayImage, ImageBuffer};
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
use std::arch::wasm32::*;
pub fn build_pyramid(image: &GrayImage, levels: usize) -> Vec<GrayImage> {
let mut pyramid = Vec::new();
build_pyramid_into(image, levels, &mut pyramid);
pyramid
}
pub fn build_pyramid_into(image: &GrayImage, levels: usize, pyramid: &mut Vec<GrayImage>) {
ensure_level(pyramid, 0, image.width(), image.height());
pyramid[0].copy_from_slice(image.as_raw());
let mut produced = 1;
for level in 1..levels {
let (prev_w, prev_h) = pyramid[level - 1].dimensions();
if prev_w < 2 || prev_h < 2 {
break;
}
let (new_w, new_h) = (prev_w / 2, prev_h / 2);
ensure_level(pyramid, level, new_w, new_h);
let (head, tail) = pyramid.split_at_mut(level);
let previous_level = &head[level - 1];
let new_image = &mut tail[0];
downsample_2x2_into(
previous_level.as_raw(),
prev_w as usize,
new_w as usize,
new_h as usize,
&mut **new_image,
);
produced += 1;
}
pyramid.truncate(produced);
}
fn downsample_2x2_into(src: &[u8], prev_w: usize, new_w: usize, new_h: usize, dst: &mut [u8]) {
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
{
unsafe { downsample_2x2_simd128(src, prev_w, new_w, new_h, dst) }
}
#[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))]
{
downsample_2x2_scalar(src, prev_w, new_w, new_h, dst);
}
}
#[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))]
fn downsample_2x2_scalar(src: &[u8], prev_w: usize, new_w: usize, new_h: usize, dst: &mut [u8]) {
for y in 0..new_h {
let r0 = (2 * y) * prev_w;
let r1 = r0 + prev_w;
let out_row = y * new_w;
for x in 0..new_w {
let px = 2 * x;
unsafe {
let s = *src.get_unchecked(r0 + px) as u32
+ *src.get_unchecked(r0 + px + 1) as u32
+ *src.get_unchecked(r1 + px) as u32
+ *src.get_unchecked(r1 + px + 1) as u32;
*dst.get_unchecked_mut(out_row + x) = (s / 4) as u8;
}
}
}
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
#[target_feature(enable = "simd128")]
unsafe fn downsample_2x2_simd128(
src: &[u8],
prev_w: usize,
new_w: usize,
new_h: usize,
dst: &mut [u8],
) {
let chunks = new_w / 16;
for y in 0..new_h {
let r0 = (2 * y) * prev_w;
let r1 = r0 + prev_w;
let out_row = y * new_w;
for c in 0..chunks {
let out_x = c * 16;
let in_x = out_x * 2; let (s0a, s0b, s1a, s1b) = unsafe {
(
v128_load(src.as_ptr().add(r0 + in_x) as *const v128),
v128_load(src.as_ptr().add(r0 + in_x + 16) as *const v128),
v128_load(src.as_ptr().add(r1 + in_x) as *const v128),
v128_load(src.as_ptr().add(r1 + in_x + 16) as *const v128),
)
};
let lo = u16x8_add(
u16x8_extadd_pairwise_u8x16(s0a),
u16x8_extadd_pairwise_u8x16(s1a),
);
let hi = u16x8_add(
u16x8_extadd_pairwise_u8x16(s0b),
u16x8_extadd_pairwise_u8x16(s1b),
);
let packed = u8x16_narrow_i16x8(u16x8_shr(lo, 2), u16x8_shr(hi, 2));
unsafe {
v128_store(dst.as_mut_ptr().add(out_row + out_x) as *mut v128, packed);
}
}
for x in (chunks * 16)..new_w {
let px = 2 * x;
unsafe {
let s = *src.get_unchecked(r0 + px) as u32
+ *src.get_unchecked(r0 + px + 1) as u32
+ *src.get_unchecked(r1 + px) as u32
+ *src.get_unchecked(r1 + px + 1) as u32;
*dst.get_unchecked_mut(out_row + x) = (s / 4) as u8;
}
}
}
}
fn ensure_level(pyramid: &mut Vec<GrayImage>, index: usize, width: u32, height: u32) {
if index < pyramid.len() {
if pyramid[index].dimensions() != (width, height) {
pyramid[index] = ImageBuffer::new(width, height);
}
} else {
pyramid.push(ImageBuffer::new(width, height));
}
}
#[cfg(test)]
mod tests {
use super::*;
use image::Luma;
fn make_image(width: u32, height: u32) -> GrayImage {
let mut img = GrayImage::new(width, height);
for y in 0..height {
for x in 0..width {
let v = ((x * 37 + y * 19 + (x ^ y) * 7) & 0xff) as u8;
img.put_pixel(x, y, Luma([v]));
}
}
img
}
fn reference_half(prev: &GrayImage) -> GrayImage {
let (pw, ph) = prev.dimensions();
let (nw, nh) = (pw / 2, ph / 2);
let mut out = GrayImage::new(nw, nh);
for y in 0..nh {
for x in 0..nw {
let (px, py) = (2 * x, 2 * y);
let s = prev.get_pixel(px, py)[0] as u32
+ prev.get_pixel(px + 1, py)[0] as u32
+ prev.get_pixel(px, py + 1)[0] as u32
+ prev.get_pixel(px + 1, py + 1)[0] as u32;
out.put_pixel(x, y, Luma([(s / 4) as u8]));
}
}
out
}
#[test]
fn pyramid_matches_naive_reference() {
for (w, h) in [(64u32, 48u32), (65, 49), (37, 71), (16, 16), (3, 3)] {
let img = make_image(w, h);
let pyr = build_pyramid(&img, 4);
assert_eq!(pyr[0], img);
let mut expected = img.clone();
for level in pyr.iter().skip(1) {
expected = reference_half(&expected);
assert_eq!(level, &expected, "level mismatch at {w}x{h}");
}
}
}
}