1use kornia_image::{allocator::ImageAllocator, Image, ImageError, ImageSize};
2use rayon::prelude::*;
3
4#[derive(Debug, Clone, Copy)]
6pub enum PaddingMode {
7 Constant,
11
12 Replicate,
16
17 Reflect101,
21
22 Reflect,
26
27 Wrap,
31}
32impl PaddingMode {
33 #[inline]
34 fn reflect(i: isize, len: usize) -> usize {
35 if len == 1 {
36 return 0;
37 }
38 let len = len as isize;
39 let mut i = i;
40 while i < 0 || i >= len {
41 if i < 0 {
42 i = -i - 1;
43 } else if i >= len {
44 i = 2 * len - i - 1;
45 }
46 }
47 i as usize
48 }
49
50 #[inline]
51 fn reflect101(i: isize, len: usize) -> usize {
52 if len == 1 {
53 return 0;
54 }
55 let len = len as isize;
56 let mut i = i;
57 while i < 0 || i >= len {
58 if i < 0 {
59 i = -i;
60 } else if i >= len {
61 i = 2 * len - i - 2;
62 }
63 }
64 i as usize
65 }
66
67 #[inline]
68 fn wrap(i: isize, len: usize) -> usize {
69 ((i % len as isize + len as isize) % len as isize) as usize
70 }
71
72 #[inline]
87 pub fn map_index(&self, i: isize, len: usize) -> usize {
88 match self {
89 PaddingMode::Replicate => i.clamp(0, len as isize - 1) as usize,
90 PaddingMode::Reflect => Self::reflect(i, len),
91 PaddingMode::Reflect101 => Self::reflect101(i, len),
92 PaddingMode::Wrap => Self::wrap(i, len),
93 PaddingMode::Constant => 0,
94 }
95 }
96
97 pub fn apply_padding<T: Copy + Send + Sync, const C: usize>(
109 &self,
110 new_data: &mut [T],
111 old_width: usize,
112 old_height: usize,
113 new_width: usize,
114 new_height: usize,
115 padding: &Padding2D,
116 ) {
117 if let PaddingMode::Constant = self {
118 return; }
120
121 let top = padding.top;
122 let bottom = padding.bottom;
123 let left = padding.left;
124 let right = padding.right;
125 let row_stride = new_width * C;
126
127 const ROWS_PER_TASK: usize = 16;
128 let chunk_elems = ROWS_PER_TASK * row_stride;
129
130 {
132 let (top_section, rest) = new_data.split_at_mut(top * row_stride);
133
134 top_section
135 .par_chunks_mut(chunk_elems)
136 .enumerate()
137 .for_each(|(chunk_idx, dst_chunk)| {
138 let row_base = chunk_idx * ROWS_PER_TASK;
139 dst_chunk
140 .chunks_exact_mut(row_stride)
141 .enumerate()
142 .for_each(|(dr, dst_row)| {
143 let y = row_base + dr;
144 let src_y = self.map_index(y as isize - top as isize, old_height);
145 let src_row = &rest[src_y * row_stride..(src_y + 1) * row_stride];
146 dst_row.copy_from_slice(src_row);
147 });
148 });
149 }
150
151 {
153 let split_point = (new_height - bottom) * row_stride;
154 let (rest, bottom_section) = new_data.split_at_mut(split_point);
155
156 bottom_section
157 .par_chunks_mut(chunk_elems)
158 .enumerate()
159 .for_each(|(chunk_idx, dst_chunk)| {
160 let row_base = chunk_idx * ROWS_PER_TASK;
161 dst_chunk
162 .chunks_exact_mut(row_stride)
163 .enumerate()
164 .for_each(|(dr, dst_row)| {
165 let idx = row_base + dr;
166 let y = new_height - bottom + idx;
167 let src_y = self.map_index(y as isize - top as isize, old_height);
168 let src_start = (src_y + top) * row_stride;
169 let src_row = &rest[src_start..src_start + row_stride];
170 dst_row.copy_from_slice(src_row);
171 });
172 });
173 }
174
175 new_data.par_chunks_mut(chunk_elems).for_each(|dst_chunk| {
176 dst_chunk.chunks_exact_mut(row_stride).for_each(|row| {
177 for x in 0..left {
179 let src_x = self.map_index(x as isize - left as isize, old_width);
180 let src_idx = (left + src_x) * C;
181 let dst_idx = x * C;
182 row.copy_within(src_idx..src_idx + C, dst_idx);
183 }
184
185 for x in (new_width - right)..new_width {
187 let src_x = self.map_index(x as isize - left as isize, old_width);
188 let src_idx = (left + src_x) * C;
189 let dst_idx = x * C;
190 row.copy_within(src_idx..src_idx + C, dst_idx);
191 }
192 });
193 });
194 }
195}
196
197pub struct Padding2D {
199 pub top: usize,
201 pub bottom: usize,
203 pub left: usize,
205 pub right: usize,
207}
208impl Padding2D {
209 pub fn validate_size(&self, old_size: ImageSize, new_size: ImageSize) -> bool {
232 new_size.width == old_size.width + self.left + self.right
233 && new_size.height == old_size.height + self.top + self.bottom
234 }
235}
236
237pub fn spatial_padding<T, const C: usize, A1: ImageAllocator, A2: ImageAllocator>(
287 src: &Image<T, C, A1>,
288 dst: &mut Image<T, C, A2>,
289 padding: Padding2D,
290 padding_mode: PaddingMode,
291 constant_value: [T; C],
292) -> Result<(), ImageError>
293where
294 T: Copy + Default + Send + Sync,
295{
296 if !padding.validate_size(src.size(), dst.size()) {
297 return Err(ImageError::InvalidImageSize(
298 dst.width(),
299 dst.height(),
300 src.width() + padding.left + padding.right,
301 src.height() + padding.top + padding.bottom,
302 ));
303 }
304
305 let old_width = src.width();
306 let old_height = src.height();
307 let new_width = dst.width();
308 let new_height = dst.height();
309
310 let old_data = src.as_slice();
311 let new_data = dst.as_slice_mut();
312
313 match padding_mode {
314 PaddingMode::Constant => {
316 new_data
317 .chunks_exact_mut(C)
318 .for_each(|chunk| chunk.copy_from_slice(&constant_value));
319 }
320 _ => {
321 new_data.fill(T::default());
322 }
323 }
324
325 let new_stride = new_width * C;
327 let old_stride = old_width * C;
328
329 let row_offset = padding.top * new_stride + padding.left * C;
330
331 for (src_row, dst_row) in old_data
332 .chunks_exact(old_stride)
333 .zip(new_data[row_offset..].chunks_exact_mut(new_stride))
334 {
335 dst_row[..old_stride].copy_from_slice(src_row);
336 }
337
338 padding_mode.apply_padding::<T, C>(
339 new_data, old_width, old_height, new_width, new_height, &padding,
340 );
341
342 Ok(())
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348 use kornia_image::{allocator::CpuAllocator, Image, ImageError, ImageSize};
349
350 fn make_src_2x2_rgb() -> Result<Image<u8, 3, CpuAllocator>, ImageError> {
352 Image::new(
353 ImageSize {
354 width: 2,
355 height: 2,
356 },
357 vec![1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4],
358 CpuAllocator,
359 )
360 }
361
362 fn make_dst_4x4_rgb() -> Result<Image<u8, 3, CpuAllocator>, ImageError> {
363 Image::new(
364 ImageSize {
365 width: 4,
366 height: 4,
367 },
368 vec![0u8; 48],
369 CpuAllocator,
370 )
371 }
372
373 const PAD_1: Padding2D = Padding2D {
374 top: 1,
375 bottom: 1,
376 left: 1,
377 right: 1,
378 };
379
380 #[test]
381 fn test_spatial_padding_constant() -> Result<(), ImageError> {
382 let src = make_src_2x2_rgb()?;
383 let mut dst = make_dst_4x4_rgb()?;
384
385 spatial_padding(&src, &mut dst, PAD_1, PaddingMode::Constant, [9, 9, 9])?;
386
387 let d = dst.as_slice();
388
389 assert_eq!(&d[0..3], &[9, 9, 9]);
391 assert_eq!(&d[45..48], &[9, 9, 9]);
392
393 assert_eq!(&d[3..6], &[9, 9, 9]);
395
396 assert_eq!(&d[15..18], &[1, 1, 1]);
398 assert_eq!(&d[30..33], &[4, 4, 4]);
399
400 Ok(())
401 }
402
403 #[test]
404 fn test_spatial_padding_replicate() -> Result<(), ImageError> {
405 let src = make_src_2x2_rgb()?;
406 let mut dst = make_dst_4x4_rgb()?;
407
408 spatial_padding(&src, &mut dst, PAD_1, PaddingMode::Replicate, [0, 0, 0])?;
409
410 let d = dst.as_slice();
411
412 assert_eq!(&d[0..3], &[1, 1, 1]);
414 assert_eq!(&d[45..48], &[4, 4, 4]);
415
416 assert_eq!(&d[3..6], &[1, 1, 1]);
418 assert_eq!(&d[21..24], &[2, 2, 2]);
419
420 Ok(())
421 }
422
423 #[test]
424 fn test_spatial_padding_reflect101() -> Result<(), ImageError> {
425 let src = make_src_2x2_rgb()?;
426 let mut dst = make_dst_4x4_rgb()?;
427
428 spatial_padding(&src, &mut dst, PAD_1, PaddingMode::Reflect101, [0, 0, 0])?;
429
430 let d = dst.as_slice();
431
432 assert_eq!(&d[0..3], &[4, 4, 4]);
434 assert_eq!(&d[9..12], &[3, 3, 3]);
435
436 assert_eq!(&d[3..6], &[3, 3, 3]);
438
439 assert_eq!(&d[15..18], &[1, 1, 1]);
441
442 Ok(())
443 }
444
445 #[test]
446 fn test_spatial_padding_reflect() -> Result<(), ImageError> {
447 let src = make_src_2x2_rgb()?;
448 let mut dst = make_dst_4x4_rgb()?;
449
450 spatial_padding(&src, &mut dst, PAD_1, PaddingMode::Reflect, [0, 0, 0])?;
451
452 let d = dst.as_slice();
453
454 assert_eq!(&d[0..3], &[1, 1, 1]);
456 assert_eq!(&d[9..12], &[2, 2, 2]);
457
458 assert_eq!(&d[6..9], &[2, 2, 2]);
460 assert_eq!(&d[39..42], &[3, 3, 3]);
461
462 Ok(())
463 }
464
465 #[test]
466 fn test_spatial_padding_wrap() -> Result<(), ImageError> {
467 let src = make_src_2x2_rgb()?;
468 let mut dst = make_dst_4x4_rgb()?;
469
470 spatial_padding(&src, &mut dst, PAD_1, PaddingMode::Wrap, [0, 0, 0])?;
471
472 let d = dst.as_slice();
473
474 assert_eq!(&d[0..3], &[4, 4, 4]);
476 assert_eq!(&d[9..12], &[3, 3, 3]);
477 assert_eq!(&d[36..39], &[2, 2, 2]);
478 assert_eq!(&d[45..48], &[1, 1, 1]);
479
480 assert_eq!(&d[12..15], &[2, 2, 2]);
482
483 Ok(())
484 }
485
486 #[test]
487 fn test_spatial_padding_dst_size_mismatch() -> Result<(), ImageError> {
488 let src = make_src_2x2_rgb()?;
489 let mut dst = Image::<u8, 3, _>::new(
490 ImageSize {
491 width: 3,
492 height: 4,
493 },
494 vec![0u8; 36],
495 CpuAllocator,
496 )?;
497
498 let res = spatial_padding(&src, &mut dst, PAD_1, PaddingMode::Replicate, [0, 0, 0]);
499 assert!(res.is_err());
500
501 Ok(())
502 }
503
504 #[test]
505 fn test_spatial_padding_larger_than_image_replicate() -> Result<(), ImageError> {
506 let src = Image::<u8, 3, _>::new(
507 ImageSize {
508 width: 1,
509 height: 1,
510 },
511 vec![7, 7, 7],
512 CpuAllocator,
513 )?;
514
515 let padding = Padding2D {
516 top: 3,
517 bottom: 3,
518 left: 4,
519 right: 4,
520 };
521
522 let mut dst = Image::<u8, 3, _>::new(
523 ImageSize {
524 width: 9,
525 height: 7,
526 },
527 vec![0u8; 189],
528 CpuAllocator,
529 )?;
530
531 spatial_padding(&src, &mut dst, padding, PaddingMode::Replicate, [0, 0, 0])?;
532
533 for px in dst.as_slice().chunks_exact(3) {
534 assert_eq!(px, &[7, 7, 7]);
535 }
536
537 Ok(())
538 }
539
540 #[test]
541 fn test_spatial_padding_larger_than_image_wrap() -> Result<(), ImageError> {
542 let src = Image::<u8, 3, _>::new(
543 ImageSize {
544 width: 1,
545 height: 1,
546 },
547 vec![5, 5, 5],
548 CpuAllocator,
549 )?;
550
551 let padding = Padding2D {
552 top: 2,
553 bottom: 2,
554 left: 2,
555 right: 2,
556 };
557
558 let mut dst = Image::<u8, 3, _>::new(
559 ImageSize {
560 width: 5,
561 height: 5,
562 },
563 vec![0u8; 75],
564 CpuAllocator,
565 )?;
566
567 spatial_padding(&src, &mut dst, padding, PaddingMode::Wrap, [0, 0, 0])?;
568
569 for px in dst.as_slice().chunks_exact(3) {
570 assert_eq!(px, &[5, 5, 5]);
571 }
572
573 Ok(())
574 }
575}