use crate::error::ImageError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Interpolation {
Nearest,
Bilinear,
Bicubic,
Lanczos,
}
pub fn resize(
image: &[f32],
src_w: usize,
src_h: usize,
dst_w: usize,
dst_h: usize,
method: Interpolation,
) -> Result<Vec<f32>, ImageError> {
if image.len() != src_w * src_h {
return Err(ImageError::BufferLengthMismatch {
expected: src_w * src_h,
got: image.len(),
width: src_w,
height: src_h,
});
}
if dst_w == 0 || dst_h == 0 {
return Err(ImageError::ZeroDimension {
width: dst_w,
height: dst_h,
});
}
let mut output = vec![0.0f32; dst_w * dst_h];
let scale_x = src_w as f32 / dst_w as f32;
let scale_y = src_h as f32 / dst_h as f32;
for dy in 0..dst_h {
for dx in 0..dst_w {
let sx = (dx as f32 + 0.5) * scale_x - 0.5;
let sy = (dy as f32 + 0.5) * scale_y - 0.5;
output[dy * dst_w + dx] = match method {
Interpolation::Nearest => {
let ix = (sx + 0.5) as usize;
let iy = (sy + 0.5) as usize;
let ix = ix.min(src_w - 1);
let iy = iy.min(src_h - 1);
image[iy * src_w + ix]
}
Interpolation::Bilinear => bilinear_sample(image, src_w, src_h, sx, sy),
Interpolation::Bicubic => bicubic_sample(image, src_w, src_h, sx, sy),
Interpolation::Lanczos => lanczos_sample(image, src_w, src_h, sx, sy),
};
}
}
Ok(output)
}
#[inline]
fn clamp_idx(i: isize, size: usize) -> usize {
i.clamp(0, size as isize - 1) as usize
}
fn bilinear_sample(image: &[f32], w: usize, h: usize, x: f32, y: f32) -> f32 {
let x0 = (x.floor() as isize).max(0) as usize;
let y0 = (y.floor() as isize).max(0) as usize;
let x1 = (x0 + 1).min(w - 1);
let y1 = (y0 + 1).min(h - 1);
let fx = (x - x0 as f32).clamp(0.0, 1.0);
let fy = (y - y0 as f32).clamp(0.0, 1.0);
let p00 = image[y0 * w + x0];
let p10 = image[y0 * w + x1];
let p01 = image[y1 * w + x0];
let p11 = image[y1 * w + x1];
p00 * (1.0 - fx) * (1.0 - fy) + p10 * fx * (1.0 - fy) + p01 * (1.0 - fx) * fy + p11 * fx * fy
}
#[inline]
fn cubic_weight(t: f32) -> f32 {
let t = t.abs();
if t <= 1.0 {
(1.5 * t - 2.5) * t * t + 1.0
} else if t < 2.0 {
((-0.5 * t + 2.5) * t - 4.0) * t + 2.0
} else {
0.0
}
}
fn bicubic_sample(image: &[f32], w: usize, h: usize, x: f32, y: f32) -> f32 {
let ix = x.floor() as isize;
let iy = y.floor() as isize;
let fx = x - ix as f32;
let fy = y - iy as f32;
let mut sum = 0.0f64;
for j in -1..=2_isize {
let wy = cubic_weight(fy - j as f32) as f64;
let cy = clamp_idx(iy + j, h);
for i in -1..=2_isize {
let wx = cubic_weight(fx - i as f32) as f64;
let cx = clamp_idx(ix + i, w);
sum += wy * wx * f64::from(image[cy * w + cx]);
}
}
sum as f32
}
#[inline]
fn lanczos_weight(t: f32) -> f32 {
let t = t.abs();
if t < 1e-7 {
1.0
} else if t < 3.0 {
let pi_t = std::f32::consts::PI * t;
let pi_t_over_a = pi_t / 3.0;
(pi_t.sin() * pi_t_over_a.sin()) / (pi_t * pi_t_over_a)
} else {
0.0
}
}
fn lanczos_sample(image: &[f32], w: usize, h: usize, x: f32, y: f32) -> f32 {
let ix = x.floor() as isize;
let iy = y.floor() as isize;
let fx = x - ix as f32;
let fy = y - iy as f32;
let mut sum = 0.0f64;
let mut weight_sum = 0.0f64;
for j in -2..=3_isize {
let wy = lanczos_weight(fy - j as f32) as f64;
let cy = clamp_idx(iy + j, h);
for i in -2..=3_isize {
let wx = lanczos_weight(fx - i as f32) as f64;
let cx = clamp_idx(ix + i, w);
let w_total = wy * wx;
sum += w_total * f64::from(image[cy * w + cx]);
weight_sum += w_total;
}
}
if weight_sum.abs() > 1e-12 {
(sum / weight_sum) as f32
} else {
0.0
}
}