use crate::error::Result;
use image::{DynamicImage, GenericImageView, ImageBuffer, Pixel, Rgba};
use scirs2_core::ndarray::{Array1, Array2};
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum InterpolationMethod {
Nearest,
#[default]
Bilinear,
Bicubic,
Lanczos3,
EdgePreserving,
}
#[allow(dead_code)]
pub fn resize(
src: &DynamicImage,
width: u32,
height: u32,
method: InterpolationMethod,
) -> Result<DynamicImage> {
let (src_width, src_height) = src.dimensions();
if src_width == width && src_height == height {
return Ok(src.clone());
}
let mut dst: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::new(width, height);
let scale_x = src_width as f64 / width as f64;
let scale_y = src_height as f64 / height as f64;
match method {
InterpolationMethod::Nearest => {
for y in 0..height {
for x in 0..width {
let src_x = (x as f64 * scale_x).floor() as u32;
let src_y = (y as f64 * scale_y).floor() as u32;
let src_x = src_x.min(src_width - 1);
let src_y = src_y.min(src_height - 1);
let pixel = src.get_pixel(src_x, src_y);
dst.put_pixel(x, y, pixel);
}
}
}
InterpolationMethod::Bilinear => {
for y in 0..height {
for x in 0..width {
let src_x = x as f64 * scale_x;
let src_y = y as f64 * scale_y;
let pixel = bilinear_interpolate(src, src_x, src_y);
dst.put_pixel(x, y, pixel);
}
}
}
InterpolationMethod::Bicubic => {
for y in 0..height {
for x in 0..width {
let src_x = x as f64 * scale_x;
let src_y = y as f64 * scale_y;
let pixel = bicubic_interpolate(src, src_x, src_y);
dst.put_pixel(x, y, pixel);
}
}
}
InterpolationMethod::Lanczos3 => {
for y in 0..height {
for x in 0..width {
let src_x = x as f64 * scale_x;
let src_y = y as f64 * scale_y;
let pixel = lanczos_interpolate(src, src_x, src_y, 3);
dst.put_pixel(x, y, pixel);
}
}
}
InterpolationMethod::EdgePreserving => {
return resize_edge_preserving(src, width, height);
}
}
Ok(DynamicImage::ImageRgba8(dst))
}
#[allow(dead_code)]
fn bilinear_interpolate(src: &DynamicImage, x: f64, y: f64) -> Rgba<u8> {
let (width, height) = src.dimensions();
let x0 = x.floor() as u32;
let y0 = y.floor() as u32;
let x1 = (x0 + 1).min(width - 1);
let y1 = (y0 + 1).min(height - 1);
let dx = x - x0 as f64;
let dy = y - y0 as f64;
let p00 = src.get_pixel(x0, y0).to_rgba();
let p01 = src.get_pixel(x0, y1).to_rgba();
let p10 = src.get_pixel(x1, y0).to_rgba();
let p11 = src.get_pixel(x1, y1).to_rgba();
let mut result = [0u8; 4];
for c in 0..4 {
let c00 = p00[c] as f64;
let c01 = p01[c] as f64;
let c10 = p10[c] as f64;
let c11 = p11[c] as f64;
let value = (1.0 - dx) * (1.0 - dy) * c00
+ dx * (1.0 - dy) * c10
+ (1.0 - dx) * dy * c01
+ dx * dy * c11;
result[c] = value.round().clamp(0.0, 255.0) as u8;
}
Rgba(result)
}
#[allow(dead_code)]
fn cubic_hermite(x: f64) -> f64 {
let x = x.abs();
if x < 1.0 {
return (2.0 - x * x * (3.0 - 2.0 * x)).clamp(0.0, 1.0);
} else if x < 2.0 {
return (4.0 - 8.0 * x + 5.0 * x * x - x * x * x).clamp(0.0, 1.0) / 2.0;
}
0.0
}
#[allow(dead_code)]
fn bicubic_interpolate(src: &DynamicImage, x: f64, y: f64) -> Rgba<u8> {
let (width, height) = src.dimensions();
let x_base = x.floor() as i32;
let y_base = y.floor() as i32;
let dx = x - x_base as f64;
let dy = y - y_base as f64;
let wx = [
cubic_hermite(dx + 1.0),
cubic_hermite(dx),
cubic_hermite(1.0 - dx),
cubic_hermite(2.0 - dx),
];
let wy = [
cubic_hermite(dy + 1.0),
cubic_hermite(dy),
cubic_hermite(1.0 - dy),
cubic_hermite(2.0 - dy),
];
let mut result = [0.0; 4];
for c in 0..4 {
let mut sum = 0.0;
let mut weight_sum = 0.0;
for ky in 0..4 {
let y_sample = y_base - 1 + ky;
if y_sample < 0 || y_sample >= height as i32 {
continue;
}
for kx in 0..4 {
let x_sample = x_base - 1 + kx;
if x_sample < 0 || x_sample >= width as i32 {
continue;
}
let weight = wx[kx as usize] * wy[ky as usize];
if weight > 0.0 {
let pixel = src.get_pixel(x_sample as u32, y_sample as u32).to_rgba();
sum += weight * pixel[c] as f64;
weight_sum += weight;
}
}
}
if weight_sum > 0.0 {
result[c] = (sum / weight_sum).round();
result[c] = result[c].clamp(0.0, 255.0);
}
}
Rgba([
result[0] as u8,
result[1] as u8,
result[2] as u8,
result[3] as u8,
])
}
#[allow(dead_code)]
fn lanczos(x: f64, a: i32) -> f64 {
if x.abs() < f64::EPSILON {
return 1.0;
}
if x.abs() >= a as f64 {
return 0.0;
}
let a_f64 = a as f64;
let pi_x = std::f64::consts::PI * x;
(a_f64 * (pi_x / a_f64).sin() * (pi_x).sin()) / (pi_x * pi_x)
}
#[allow(dead_code)]
fn lanczos_interpolate(src: &DynamicImage, x: f64, y: f64, a: i32) -> Rgba<u8> {
let (width, height) = src.dimensions();
let x_base = x.floor() as i32;
let y_base = y.floor() as i32;
let dx = x - x_base as f64;
let dy = y - y_base as f64;
let kernel_width = 2 * a;
let mut weights_x = vec![0.0; kernel_width as usize];
let mut weights_y = vec![0.0; kernel_width as usize];
for k in 0..kernel_width {
let kx = k - a + 1;
weights_x[k as usize] = lanczos(dx - kx as f64, a);
}
for k in 0..kernel_width {
let ky = k - a + 1;
weights_y[k as usize] = lanczos(dy - ky as f64, a);
}
let sum_wx: f64 = weights_x.iter().sum();
let sum_wy: f64 = weights_y.iter().sum();
for k in 0..kernel_width as usize {
weights_x[k] /= sum_wx;
weights_y[k] /= sum_wy;
}
let mut result = [0.0; 4];
for c in 0..4 {
let mut sum = 0.0;
let mut weight_sum = 0.0;
for ky in 0..kernel_width {
let y_sample = y_base + ky - a + 1;
if y_sample < 0 || y_sample >= height as i32 {
continue;
}
for kx in 0..kernel_width {
let x_sample = x_base + kx - a + 1;
if x_sample < 0 || x_sample >= width as i32 {
continue;
}
let weight = weights_x[kx as usize] * weights_y[ky as usize];
let pixel = src.get_pixel(x_sample as u32, y_sample as u32).to_rgba();
sum += weight * pixel[c] as f64;
weight_sum += weight;
}
}
if weight_sum > 0.0 {
result[c] = (sum / weight_sum).round();
result[c] = result[c].clamp(0.0, 255.0);
}
}
Rgba([
result[0] as u8,
result[1] as u8,
result[2] as u8,
result[3] as u8,
])
}
#[allow(dead_code)]
fn create_kernel(_kernelfunc: fn(f64) -> f64, kernel_size: usize, scale: f64) -> Array1<f64> {
let mut kernel = Array1::zeros(kernel_size);
let radius = (kernel_size as f64 - 1.0) / 2.0;
for i in 0..kernel_size {
let x = (i as f64 - radius) / scale;
kernel[i] = _kernelfunc(x);
}
let sum = kernel.sum();
if sum > 0.0 {
kernel.mapv_inplace(|x| x / sum);
}
kernel
}
#[allow(dead_code)]
pub fn convolve_1d(
src: &Array2<f64>,
kernel: &Array1<f64>,
horizontal: bool,
) -> Result<Array2<f64>> {
let (height, width) = src.dim();
let mut dst = Array2::zeros((height, width));
let k_size = kernel.len();
let k_radius = (k_size / 2) as isize;
if horizontal {
for y in 0..height {
for x in 0..width {
let mut sum = 0.0;
let mut weight_sum = 0.0;
for k in 0..k_size {
let kx = x as isize + (k as isize - k_radius);
if kx >= 0 && kx < width as isize {
let w = kernel[k];
sum += w * src[[y, kx as usize]];
weight_sum += w;
}
}
if weight_sum > 0.0 {
dst[[y, x]] = sum / weight_sum;
}
}
}
} else {
for y in 0..height {
for x in 0..width {
let mut sum = 0.0;
let mut weight_sum = 0.0;
for k in 0..k_size {
let ky = y as isize + (k as isize - k_radius);
if ky >= 0 && ky < height as isize {
let w = kernel[k];
sum += w * src[[ky as usize, x]];
weight_sum += w;
}
}
if weight_sum > 0.0 {
dst[[y, x]] = sum / weight_sum;
}
}
}
}
Ok(dst)
}
#[allow(dead_code)]
pub fn resize_convolution(
src: &DynamicImage,
width: u32,
height: u32,
kernel_func: fn(f64) -> f64,
kernel_size: usize,
) -> Result<DynamicImage> {
let (src_width, src_height) = src.dimensions();
if src_width == width && src_height == height {
return Ok(src.clone());
}
let scale_x = width as f64 / src_width as f64;
let scale_y = height as f64 / src_height as f64;
let mut dst: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::new(width, height);
for c in 0..4 {
let mut channel = Array2::zeros((src_height as usize, src_width as usize));
for y in 0..src_height {
for x in 0..src_width {
let pixel = src.get_pixel(x, y).to_rgba();
channel[[y as usize, x as usize]] = pixel[c] as f64;
}
}
let scale_factor_x = if scale_x < 1.0 { scale_x } else { 1.0 };
let scale_factor_y = if scale_y < 1.0 { scale_y } else { 1.0 };
let kernel_x = create_kernel(kernel_func, kernel_size, scale_factor_x);
let kernel_y = create_kernel(kernel_func, kernel_size, scale_factor_y);
let temp = convolve_1d(&channel, &kernel_x, true)?;
let filtered = convolve_1d(&temp, &kernel_y, false)?;
for y in 0..height {
for x in 0..width {
let src_x = (x as f64 + 0.5) / scale_x - 0.5;
let src_y = (y as f64 + 0.5) / scale_y - 0.5;
let x0 = src_x.floor() as i32;
let y0 = src_y.floor() as i32;
let dx = src_x - x0 as f64;
let dy = src_y - y0 as f64;
let w00 = (1.0 - dx) * (1.0 - dy);
let w01 = (1.0 - dx) * dy;
let w10 = dx * (1.0 - dy);
let w11 = dx * dy;
let mut value = 0.0;
let mut weight_sum = 0.0;
for ky in 0..2 {
let y_index = y0 + ky;
if y_index >= 0 && y_index < src_height as i32 {
for kx in 0..2 {
let x_index = x0 + kx;
if x_index >= 0 && x_index < src_width as i32 {
let w = match (kx, ky) {
(0, 0) => w00,
(0, 1) => w01,
(1, 0) => w10,
(1, 1) => w11,
_ => 0.0,
};
if w > 0.0 {
value += w * filtered[[y_index as usize, x_index as usize]];
weight_sum += w;
}
}
}
}
}
if weight_sum > 0.0 {
value /= weight_sum;
}
let mut pixel = dst.get_pixel_mut(x, y).to_rgba();
pixel[c] = value.round().clamp(0.0, 255.0) as u8;
dst.put_pixel(x, y, pixel);
}
}
}
Ok(DynamicImage::ImageRgba8(dst))
}
#[allow(dead_code)]
fn lanczos_kernel(x: f64) -> f64 {
lanczos(x, 3)
}
#[allow(dead_code)]
fn bicubic_kernel(x: f64) -> f64 {
cubic_hermite(x.abs())
}
#[allow(dead_code)]
pub fn resize_lanczos(src: &DynamicImage, width: u32, height: u32) -> Result<DynamicImage> {
resize_convolution(src, width, height, lanczos_kernel, 7)
}
#[allow(dead_code)]
pub fn resize_bicubic(src: &DynamicImage, width: u32, height: u32) -> Result<DynamicImage> {
resize_convolution(src, width, height, bicubic_kernel, 5)
}
#[allow(dead_code)]
fn guided_filter(
guide: &Array2<f64>,
src: &Array2<f64>,
radius: usize,
epsilon: f64,
) -> Result<Array2<f64>> {
let (height, width) = guide.dim();
let mean_i;
let mean_p;
let mean_ii;
let mean_ip;
let box_kernel = Array1::from_elem(2 * radius + 1, 1.0 / ((2 * radius + 1) as f64));
let mut temp_i = guide.clone();
let mut temp_p = src.clone();
temp_i = convolve_1d(&temp_i, &box_kernel, true)?;
temp_p = convolve_1d(&temp_p, &box_kernel, true)?;
mean_i = convolve_1d(&temp_i, &box_kernel, false)?;
mean_p = convolve_1d(&temp_p, &box_kernel, false)?;
let mut ii = Array2::zeros((height, width));
let mut ip = Array2::zeros((height, width));
for y in 0..height {
for x in 0..width {
ii[[y, x]] = guide[[y, x]] * guide[[y, x]];
ip[[y, x]] = guide[[y, x]] * src[[y, x]];
}
}
let mut temp_ii = ii.clone();
let mut temp_ip = ip.clone();
temp_ii = convolve_1d(&temp_ii, &box_kernel, true)?;
temp_ip = convolve_1d(&temp_ip, &box_kernel, true)?;
mean_ii = convolve_1d(&temp_ii, &box_kernel, false)?;
mean_ip = convolve_1d(&temp_ip, &box_kernel, false)?;
let mut cov_ip = Array2::zeros((height, width));
for y in 0..height {
for x in 0..width {
cov_ip[[y, x]] = mean_ip[[y, x]] - mean_i[[y, x]] * mean_p[[y, x]];
}
}
let mut var_i = Array2::zeros((height, width));
for y in 0..height {
for x in 0..width {
var_i[[y, x]] = mean_ii[[y, x]] - mean_i[[y, x]] * mean_i[[y, x]];
}
}
let mut a = Array2::zeros((height, width));
let mut b = Array2::zeros((height, width));
for y in 0..height {
for x in 0..width {
a[[y, x]] = cov_ip[[y, x]] / (var_i[[y, x]] + epsilon);
b[[y, x]] = mean_p[[y, x]] - a[[y, x]] * mean_i[[y, x]];
}
}
let temp_a = convolve_1d(&a, &box_kernel, true)?;
let temp_b = convolve_1d(&b, &box_kernel, true)?;
let mean_a = convolve_1d(&temp_a, &box_kernel, false)?;
let mean_b = convolve_1d(&temp_b, &box_kernel, false)?;
let mut output = Array2::zeros((height, width));
for y in 0..height {
for x in 0..width {
output[[y, x]] = mean_a[[y, x]] * guide[[y, x]] + mean_b[[y, x]];
}
}
Ok(output)
}
#[allow(dead_code)]
pub fn resize_edge_preserving(src: &DynamicImage, width: u32, height: u32) -> Result<DynamicImage> {
let initial_resize = resize_lanczos(src, width, height)?;
let radius = 2;
let epsilon = 0.01;
let grayscale = initial_resize.to_luma8();
let mut dst: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::new(width, height);
for c in 0..3 {
let mut channel = Array2::zeros((height as usize, width as usize));
let mut guide = Array2::zeros((height as usize, width as usize));
for y in 0..height {
for x in 0..width {
let pixel = initial_resize.get_pixel(x, y).to_rgba();
channel[[y as usize, x as usize]] = pixel[c] as f64;
let guide_value = grayscale.get_pixel(x, y)[0];
guide[[y as usize, x as usize]] = guide_value as f64;
}
}
let filtered = guided_filter(&guide, &channel, radius, epsilon)?;
for y in 0..height {
for x in 0..width {
let mut pixel = dst.get_pixel_mut(x, y).to_rgba();
pixel[c] = filtered[[y as usize, x as usize]].round().clamp(0.0, 255.0) as u8;
dst.put_pixel(x, y, pixel);
}
}
}
for y in 0..height {
for x in 0..width {
let alpha = initial_resize.get_pixel(x, y).to_rgba()[3];
let mut pixel = dst.get_pixel_mut(x, y).to_rgba();
pixel[3] = alpha;
dst.put_pixel(x, y, pixel);
}
}
Ok(DynamicImage::ImageRgba8(dst))
}
#[cfg(test)]
mod tests {
use super::*;
use image::{DynamicImage, ImageBuffer, Rgba};
#[test]
fn test_lanczos_kernel() {
assert!((lanczos_kernel(0.0) - 1.0).abs() < 1e-10);
assert!(lanczos_kernel(3.0).abs() < 1e-10);
for x in [0.5, 1.0, 1.5, 2.0, 2.5] {
assert!((lanczos_kernel(x) - lanczos_kernel(-x)).abs() < 1e-10);
}
}
#[test]
fn test_bicubic_kernel() {
assert!(bicubic_kernel(0.0) - 1.0 < 1e-10);
assert!(bicubic_kernel(2.0).abs() < 1e-10);
assert!(bicubic_kernel(3.0).abs() < 1e-10);
for x in [0.5, 1.0, 1.5, 1.9] {
assert!((bicubic_kernel(x) - bicubic_kernel(-x)).abs() < 1e-10);
}
}
#[test]
fn test_guided_filter() {
let width = 10;
let height = 10;
let mut guide = Array2::zeros((height, width));
let mut src = Array2::zeros((height, width));
for y in 0..height {
for x in 0..width {
if x < width / 2 {
guide[[y, x]] = 50.0;
src[[y, x]] = 50.0;
} else {
guide[[y, x]] = 150.0;
src[[y, x]] = 150.0;
}
if x % 2 == 0 && y % 2 == 0 {
src[[y, x]] += 20.0;
}
}
}
let radius = 1;
let epsilon = 0.1;
let result = guided_filter(&guide, &src, radius, epsilon).expect("Operation failed");
let edge_preserved = result[[5, 4]] < 100.0 && result[[5, 5]] > 100.0;
assert!(edge_preserved);
let noise_reduced = (result[[2, 2]] - result[[2, 0]]).abs() < 10.0;
assert!(noise_reduced);
}
#[test]
fn test_resize_edge_preserving() {
let width = 20;
let height = 20;
let mut img = ImageBuffer::new(width, height);
for y in 0..height {
for x in 0..width {
let pixel_value = if x < width / 2 { 50 } else { 200 };
img.put_pixel(x, y, Rgba([pixel_value, pixel_value, pixel_value, 255]));
}
}
let src = DynamicImage::ImageRgba8(img);
let result = resize_edge_preserving(&src, 10, 10).expect("Operation failed");
let edge_before = result.get_pixel(4, 5)[0];
let edge_after = result.get_pixel(5, 5)[0];
assert!(edge_after - edge_before > 50);
let result_up = resize_edge_preserving(&src, 30, 30).expect("Operation failed");
assert_eq!(result_up.width(), 30);
assert_eq!(result_up.height(), 30);
let edge_before_up = result_up.get_pixel(14, 15)[0];
let edge_after_up = result_up.get_pixel(15, 15)[0];
assert!(edge_after_up - edge_before_up > 50);
}
}