oxigdal_algorithms/resampling/
nearest.rs1use crate::error::{AlgorithmError, Result};
21use oxigdal_core::buffer::RasterBuffer;
22
23#[derive(Debug, Clone, Copy, Default)]
25pub struct NearestResampler;
26
27impl NearestResampler {
28 #[must_use]
30 pub const fn new() -> Self {
31 Self
32 }
33
34 pub fn resample(
49 &self,
50 src: &RasterBuffer,
51 dst_width: u64,
52 dst_height: u64,
53 ) -> Result<RasterBuffer> {
54 if dst_width == 0 || dst_height == 0 {
55 return Err(AlgorithmError::InvalidParameter {
56 parameter: "dimensions",
57 message: "Target dimensions must be non-zero".to_string(),
58 });
59 }
60
61 let src_width = src.width();
62 let src_height = src.height();
63
64 if src_width == 0 || src_height == 0 {
65 return Err(AlgorithmError::EmptyInput {
66 operation: "nearest neighbor resampling",
67 });
68 }
69
70 let mut dst = RasterBuffer::zeros(dst_width, dst_height, src.data_type());
72
73 let scale_x = src_width as f64 / dst_width as f64;
75 let scale_y = src_height as f64 / dst_height as f64;
76
77 for dst_y in 0..dst_height {
79 for dst_x in 0..dst_width {
80 let src_x = self.map_coordinate(dst_x as f64, scale_x, src_width);
82 let src_y = self.map_coordinate(dst_y as f64, scale_y, src_height);
83
84 let value = src.get_pixel(src_x, src_y).map_err(AlgorithmError::Core)?;
86 dst.set_pixel(dst_x, dst_y, value)
87 .map_err(AlgorithmError::Core)?;
88 }
89 }
90
91 Ok(dst)
92 }
93
94 #[inline]
109 fn map_coordinate(&self, dst_coord: f64, scale: f64, src_size: u64) -> u64 {
110 let src_coord = (dst_coord + 0.5) * scale - 0.5;
112
113 let src_coord_rounded = src_coord.round();
115
116 let clamped = src_coord_rounded.max(0.0).min((src_size - 1) as f64);
118
119 clamped as u64
120 }
121
122 #[allow(clippy::too_many_arguments)]
140 pub fn resample_with_transform(
141 &self,
142 src: &RasterBuffer,
143 dst_width: u64,
144 dst_height: u64,
145 scale_x: f64,
146 scale_y: f64,
147 offset_x: f64,
148 offset_y: f64,
149 ) -> Result<RasterBuffer> {
150 if dst_width == 0 || dst_height == 0 {
151 return Err(AlgorithmError::InvalidParameter {
152 parameter: "dimensions",
153 message: "Target dimensions must be non-zero".to_string(),
154 });
155 }
156
157 if scale_x <= 0.0 || scale_y <= 0.0 {
158 return Err(AlgorithmError::InvalidParameter {
159 parameter: "scale",
160 message: "Scale factors must be positive".to_string(),
161 });
162 }
163
164 let src_width = src.width();
165 let src_height = src.height();
166
167 let mut dst = RasterBuffer::zeros(dst_width, dst_height, src.data_type());
168
169 for dst_y in 0..dst_height {
170 for dst_x in 0..dst_width {
171 let src_x_f64 = dst_x as f64 * scale_x + offset_x;
173 let src_y_f64 = dst_y as f64 * scale_y + offset_y;
174
175 let src_x = src_x_f64.round().max(0.0).min((src_width - 1) as f64) as u64;
177 let src_y = src_y_f64.round().max(0.0).min((src_height - 1) as f64) as u64;
178
179 let value = src.get_pixel(src_x, src_y).map_err(AlgorithmError::Core)?;
181 dst.set_pixel(dst_x, dst_y, value)
182 .map_err(AlgorithmError::Core)?;
183 }
184 }
185
186 Ok(dst)
187 }
188
189 pub fn resample_repeat(
194 &self,
195 src: &RasterBuffer,
196 dst_width: u64,
197 dst_height: u64,
198 ) -> Result<RasterBuffer> {
199 if dst_width == 0 || dst_height == 0 {
200 return Err(AlgorithmError::InvalidParameter {
201 parameter: "dimensions",
202 message: "Target dimensions must be non-zero".to_string(),
203 });
204 }
205
206 let src_width = src.width();
207 let src_height = src.height();
208
209 if src_width == 0 || src_height == 0 {
210 return Err(AlgorithmError::EmptyInput {
211 operation: "nearest neighbor resampling",
212 });
213 }
214
215 let mut dst = RasterBuffer::zeros(dst_width, dst_height, src.data_type());
216
217 let scale_x = src_width as f64 / dst_width as f64;
218 let scale_y = src_height as f64 / dst_height as f64;
219
220 for dst_y in 0..dst_height {
221 for dst_x in 0..dst_width {
222 let src_coord_x = (dst_x as f64 + 0.5) * scale_x - 0.5;
223 let src_coord_y = (dst_y as f64 + 0.5) * scale_y - 0.5;
224
225 let src_x = (src_coord_x.round() as i64).rem_euclid(src_width as i64) as u64;
227 let src_y = (src_coord_y.round() as i64).rem_euclid(src_height as i64) as u64;
228
229 let value = src.get_pixel(src_x, src_y).map_err(AlgorithmError::Core)?;
230 dst.set_pixel(dst_x, dst_y, value)
231 .map_err(AlgorithmError::Core)?;
232 }
233 }
234
235 Ok(dst)
236 }
237}
238
239#[cfg(feature = "simd")]
240mod simd_impl {
241 use super::*;
247
248 impl NearestResampler {
249 #[cfg(target_arch = "x86_64")]
254 pub fn resample_simd(
255 &self,
256 src: &RasterBuffer,
257 dst_width: u64,
258 dst_height: u64,
259 ) -> Result<RasterBuffer> {
260 self.resample(src, dst_width, dst_height)
271 }
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278 use approx::assert_abs_diff_eq;
279 use oxigdal_core::types::RasterDataType;
280
281 #[test]
282 fn test_nearest_identity() {
283 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
285
286 for y in 0..10 {
288 for x in 0..10 {
289 src.set_pixel(x, y, (y * 10 + x) as f64).ok();
290 }
291 }
292
293 let resampler = NearestResampler::new();
294 let result = resampler.resample(&src, 10, 10);
295 assert!(result.is_ok());
296
297 if let Ok(dst) = result {
298 for y in 0..10 {
299 for x in 0..10 {
300 if let (Ok(sv), Ok(dv)) = (src.get_pixel(x, y), dst.get_pixel(x, y)) {
301 assert_abs_diff_eq!(sv, dv, epsilon = 1e-10);
302 }
303 }
304 }
305 }
306 }
307
308 #[test]
309 fn test_nearest_downsample() {
310 let mut src = RasterBuffer::zeros(4, 4, RasterDataType::Float32);
312 for y in 0..4 {
313 for x in 0..4 {
314 let value = if (x + y) % 2 == 0 { 1.0 } else { 0.0 };
315 src.set_pixel(x, y, value).ok();
316 }
317 }
318
319 let resampler = NearestResampler::new();
320 let dst = resampler.resample(&src, 2, 2);
321 assert!(dst.is_ok());
322 }
323
324 #[test]
325 fn test_nearest_upsample() {
326 let mut src = RasterBuffer::zeros(2, 2, RasterDataType::Float32);
328 src.set_pixel(0, 0, 1.0).ok();
329 src.set_pixel(1, 0, 2.0).ok();
330 src.set_pixel(0, 1, 3.0).ok();
331 src.set_pixel(1, 1, 4.0).ok();
332
333 let resampler = NearestResampler::new();
334 let dst = resampler.resample(&src, 4, 4);
335 assert!(dst.is_ok());
336
337 if let Ok(dst) = dst {
339 let val = dst.get_pixel(0, 0).ok();
341 assert!(val.is_some());
342 }
343 }
344
345 #[test]
346 fn test_nearest_zero_dimensions() {
347 let src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
348 let resampler = NearestResampler::new();
349
350 assert!(resampler.resample(&src, 0, 10).is_err());
351 assert!(resampler.resample(&src, 10, 0).is_err());
352 }
353
354 #[test]
355 fn test_map_coordinate() {
356 let resampler = NearestResampler::new();
357
358 assert_eq!(resampler.map_coordinate(0.0, 2.0, 10), 1);
363
364 assert_eq!(resampler.map_coordinate(1.0, 2.0, 10), 3);
366
367 assert_eq!(resampler.map_coordinate(2.0, 2.0, 10), 5);
369
370 assert_eq!(resampler.map_coordinate(3.0, 2.0, 10), 7);
372
373 assert_eq!(resampler.map_coordinate(0.0, 0.5, 10), 0);
376
377 assert_eq!(resampler.map_coordinate(1.0, 0.5, 10), 0);
379
380 assert_eq!(resampler.map_coordinate(2.0, 0.5, 10), 1);
382
383 assert_eq!(resampler.map_coordinate(20.0, 2.0, 10), 9);
386 }
387
388 #[test]
389 fn test_nearest_with_transform() {
390 let src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
391 let resampler = NearestResampler::new();
392
393 let result = resampler.resample_with_transform(&src, 10, 10, 1.0, 1.0, 0.0, 0.0);
395 assert!(result.is_ok());
396
397 let result = resampler.resample_with_transform(&src, 10, 10, 0.0, 1.0, 0.0, 0.0);
399 assert!(result.is_err());
400
401 let result = resampler.resample_with_transform(&src, 10, 10, 1.0, -1.0, 0.0, 0.0);
402 assert!(result.is_err());
403 }
404
405 #[test]
406 fn test_nearest_repeat() {
407 let mut src = RasterBuffer::zeros(3, 3, RasterDataType::Float32);
408 for y in 0..3 {
409 for x in 0..3 {
410 src.set_pixel(x, y, (y * 3 + x) as f64).ok();
411 }
412 }
413
414 let resampler = NearestResampler::new();
415 let result = resampler.resample_repeat(&src, 6, 6);
416 assert!(result.is_ok());
417 }
418}