spatialrust_vision/
border.rs1#[cfg(any(
4 feature = "warp",
5 feature = "imgproc-filter",
6 feature = "imgproc-morphology",
7 feature = "imgproc-analysis"
8))]
9use spatialrust_image::ImageView;
10
11#[cfg(any(
12 feature = "warp",
13 feature = "imgproc-filter",
14 feature = "imgproc-morphology",
15 feature = "imgproc-analysis"
16))]
17use crate::PixelComponent;
18
19#[derive(Clone, Copy, Debug, PartialEq)]
21pub enum BorderMode<T, const CHANNELS: usize> {
22 Constant([T; CHANNELS]),
24 Replicate,
26 Reflect,
28 Reflect101,
30 Wrap,
32}
33
34#[cfg(any(
35 feature = "warp",
36 feature = "imgproc-filter",
37 feature = "imgproc-morphology",
38 feature = "imgproc-analysis"
39))]
40pub(crate) fn constant_pixel<T: PixelComponent, const CHANNELS: usize>(
41 border: BorderMode<T, CHANNELS>,
42) -> [T; CHANNELS] {
43 match border {
44 BorderMode::Constant(pixel) => pixel,
45 BorderMode::Replicate | BorderMode::Reflect | BorderMode::Reflect101 | BorderMode::Wrap => {
46 std::array::from_fn(|_| T::from_f64(0.0))
47 }
48 }
49}
50
51#[cfg(any(
52 feature = "warp",
53 feature = "imgproc-filter",
54 feature = "imgproc-morphology",
55 feature = "imgproc-analysis"
56))]
57pub(crate) fn fetch<T: PixelComponent, const CHANNELS: usize>(
58 input: ImageView<'_, T, CHANNELS>,
59 x: isize,
60 y: isize,
61 border: BorderMode<T, CHANNELS>,
62) -> [T; CHANNELS] {
63 if let (Some(ix), Some(iy)) =
64 (map_index(x, input.width(), border), map_index(y, input.height(), border))
65 {
66 return *input.get(ix, iy).expect("mapped coordinate is in bounds");
67 }
68 constant_pixel(border)
69}
70
71#[cfg(any(
72 feature = "warp",
73 feature = "imgproc-filter",
74 feature = "imgproc-morphology",
75 feature = "imgproc-analysis"
76))]
77pub(crate) fn map_index<T, const CHANNELS: usize>(
78 index: isize,
79 length: usize,
80 border: BorderMode<T, CHANNELS>,
81) -> Option<usize> {
82 if length == 0 {
83 return None;
84 }
85 if index >= 0 && index < length as isize {
86 return Some(index as usize);
87 }
88 match border {
89 BorderMode::Constant(_) => None,
90 BorderMode::Replicate => Some(index.clamp(0, length as isize - 1) as usize),
91 BorderMode::Reflect => Some(reflect_index(index, length, false)),
92 BorderMode::Reflect101 => Some(reflect_index(index, length, true)),
93 BorderMode::Wrap => Some(index.rem_euclid(length as isize) as usize),
94 }
95}
96
97#[cfg(any(
98 feature = "warp",
99 feature = "imgproc-filter",
100 feature = "imgproc-morphology",
101 feature = "imgproc-analysis"
102))]
103fn reflect_index(mut index: isize, length: usize, reflect101: bool) -> usize {
104 if length <= 1 {
105 return 0;
106 }
107 let length = length as isize;
108 while index < 0 || index >= length {
109 index = if index < 0 {
110 if reflect101 {
111 -index
112 } else {
113 -index - 1
114 }
115 } else if reflect101 {
116 2 * length - index - 2
117 } else {
118 2 * length - index - 1
119 };
120 }
121 index as usize
122}
123
124#[cfg(all(
125 test,
126 any(
127 feature = "warp",
128 feature = "imgproc-filter",
129 feature = "imgproc-morphology",
130 feature = "imgproc-analysis"
131 )
132))]
133mod tests {
134 use super::{map_index, BorderMode};
135
136 #[test]
137 fn maps_single_pixel_without_looping() {
138 for border in [
139 BorderMode::<u8, 1>::Replicate,
140 BorderMode::Reflect,
141 BorderMode::Reflect101,
142 BorderMode::Wrap,
143 ] {
144 assert_eq!(map_index(-100, 1, border), Some(0));
145 assert_eq!(map_index(100, 1, border), Some(0));
146 }
147 }
148
149 #[test]
150 fn empty_images_always_map_to_constant_space() {
151 assert_eq!(map_index(0, 0, BorderMode::<u8, 1>::Wrap), None);
152 }
153}