use crate::error::{AlgorithmError, Result};
use oxigdal_core::buffer::RasterBuffer;
#[derive(Debug, Clone, Copy, Default)]
pub struct NearestResampler;
impl NearestResampler {
#[must_use]
pub const fn new() -> Self {
Self
}
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: "nearest neighbor 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 = self.map_coordinate(dst_x as f64, scale_x, src_width);
let src_y = self.map_coordinate(dst_y as f64, scale_y, src_height);
let value = src.get_pixel(src_x, src_y).map_err(AlgorithmError::Core)?;
dst.set_pixel(dst_x, dst_y, value)
.map_err(AlgorithmError::Core)?;
}
}
Ok(dst)
}
#[inline]
fn map_coordinate(&self, dst_coord: f64, scale: f64, src_size: u64) -> u64 {
let src_coord = (dst_coord + 0.5) * scale - 0.5;
let src_coord_rounded = src_coord.round();
let clamped = src_coord_rounded.max(0.0).min((src_size - 1) as f64);
clamped as u64
}
#[allow(clippy::too_many_arguments)]
pub fn resample_with_transform(
&self,
src: &RasterBuffer,
dst_width: u64,
dst_height: u64,
scale_x: f64,
scale_y: f64,
offset_x: f64,
offset_y: f64,
) -> Result<RasterBuffer> {
if dst_width == 0 || dst_height == 0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "dimensions",
message: "Target dimensions must be non-zero".to_string(),
});
}
if scale_x <= 0.0 || scale_y <= 0.0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "scale",
message: "Scale factors must be positive".to_string(),
});
}
let src_width = src.width();
let src_height = src.height();
let mut dst = RasterBuffer::zeros(dst_width, dst_height, src.data_type());
for dst_y in 0..dst_height {
for dst_x in 0..dst_width {
let src_x_f64 = dst_x as f64 * scale_x + offset_x;
let src_y_f64 = dst_y as f64 * scale_y + offset_y;
let src_x = src_x_f64.round().max(0.0).min((src_width - 1) as f64) as u64;
let src_y = src_y_f64.round().max(0.0).min((src_height - 1) as f64) as u64;
let value = src.get_pixel(src_x, src_y).map_err(AlgorithmError::Core)?;
dst.set_pixel(dst_x, dst_y, value)
.map_err(AlgorithmError::Core)?;
}
}
Ok(dst)
}
pub fn resample_repeat(
&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: "nearest neighbor 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_coord_x = (dst_x as f64 + 0.5) * scale_x - 0.5;
let src_coord_y = (dst_y as f64 + 0.5) * scale_y - 0.5;
let src_x = (src_coord_x.round() as i64).rem_euclid(src_width as i64) as u64;
let src_y = (src_coord_y.round() as i64).rem_euclid(src_height as i64) as u64;
let value = src.get_pixel(src_x, src_y).map_err(AlgorithmError::Core)?;
dst.set_pixel(dst_x, dst_y, value)
.map_err(AlgorithmError::Core)?;
}
}
Ok(dst)
}
}
#[cfg(feature = "simd")]
mod simd_impl {
use super::*;
impl NearestResampler {
#[cfg(target_arch = "x86_64")]
pub fn resample_simd(
&self,
src: &RasterBuffer,
dst_width: u64,
dst_height: u64,
) -> Result<RasterBuffer> {
self.resample(src, dst_width, dst_height)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use oxigdal_core::types::RasterDataType;
#[test]
fn test_nearest_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 = NearestResampler::new();
let result = resampler.resample(&src, 10, 10);
assert!(result.is_ok());
if let Ok(dst) = result {
for y in 0..10 {
for x in 0..10 {
if let (Ok(sv), Ok(dv)) = (src.get_pixel(x, y), dst.get_pixel(x, y)) {
assert_abs_diff_eq!(sv, dv, epsilon = 1e-10);
}
}
}
}
}
#[test]
fn test_nearest_downsample() {
let mut src = RasterBuffer::zeros(4, 4, RasterDataType::Float32);
for y in 0..4 {
for x in 0..4 {
let value = if (x + y) % 2 == 0 { 1.0 } else { 0.0 };
src.set_pixel(x, y, value).ok();
}
}
let resampler = NearestResampler::new();
let dst = resampler.resample(&src, 2, 2);
assert!(dst.is_ok());
}
#[test]
fn test_nearest_upsample() {
let mut src = RasterBuffer::zeros(2, 2, RasterDataType::Float32);
src.set_pixel(0, 0, 1.0).ok();
src.set_pixel(1, 0, 2.0).ok();
src.set_pixel(0, 1, 3.0).ok();
src.set_pixel(1, 1, 4.0).ok();
let resampler = NearestResampler::new();
let dst = resampler.resample(&src, 4, 4);
assert!(dst.is_ok());
if let Ok(dst) = dst {
let val = dst.get_pixel(0, 0).ok();
assert!(val.is_some());
}
}
#[test]
fn test_nearest_zero_dimensions() {
let src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
let resampler = NearestResampler::new();
assert!(resampler.resample(&src, 0, 10).is_err());
assert!(resampler.resample(&src, 10, 0).is_err());
}
#[test]
fn test_map_coordinate() {
let resampler = NearestResampler::new();
assert_eq!(resampler.map_coordinate(0.0, 2.0, 10), 1);
assert_eq!(resampler.map_coordinate(1.0, 2.0, 10), 3);
assert_eq!(resampler.map_coordinate(2.0, 2.0, 10), 5);
assert_eq!(resampler.map_coordinate(3.0, 2.0, 10), 7);
assert_eq!(resampler.map_coordinate(0.0, 0.5, 10), 0);
assert_eq!(resampler.map_coordinate(1.0, 0.5, 10), 0);
assert_eq!(resampler.map_coordinate(2.0, 0.5, 10), 1);
assert_eq!(resampler.map_coordinate(20.0, 2.0, 10), 9);
}
#[test]
fn test_nearest_with_transform() {
let src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
let resampler = NearestResampler::new();
let result = resampler.resample_with_transform(&src, 10, 10, 1.0, 1.0, 0.0, 0.0);
assert!(result.is_ok());
let result = resampler.resample_with_transform(&src, 10, 10, 0.0, 1.0, 0.0, 0.0);
assert!(result.is_err());
let result = resampler.resample_with_transform(&src, 10, 10, 1.0, -1.0, 0.0, 0.0);
assert!(result.is_err());
}
#[test]
fn test_nearest_repeat() {
let mut src = RasterBuffer::zeros(3, 3, RasterDataType::Float32);
for y in 0..3 {
for x in 0..3 {
src.set_pixel(x, y, (y * 3 + x) as f64).ok();
}
}
let resampler = NearestResampler::new();
let result = resampler.resample_repeat(&src, 6, 6);
assert!(result.is_ok());
}
}