use crate::error::{AlgorithmError, Result};
use crate::resampling::kernel::{lanczos, normalize_weights};
use oxigdal_core::buffer::RasterBuffer;
#[derive(Debug, Clone, Copy)]
pub struct LanczosResampler {
lobes: usize,
}
impl Default for LanczosResampler {
fn default() -> Self {
Self::new(3)
}
}
impl LanczosResampler {
#[must_use]
pub const fn new(lobes: usize) -> Self {
Self { lobes }
}
#[must_use]
pub const fn lobes(&self) -> usize {
self.lobes
}
#[must_use]
pub const fn radius(&self) -> usize {
self.lobes
}
pub fn resample(
&self,
src: &RasterBuffer,
dst_width: u64,
dst_height: u64,
) -> Result<RasterBuffer> {
if dst_width == 0 || dst_height == 0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "dimensions",
message: "Target dimensions must be non-zero".to_string(),
});
}
let src_width = src.width();
let src_height = src.height();
if src_width == 0 || src_height == 0 {
return Err(AlgorithmError::EmptyInput {
operation: "Lanczos resampling",
});
}
let mut dst = RasterBuffer::zeros(dst_width, dst_height, src.data_type());
let scale_x = src_width as f64 / dst_width as f64;
let scale_y = src_height as f64 / dst_height as f64;
for dst_y in 0..dst_height {
for dst_x in 0..dst_width {
let src_x = (dst_x as f64 + 0.5) * scale_x - 0.5;
let src_y = (dst_y as f64 + 0.5) * scale_y - 0.5;
let value = self.interpolate_at(src, src_x, src_y)?;
dst.set_pixel(dst_x, dst_y, value)
.map_err(AlgorithmError::Core)?;
}
}
Ok(dst)
}
fn interpolate_at(&self, src: &RasterBuffer, src_x: f64, src_y: f64) -> Result<f64> {
let src_width = src.width();
let src_height = src.height();
let src_x_clamped = src_x.max(0.0).min((src_width - 1) as f64);
let src_y_clamped = src_y.max(0.0).min((src_height - 1) as f64);
let x_center = src_x_clamped.floor() as i64;
let y_center = src_y_clamped.floor() as i64;
let radius = self.radius() as i64;
let x_start = x_center - radius + 1;
let x_end = x_center + radius + 1;
let y_start = y_center - radius + 1;
let y_end = y_center + radius + 1;
let kernel_width = (x_end - x_start) as usize;
let kernel_height = (y_end - y_start) as usize;
let total_samples = kernel_width * kernel_height;
let mut values = vec![0.0f64; total_samples];
let mut weights = vec![0.0f64; total_samples];
let mut idx = 0;
for sy in y_start..y_end {
for sx in x_start..x_end {
let sample_x = sx.max(0).min(src_width as i64 - 1) as u64;
let sample_y = sy.max(0).min(src_height as i64 - 1) as u64;
values[idx] = src
.get_pixel(sample_x, sample_y)
.map_err(AlgorithmError::Core)?;
let dx = sx as f64 - src_x_clamped;
let dy = sy as f64 - src_y_clamped;
let wx = lanczos(dx, self.lobes);
let wy = lanczos(dy, self.lobes);
weights[idx] = wx * wy;
idx += 1;
}
}
normalize_weights(&mut weights);
let mut result = 0.0;
for (value, weight) in values.iter().zip(weights.iter()) {
result += value * weight;
}
Ok(result)
}
pub fn resample_with_edge_mode(
&self,
src: &RasterBuffer,
dst_width: u64,
dst_height: u64,
edge_mode: EdgeMode,
) -> Result<RasterBuffer> {
match edge_mode {
EdgeMode::Clamp => self.resample(src, dst_width, dst_height),
EdgeMode::Wrap => Err(AlgorithmError::UnsupportedOperation {
operation: "Wrap edge mode not yet implemented".to_string(),
}),
EdgeMode::Mirror => Err(AlgorithmError::UnsupportedOperation {
operation: "Mirror edge mode not yet implemented".to_string(),
}),
}
}
pub fn resample_separable(
&self,
src: &RasterBuffer,
dst_width: u64,
dst_height: u64,
) -> Result<RasterBuffer> {
if dst_width == 0 || dst_height == 0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "dimensions",
message: "Target dimensions must be non-zero".to_string(),
});
}
let src_width = src.width();
let src_height = src.height();
let mut temp = RasterBuffer::zeros(dst_width, src_height, src.data_type());
let scale_x = src_width as f64 / dst_width as f64;
for src_y in 0..src_height {
for dst_x in 0..dst_width {
let src_x = (dst_x as f64 + 0.5) * scale_x - 0.5;
let value = self.interpolate_horizontal(src, src_x, src_y)?;
temp.set_pixel(dst_x, src_y, value)
.map_err(AlgorithmError::Core)?;
}
}
let mut dst = RasterBuffer::zeros(dst_width, dst_height, src.data_type());
let scale_y = src_height as f64 / dst_height as f64;
for dst_x in 0..dst_width {
for dst_y in 0..dst_height {
let src_y = (dst_y as f64 + 0.5) * scale_y - 0.5;
let value = self.interpolate_vertical(&temp, dst_x, src_y)?;
dst.set_pixel(dst_x, dst_y, value)
.map_err(AlgorithmError::Core)?;
}
}
Ok(dst)
}
fn interpolate_horizontal(&self, src: &RasterBuffer, src_x: f64, y: u64) -> Result<f64> {
let src_width = src.width();
let src_x_clamped = src_x.max(0.0).min((src_width - 1) as f64);
let x_center = src_x_clamped.floor() as i64;
let radius = self.radius() as i64;
let x_start = x_center - radius + 1;
let x_end = x_center + radius + 1;
let kernel_width = (x_end - x_start) as usize;
let mut values = vec![0.0f64; kernel_width];
let mut weights = vec![0.0f64; kernel_width];
for (idx, sx) in (x_start..x_end).enumerate() {
let sample_x = sx.max(0).min(src_width as i64 - 1) as u64;
values[idx] = src.get_pixel(sample_x, y).map_err(AlgorithmError::Core)?;
let dx = sx as f64 - src_x_clamped;
weights[idx] = lanczos(dx, self.lobes);
}
normalize_weights(&mut weights);
let result = values.iter().zip(weights.iter()).map(|(v, w)| v * w).sum();
Ok(result)
}
fn interpolate_vertical(&self, src: &RasterBuffer, x: u64, src_y: f64) -> Result<f64> {
let src_height = src.height();
let src_y_clamped = src_y.max(0.0).min((src_height - 1) as f64);
let y_center = src_y_clamped.floor() as i64;
let radius = self.radius() as i64;
let y_start = y_center - radius + 1;
let y_end = y_center + radius + 1;
let kernel_height = (y_end - y_start) as usize;
let mut values = vec![0.0f64; kernel_height];
let mut weights = vec![0.0f64; kernel_height];
for (idx, sy) in (y_start..y_end).enumerate() {
let sample_y = sy.max(0).min(src_height as i64 - 1) as u64;
values[idx] = src.get_pixel(x, sample_y).map_err(AlgorithmError::Core)?;
let dy = sy as f64 - src_y_clamped;
weights[idx] = lanczos(dy, self.lobes);
}
normalize_weights(&mut weights);
let result = values.iter().zip(weights.iter()).map(|(v, w)| v * w).sum();
Ok(result)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EdgeMode {
Clamp,
Wrap,
Mirror,
}
#[cfg(test)]
mod tests {
use super::*;
use oxigdal_core::types::RasterDataType;
#[test]
fn test_lanczos_creation() {
let l2 = LanczosResampler::new(2);
assert_eq!(l2.lobes(), 2);
assert_eq!(l2.radius(), 2);
let l3 = LanczosResampler::new(3);
assert_eq!(l3.lobes(), 3);
assert_eq!(l3.radius(), 3);
}
#[test]
fn test_lanczos_identity() {
let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
for y in 0..10 {
for x in 0..10 {
src.set_pixel(x, y, (y * 10 + x) as f64).ok();
}
}
let resampler = LanczosResampler::new(3);
let dst = resampler.resample(&src, 10, 10);
assert!(dst.is_ok());
}
#[test]
fn test_lanczos_quality() {
let mut src = RasterBuffer::zeros(5, 5, RasterDataType::Float32);
for y in 0..5 {
for x in 0..5 {
src.set_pixel(x, y, ((x + y) * (x + y)) as f64).ok();
}
}
let resampler = LanczosResampler::new(3);
let dst = resampler.resample(&src, 10, 10);
assert!(dst.is_ok());
if let Ok(dst) = dst {
let v1 = dst.get_pixel(4, 4).ok();
let v2 = dst.get_pixel(5, 5).ok();
assert!(v1.is_some());
assert!(v2.is_some());
}
}
#[test]
fn test_lanczos_separable() {
let mut src = RasterBuffer::zeros(8, 8, RasterDataType::Float32);
for y in 0..8 {
for x in 0..8 {
src.set_pixel(x, y, (x + y) as f64).ok();
}
}
let resampler = LanczosResampler::new(3);
let dst1 = resampler.resample(&src, 16, 16);
let dst2 = resampler.resample_separable(&src, 16, 16);
assert!(dst1.is_ok());
assert!(dst2.is_ok());
}
#[test]
fn test_lanczos_lobes() {
let src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
let l2 = LanczosResampler::new(2);
let l3 = LanczosResampler::new(3);
let dst2 = l2.resample(&src, 20, 20);
let dst3 = l3.resample(&src, 20, 20);
assert!(dst2.is_ok());
assert!(dst3.is_ok());
}
#[test]
fn test_edge_modes() {
let src = RasterBuffer::zeros(5, 5, RasterDataType::Float32);
let resampler = LanczosResampler::new(3);
let clamp = resampler.resample_with_edge_mode(&src, 10, 10, EdgeMode::Clamp);
assert!(clamp.is_ok());
let wrap = resampler.resample_with_edge_mode(&src, 10, 10, EdgeMode::Wrap);
assert!(wrap.is_err());
let mirror = resampler.resample_with_edge_mode(&src, 10, 10, EdgeMode::Mirror);
assert!(mirror.is_err());
}
#[test]
fn test_lanczos_zero_dimensions() {
let src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
let resampler = LanczosResampler::new(3);
assert!(resampler.resample(&src, 0, 10).is_err());
assert!(resampler.resample(&src, 10, 0).is_err());
}
}