1use crate::core::{ColourModel, Image, ImageBase};
2use ndarray::{prelude::*, s, Data, OwnedRepr};
3use num_traits::identities::Zero;
4use std::marker::PhantomData;
5
6pub trait PaddingStrategy<T>
9where
10 T: Copy,
11{
12 fn pad(
15 &self,
16 image: ArrayView<T, Ix3>,
17 padding: (usize, usize),
18 ) -> ArrayBase<OwnedRepr<T>, Ix3>;
19
20 fn get_pixel(&self, image: ArrayView<T, Ix3>, index: (isize, isize)) -> Option<Array1<T>>;
24
25 fn get_value(&self, image: ArrayView<T, Ix3>, index: (isize, isize, usize)) -> Option<T>;
28
29 fn will_pad(&self, _coord: Option<(isize, isize)>) -> bool {
32 true
33 }
34}
35
36#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
39pub struct NoPadding;
40
41#[derive(Clone, Eq, PartialEq, Hash, Debug)]
43pub struct ConstantPadding<T>(T)
44where
45 T: Copy;
46
47#[derive(Clone, Eq, PartialEq, Hash, Debug)]
49pub struct ZeroPadding;
50
51#[inline]
52fn is_out_of_bounds(dim: (usize, usize, usize), index: (isize, isize, usize)) -> bool {
53 index.0 < 0
54 || index.1 < 0
55 || index.0 >= dim.0 as isize
56 || index.1 >= dim.1 as isize
57 || index.2 >= dim.2
58}
59
60impl<T> PaddingStrategy<T> for NoPadding
61where
62 T: Copy,
63{
64 fn pad(
65 &self,
66 image: ArrayView<T, Ix3>,
67 _padding: (usize, usize),
68 ) -> ArrayBase<OwnedRepr<T>, Ix3> {
69 image.to_owned()
70 }
71
72 fn get_pixel(&self, image: ArrayView<T, Ix3>, index: (isize, isize)) -> Option<Array1<T>> {
73 let index = (index.0, index.1, 0);
74 if is_out_of_bounds(image.dim(), index) {
75 None
76 } else {
77 Some(image.slice(s![index.0, index.1, ..]).to_owned())
78 }
79 }
80
81 fn get_value(&self, image: ArrayView<T, Ix3>, index: (isize, isize, usize)) -> Option<T> {
82 if is_out_of_bounds(image.dim(), index) {
83 None
84 } else {
85 image
86 .get((index.0 as usize, index.1 as usize, index.2))
87 .copied()
88 }
89 }
90
91 fn will_pad(&self, _coord: Option<(isize, isize)>) -> bool {
92 false
93 }
94}
95
96impl<T> PaddingStrategy<T> for ConstantPadding<T>
97where
98 T: Copy,
99{
100 fn pad(
101 &self,
102 image: ArrayView<T, Ix3>,
103 padding: (usize, usize),
104 ) -> ArrayBase<OwnedRepr<T>, Ix3> {
105 let shape = (
106 image.shape()[0] + padding.0 * 2,
107 image.shape()[1] + padding.1 * 2,
108 image.shape()[2],
109 );
110
111 let mut result = Array::from_elem(shape, self.0);
112 result
113 .slice_mut(s![
114 padding.0..shape.0 - padding.0,
115 padding.1..shape.1 - padding.1,
116 ..
117 ])
118 .assign(&image);
119
120 result
121 }
122
123 fn get_pixel(&self, image: ArrayView<T, Ix3>, index: (isize, isize)) -> Option<Array1<T>> {
124 let index = (index.0, index.1, 0);
125 if is_out_of_bounds(image.dim(), index) {
126 let v = vec![self.0; image.dim().2];
127 Some(Array1::from(v))
128 } else {
129 Some(image.slice(s![index.0, index.1, ..]).to_owned())
130 }
131 }
132
133 fn get_value(&self, image: ArrayView<T, Ix3>, index: (isize, isize, usize)) -> Option<T> {
134 if is_out_of_bounds(image.dim(), index) {
135 Some(self.0)
136 } else {
137 image
138 .get((index.0 as usize, index.1 as usize, index.2))
139 .copied()
140 }
141 }
142}
143
144impl<T> PaddingStrategy<T> for ZeroPadding
145where
146 T: Copy + Zero,
147{
148 fn pad(
149 &self,
150 image: ArrayView<T, Ix3>,
151 padding: (usize, usize),
152 ) -> ArrayBase<OwnedRepr<T>, Ix3> {
153 let padder = ConstantPadding(T::zero());
154 padder.pad(image, padding)
155 }
156
157 fn get_pixel(&self, image: ArrayView<T, Ix3>, index: (isize, isize)) -> Option<Array1<T>> {
158 let padder = ConstantPadding(T::zero());
159 padder.get_pixel(image, index)
160 }
161
162 fn get_value(&self, image: ArrayView<T, Ix3>, index: (isize, isize, usize)) -> Option<T> {
163 let padder = ConstantPadding(T::zero());
164 padder.get_value(image, index)
165 }
166}
167
168pub trait PaddingExt<T> {
170 type Output;
172 fn pad(&self, padding: (usize, usize), strategy: &dyn PaddingStrategy<T>) -> Self::Output;
174}
175
176impl<T, U> PaddingExt<T> for ArrayBase<U, Ix3>
177where
178 U: Data<Elem = T>,
179 T: Copy,
180{
181 type Output = ArrayBase<OwnedRepr<T>, Ix3>;
182
183 fn pad(&self, padding: (usize, usize), strategy: &dyn PaddingStrategy<T>) -> Self::Output {
184 strategy.pad(self.view(), padding)
185 }
186}
187
188impl<T, U, C> PaddingExt<T> for ImageBase<U, C>
189where
190 U: Data<Elem = T>,
191 T: Copy,
192 C: ColourModel,
193{
194 type Output = Image<T, C>;
195
196 fn pad(&self, padding: (usize, usize), strategy: &dyn PaddingStrategy<T>) -> Self::Output {
197 Self::Output {
198 data: strategy.pad(self.data.view(), padding),
199 model: PhantomData,
200 }
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use crate::core::colour_models::{Gray, RGB};
208
209 #[test]
210 fn constant_padding() {
211 let i = Image::<u8, Gray>::from_shape_data(3, 3, vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
212
213 let p = i.pad((1, 1), &ConstantPadding(0));
214
215 let exp = Image::<u8, Gray>::from_shape_data(
216 5,
217 5,
218 vec![
219 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 4, 5, 6, 0, 0, 7, 8, 9, 0, 0, 0, 0, 0, 0,
220 ],
221 );
222 assert_eq!(p, exp);
223
224 let p = i.pad((1, 1), &ConstantPadding(2));
225
226 let exp = Image::<u8, Gray>::from_shape_data(
227 5,
228 5,
229 vec![
230 2, 2, 2, 2, 2, 2, 1, 2, 3, 2, 2, 4, 5, 6, 2, 2, 7, 8, 9, 2, 2, 2, 2, 2, 2,
231 ],
232 );
233 assert_eq!(p, exp);
234
235 let p = i.pad((2, 0), &ConstantPadding(0));
236 let z = i.pad((2, 0), &ZeroPadding {});
237 assert_eq!(p, z);
238
239 let exp = Image::<u8, Gray>::from_shape_data(
240 7,
241 3,
242 vec![
243 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
244 ],
245 );
246 assert_eq!(p, exp);
247 }
248
249 #[test]
250 fn no_padding() {
251 let i = Image::<u8, RGB>::new(5, 5);
252 let p = i.pad((10, 10), &NoPadding {});
253
254 assert_eq!(i, p);
255
256 let p = i.pad((0, 0), &NoPadding {});
257 assert_eq!(i, p);
258 }
259}