#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
use crate::filter::InterpolationDetails;
use crate::simd;
use crate::weights::I16WeightTable;
pub struct PlaneResizer {
h_weights: I16WeightTable,
v_weights: I16WeightTable,
src_w: usize,
src_h: usize,
dst_w: usize,
dst_h: usize,
intermediate: Vec<i16>,
}
impl PlaneResizer {
pub fn new(filter: Filter, src_w: u32, src_h: u32, dst_w: u32, dst_h: u32) -> Self {
assert!(src_w > 0 && src_h > 0, "source dimensions must be positive");
assert!(dst_w > 0 && dst_h > 0, "target dimensions must be positive");
let details = InterpolationDetails::create(filter);
let h_weights = I16WeightTable::new(src_w, dst_w, &details);
let v_weights = I16WeightTable::new(src_h, dst_h, &details);
let intermediate = vec![0i16; src_h as usize * dst_w as usize];
Self {
h_weights,
v_weights,
src_w: src_w as usize,
src_h: src_h as usize,
dst_w: dst_w as usize,
dst_h: dst_h as usize,
intermediate,
}
}
pub fn resize_plane(
&mut self,
input: &[i16],
input_stride: usize,
output: &mut [i16],
output_stride: usize,
) {
assert!(
input_stride >= self.src_w,
"input_stride ({input_stride}) must be >= src_w ({})",
self.src_w
);
assert!(
output_stride >= self.dst_w,
"output_stride ({output_stride}) must be >= dst_w ({})",
self.dst_w
);
assert!(
input.len() >= (self.src_h - 1) * input_stride + self.src_w,
"input buffer too small"
);
assert!(
output.len() >= (self.dst_h - 1) * output_stride + self.dst_w,
"output buffer too small"
);
for y in 0..self.src_h {
let in_start = y * input_stride;
let in_row = &input[in_start..in_start + self.src_w];
let out_start = y * self.dst_w;
simd::filter_h_i16_i16(
in_row,
&mut self.intermediate[out_start..out_start + self.dst_w],
&self.h_weights,
1, );
}
if output_stride == self.dst_w {
simd::filter_v_all_i16_i16(
&self.intermediate,
output,
self.dst_w,
self.src_h,
self.dst_h,
&self.v_weights,
);
} else {
let mut temp = vec![0i16; self.dst_h * self.dst_w];
simd::filter_v_all_i16_i16(
&self.intermediate,
&mut temp,
self.dst_w,
self.src_h,
self.dst_h,
&self.v_weights,
);
for y in 0..self.dst_h {
let src_start = y * self.dst_w;
let dst_start = y * output_stride;
output[dst_start..dst_start + self.dst_w]
.copy_from_slice(&temp[src_start..src_start + self.dst_w]);
}
}
}
pub fn src_width(&self) -> usize {
self.src_w
}
pub fn src_height(&self) -> usize {
self.src_h
}
pub fn dst_width(&self) -> usize {
self.dst_w
}
pub fn dst_height(&self) -> usize {
self.dst_h
}
}
use crate::filter::Filter;
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(feature = "std"))]
use alloc::vec;
#[test]
fn constant_plane_preserves_value() {
let src_w = 20u32;
let src_h = 20;
let dst_w = 10u32;
let dst_h = 10;
let mut resizer = PlaneResizer::new(Filter::Robidoux, src_w, src_h, dst_w, dst_h);
let value = 2048i16;
let input = vec![value; src_w as usize * src_h as usize];
let mut output = vec![0i16; dst_w as usize * dst_h as usize];
resizer.resize_plane(&input, src_w as usize, &mut output, dst_w as usize);
for (i, &v) in output.iter().enumerate() {
assert!(
(v - value).unsigned_abs() <= 2,
"pixel {i}: got {v}, expected ~{value}"
);
}
}
#[test]
fn upscale_preserves_value() {
let src_w = 10u32;
let src_h = 10;
let dst_w = 20u32;
let dst_h = 20;
let mut resizer = PlaneResizer::new(Filter::Lanczos, src_w, src_h, dst_w, dst_h);
let value = 1000i16;
let input = vec![value; src_w as usize * src_h as usize];
let mut output = vec![0i16; dst_w as usize * dst_h as usize];
resizer.resize_plane(&input, src_w as usize, &mut output, dst_w as usize);
for (i, &v) in output.iter().enumerate() {
assert!(
(v - value).unsigned_abs() <= 2,
"pixel {i}: got {v}, expected ~{value}"
);
}
}
#[test]
fn resize_with_stride() {
let src_w = 8u32;
let src_h = 8;
let dst_w = 4u32;
let dst_h = 4;
let in_stride = 16usize; let out_stride = 8usize;
let mut resizer = PlaneResizer::new(Filter::Robidoux, src_w, src_h, dst_w, dst_h);
let value = 2000i16;
let mut input = vec![0i16; src_h as usize * in_stride];
for y in 0..src_h as usize {
for x in 0..src_w as usize {
input[y * in_stride + x] = value;
}
}
let mut output = vec![0i16; dst_h as usize * out_stride];
resizer.resize_plane(&input, in_stride, &mut output, out_stride);
for y in 0..dst_h as usize {
for x in 0..dst_w as usize {
let v = output[y * out_stride + x];
assert!(
(v - value).unsigned_abs() <= 2,
"({x},{y}): got {v}, expected ~{value}"
);
}
}
}
#[test]
fn identity_resize() {
let w = 16u32;
let h = 16;
let mut resizer = PlaneResizer::new(Filter::Robidoux, w, h, w, h);
let mut input = vec![0i16; w as usize * h as usize];
for y in 0..h as usize {
for x in 0..w as usize {
input[y * w as usize + x] = ((x + y) * 256 / 32) as i16;
}
}
let mut output = vec![0i16; w as usize * h as usize];
resizer.resize_plane(&input, w as usize, &mut output, w as usize);
for i in 0..input.len() {
assert!(
(output[i] - input[i]).unsigned_abs() <= 2,
"pixel {i}: input={}, output={}",
input[i],
output[i]
);
}
}
#[test]
fn reuse_resizer() {
let mut resizer = PlaneResizer::new(Filter::Robidoux, 20, 20, 10, 10);
let input1 = vec![1000i16; 400];
let mut output1 = vec![0i16; 100];
resizer.resize_plane(&input1, 20, &mut output1, 10);
let input2 = vec![2000i16; 400];
let mut output2 = vec![0i16; 100];
resizer.resize_plane(&input2, 20, &mut output2, 10);
assert_ne!(output1, output2);
let mut output1b = vec![0i16; 100];
resizer.resize_plane(&input1, 20, &mut output1b, 10);
assert_eq!(output1, output1b);
}
}