Skip to main content

imageproc/
morphology.rs

1//! Functions for computing [morphological operators].
2//!
3//! [morphological operators]: https://homepages.inf.ed.ac.uk/rbf/HIPR2/morops.htm
4
5use crate::{
6    distance_transform::{DistanceFrom, Norm, distance_transform_impl, distance_transform_mut},
7    point::Point,
8};
9use image::{GrayImage, Luma};
10use itertools::Itertools;
11
12/// Sets all pixels within distance `k` of a foreground pixel to white.
13///
14/// A pixel is treated as belonging to the foreground if it has non-zero intensity.
15///
16/// # Examples
17/// ```
18/// # extern crate image;
19/// # #[macro_use]
20/// # extern crate imageproc;
21/// # fn main() {
22/// use image::GrayImage;
23/// use imageproc::morphology::dilate;
24/// use imageproc::distance_transform::Norm;
25///
26/// let image = gray_image!(
27///     0,   0,   0,   0,   0;
28///     0,   0,   0,   0,   0;
29///     0,   0, 255,   0,   0;
30///     0,   0,   0,   0,   0;
31///     0,   0,   0,   0,   0
32/// );
33///
34/// // L1 norm
35/// let l1_dilated = gray_image!(
36///     0,   0,   0,   0,   0;
37///     0,   0, 255,   0,   0;
38///     0, 255, 255, 255,   0;
39///     0,   0, 255,   0,   0;
40///     0,   0,   0,   0,   0
41/// );
42///
43/// assert_pixels_eq!(dilate(&image, Norm::L1, 1), l1_dilated);
44///
45/// // L2 norm
46/// //
47/// // When computing distances using the L2 norm we take the ceiling of the true values.
48/// // This means that using the L2 norm gives the same results as the L1 norm for `k <= 2`.
49/// let l2_dilated = gray_image!(
50///    0,   0,   0,   0,   0;
51///    0,   0, 255,   0,   0;
52///    0, 255, 255, 255,   0;
53///    0,   0, 255,   0,   0;
54///    0,   0,   0,   0,   0
55/// );
56///
57/// assert_pixels_eq!(dilate(&image, Norm::L2, 1), l2_dilated);
58///
59/// // LInf norm
60/// let linf_dilated = gray_image!(
61///    0,   0,   0,   0,   0;
62///    0, 255, 255, 255,   0;
63///    0, 255, 255, 255,   0;
64///    0, 255, 255, 255,   0;
65///    0,   0,   0,   0,   0
66/// );
67///
68/// assert_pixels_eq!(dilate(&image, Norm::LInf, 1), linf_dilated);
69/// # }
70/// ```
71pub fn dilate(image: &GrayImage, norm: Norm, k: u8) -> GrayImage {
72    let mut out = image.clone();
73    dilate_mut(&mut out, norm, k);
74    out
75}
76#[doc=generate_mut_doc_comment!("dilate")]
77pub fn dilate_mut(image: &mut GrayImage, norm: Norm, k: u8) {
78    distance_transform_mut(image, norm);
79    for p in image.iter_mut() {
80        *p = if *p <= k { 255 } else { 0 };
81    }
82}
83
84/// Sets all pixels within distance `k` of a background pixel to black.
85///
86/// A pixel is treated as belonging to the foreground if it has non-zero intensity.
87///
88/// # Examples
89/// ```
90/// # extern crate image;
91/// # #[macro_use]
92/// # extern crate imageproc;
93/// # fn main() {
94/// use image::GrayImage;
95/// use imageproc::morphology::erode;
96/// use imageproc::distance_transform::Norm;
97///
98/// let image = gray_image!(
99///     0,   0,   0,   0,   0,   0,   0,   0,  0;
100///     0, 255, 255, 255, 255, 255, 255, 255,  0;
101///     0, 255, 255, 255, 255, 255, 255, 255,  0;
102///     0, 255, 255, 255, 255, 255, 255, 255,  0;
103///     0, 255, 255, 255,   0, 255, 255, 255,  0;
104///     0, 255, 255, 255, 255, 255, 255, 255,  0;
105///     0, 255, 255, 255, 255, 255, 255, 255,  0;
106///     0, 255, 255, 255, 255, 255, 255, 255,  0;
107///     0,   0,   0,   0,   0,   0,   0,   0,  0
108/// );
109///
110/// // L1 norm - the outermost foreground pixels are eroded,
111/// // as well as those horizontally and vertically adjacent
112/// // to the centre background pixel.
113/// let l1_eroded = gray_image!(
114///     0,   0,   0,   0,   0,   0,   0,   0,  0;
115///     0,   0,   0,   0,   0,   0,   0,   0,  0;
116///     0,   0, 255, 255, 255, 255, 255,   0,  0;
117///     0,   0, 255, 255,   0, 255, 255,   0,  0;
118///     0,   0, 255,   0,   0,   0, 255,   0,  0;
119///     0,   0, 255, 255,   0, 255, 255,   0,  0;
120///     0,   0, 255, 255, 255, 255, 255,   0,  0;
121///     0,   0,   0,   0,   0,   0,   0,   0,  0;
122///     0,   0,   0,   0,   0,   0,   0,   0,  0
123/// );
124///
125/// assert_pixels_eq!(erode(&image, Norm::L1, 1), l1_eroded);
126///
127/// // L2 norm
128/// //
129/// // When computing distances using the L2 norm we take the ceiling of the true values.
130/// // This means that using the L2 norm gives the same results as the L1 norm for `k <= 2`.
131/// let l2_eroded = gray_image!(
132///     0,   0,   0,   0,   0,   0,   0,   0,  0;
133///     0,   0,   0,   0,   0,   0,   0,   0,  0;
134///     0,   0, 255, 255, 255, 255, 255,   0,  0;
135///     0,   0, 255, 255,   0, 255, 255,   0,  0;
136///     0,   0, 255,   0,   0,   0, 255,   0,  0;
137///     0,   0, 255, 255,   0, 255, 255,   0,  0;
138///     0,   0, 255, 255, 255, 255, 255,   0,  0;
139///     0,   0,   0,   0,   0,   0,   0,   0,  0;
140///     0,   0,   0,   0,   0,   0,   0,   0,  0
141/// );
142///
143/// assert_pixels_eq!(erode(&image, Norm::L2, 1), l2_eroded);
144///
145/// // LInf norm - all pixels eroded using the L1 norm are eroded,
146/// // as well as the pixels diagonally adjacent to the centre pixel.
147/// let linf_eroded = gray_image!(
148///     0,   0,   0,   0,   0,   0,   0,   0,  0;
149///     0,   0,   0,   0,   0,   0,   0,   0,  0;
150///     0,   0, 255, 255, 255, 255, 255,   0,  0;
151///     0,   0, 255,   0,   0,   0, 255,   0,  0;
152///     0,   0, 255,   0,   0,   0, 255,   0,  0;
153///     0,   0, 255,   0,   0,   0, 255,   0,  0;
154///     0,   0, 255, 255, 255, 255, 255,   0,  0;
155///     0,   0,   0,   0,   0,   0,   0,   0,  0;
156///     0,   0,   0,   0,   0,   0,   0,   0,  0
157/// );
158///
159/// assert_pixels_eq!(erode(&image, Norm::LInf, 1), linf_eroded);
160/// # }
161/// ```
162pub fn erode(image: &GrayImage, norm: Norm, k: u8) -> GrayImage {
163    let mut out = image.clone();
164    erode_mut(&mut out, norm, k);
165    out
166}
167#[doc=generate_mut_doc_comment!("erode")]
168pub fn erode_mut(image: &mut GrayImage, norm: Norm, k: u8) {
169    distance_transform_impl(image, norm, DistanceFrom::Background);
170    for p in image.iter_mut() {
171        *p = if *p <= k { 0 } else { 255 };
172    }
173}
174
175/// Erosion followed by dilation.
176///
177/// See the [`erode()`] and [`dilate()`]
178/// documentation for definitions of dilation and erosion.
179///
180/// # Examples
181/// ```
182/// # extern crate image;
183/// # #[macro_use]
184/// # extern crate imageproc;
185/// # fn main() {
186/// use imageproc::morphology::open;
187/// use imageproc::distance_transform::Norm;
188///
189/// // Isolated regions of foreground pixels are removed.
190/// let cross = gray_image!(
191///       0,   0,   0,   0,   0;
192///       0,   0, 255,   0,   0;
193///       0, 255, 255, 255,   0;
194///       0,   0, 255,   0,   0;
195///       0,   0,   0,   0,   0
196/// );
197///
198/// let opened_cross = gray_image!(
199///       0,   0,   0,   0,   0;
200///       0,   0,   0,   0,   0;
201///       0,   0,   0,   0,   0;
202///       0,   0,   0,   0,   0;
203///       0,   0,   0,   0,   0
204/// );
205///
206/// assert_pixels_eq!(
207///     open(&cross, Norm::LInf, 1),
208///     opened_cross
209/// );
210///
211/// // Large blocks survive unchanged.
212/// let blob = gray_image!(
213///       0,   0,   0,   0,   0;
214///       0, 255, 255, 255,   0;
215///       0, 255, 255, 255,   0;
216///       0, 255, 255, 255,   0;
217///       0,   0,   0,   0,   0
218/// );
219///
220/// assert_pixels_eq!(
221///     open(&blob, Norm::LInf, 1),
222///     blob
223/// );
224/// # }
225/// ```
226pub fn open(image: &GrayImage, norm: Norm, k: u8) -> GrayImage {
227    let mut out = image.clone();
228    open_mut(&mut out, norm, k);
229    out
230}
231#[doc=generate_mut_doc_comment!("open")]
232pub fn open_mut(image: &mut GrayImage, norm: Norm, k: u8) {
233    erode_mut(image, norm, k);
234    dilate_mut(image, norm, k);
235}
236
237/// Dilation followed by erosion.
238///
239/// See the [`erode()`] and [`dilate()`]
240/// documentation for definitions of dilation and erosion.
241///
242/// # Examples
243/// ```
244/// # extern crate image;
245/// # #[macro_use]
246/// # extern crate imageproc;
247/// # fn main() {
248/// use imageproc::morphology::close;
249/// use imageproc::distance_transform::Norm;
250///
251/// // Small holes are closed - hence the name.
252/// let small_hole = gray_image!(
253///     255, 255, 255, 255;
254///     255,   0,   0, 255;
255///     255,   0,   0, 255;
256///     255, 255, 255, 255
257/// );
258///
259/// let closed_small_hole = gray_image!(
260///     255, 255, 255, 255;
261///     255, 255, 255, 255;
262///     255, 255, 255, 255;
263///     255, 255, 255, 255
264/// );
265///
266/// assert_pixels_eq!(
267///     close(&small_hole, Norm::LInf, 1),
268///     closed_small_hole
269/// );
270///
271/// // Large holes survive unchanged.
272/// let large_hole = gray_image!(
273///     255, 255, 255, 255, 255;
274///     255,   0,   0,   0, 255;
275///     255,   0,   0,   0, 255;
276///     255,   0,   0,   0, 255;
277///     255, 255, 255, 255, 255
278/// );
279///
280/// assert_pixels_eq!(
281///     close(&large_hole, Norm::LInf, 1),
282///     large_hole
283/// );
284///
285/// // A dot gains a layer of foreground pixels
286/// // when dilated and loses them again when eroded,
287/// // resulting in no change.
288/// let dot = gray_image!(
289///       0,   0,   0,   0,   0;
290///       0,   0,   0,   0,   0;
291///       0,   0, 255,   0,   0;
292///       0,   0,   0,   0,   0;
293///       0,   0,   0,   0,   0
294/// );
295///
296/// assert_pixels_eq!(
297///     close(&dot, Norm::LInf, 1),
298///     dot
299/// );
300///
301/// // A dot near the boundary gains pixels in the top-left
302/// // of the image which are not within distance 1 of any
303/// // background pixels, so are not removed by erosion.
304/// let dot_near_boundary = gray_image!(
305///       0,   0,   0,   0,   0;
306///       0, 255,   0,   0,   0;
307///       0,   0,   0,   0,   0;
308///       0,   0,   0,   0,   0;
309///       0,   0,   0,   0,   0
310/// );
311///
312/// let closed_dot_near_boundary = gray_image!(
313///     255, 255,   0,   0,   0;
314///     255, 255,   0,   0,   0;
315///       0,   0,   0,   0,   0;
316///       0,   0,   0,   0,   0;
317///       0,   0,   0,   0,   0
318/// );
319///
320/// assert_pixels_eq!(
321///     close(&dot_near_boundary, Norm::LInf, 1),
322///     closed_dot_near_boundary
323/// );
324/// # }
325/// ```
326pub fn close(image: &GrayImage, norm: Norm, k: u8) -> GrayImage {
327    let mut out = image.clone();
328    close_mut(&mut out, norm, k);
329    out
330}
331#[doc=generate_mut_doc_comment!("close")]
332pub fn close_mut(image: &mut GrayImage, norm: Norm, k: u8) {
333    dilate_mut(image, norm, k);
334    erode_mut(image, norm, k);
335}
336
337/// A mask used in grayscale morphological operations.
338#[derive(Clone, Debug, PartialEq, Eq)]
339pub struct Mask {
340    /// For any optimisation/arithmetic purposes, it is guaranteed that:
341    /// - all the integer values will be strictly between -512 and 512
342    /// - all tuples will be sorted in reverse lexicographic order, line by line ((-1,-1),(0,-1),(1,-1),(-1,0),(0,0),...)
343    /// - no tuple shall appear twice
344    elements: Vec<Point<i16>>,
345}
346
347macro_rules! lines {
348    ($mask:expr) => {
349        $mask
350            .elements
351            .iter()
352            .chunk_by(|p| p.y)
353            .into_iter()
354            .map(|(y, line)| (y, line.map(|p| p.x)))
355    };
356}
357
358impl Mask {
359    /// Creates a mask containing the points `(x, y) - (center_x, center_y)` for each `(x, y)` with non-zero intensity in `image`.
360    ///
361    /// Mask contents are represented using signed integers, so `(center_x, center_y)` is not required to be within bounds for `image`. However, `image`
362    /// is restricted to have a side length of at most 511 pixels.
363    ///
364    /// # Panics
365    /// If `image.width() >= 512` or `image.height() >= 512`.
366    pub fn from_image(image: &GrayImage, center_x: u8, center_y: u8) -> Self {
367        assert!(
368            image.width() < 512,
369            "the input image must be at most 511 pixels wide"
370        );
371        assert!(
372            image.height() < 512,
373            "the input image must be at most 511 pixels high"
374        );
375        let center = Point::new(center_x, center_y).to_i16();
376        let elements = image
377            .enumerate_pixels()
378            .filter(|&(_, _, &p)| p[0] != 0)
379            .map(|(x, y, _)| Point::new(x, y).to_i16())
380            .map(|p| p - center)
381            .collect();
382        Self::new(elements)
383    }
384
385    fn new(elements: Vec<Point<i16>>) -> Self {
386        assert!(elements.len() <= (511 * 511) as usize);
387        debug_assert!(
388            elements
389                .iter()
390                .tuple_windows()
391                .all(|(a, b)| { if a.y == b.y { a.x < b.x } else { a.y < b.y } })
392        );
393        Self { elements }
394    }
395
396    /// Creates a square mask of side length `2 * radius + 1`.
397    ///
398    /// # Example
399    /// ```
400    /// # extern crate image;
401    /// # #[macro_use]
402    /// # extern crate imageproc;
403    /// # fn main() {
404    /// use imageproc::morphology::Mask;
405    ///
406    /// // Mask::square(1) is a 3x3 square mask centered at the origin.
407    /// let square = gray_image!(
408    ///     255, 255, 255;
409    ///     255, 255, 255;
410    ///     255, 255, 255
411    /// );
412    /// assert_eq!(Mask::square(1), Mask::from_image(&square, 1, 1));
413    /// # }
414    /// ```
415    pub fn square(radius: u8) -> Self {
416        let radius = i16::from(radius);
417        let range = -radius..=radius;
418        let elements = range
419            .clone()
420            .cartesian_product(range)
421            .map(|(y, x)| Point::new(x, y))
422            .collect();
423        Self::new(elements)
424    }
425
426    /// Creates a diamond-shaped mask containing all points with `L1` norm at most `radius`.
427    ///
428    /// # Example
429    /// ```
430    /// # extern crate image;
431    /// # #[macro_use]
432    /// # extern crate imageproc;
433    /// # fn main() {
434    /// use imageproc::morphology::Mask;
435    ///
436    /// // Mask::diamond(1) is a 3x3 cross centered at the origin.
437    /// let diamond_1 = gray_image!(
438    ///       0, 255,   0;
439    ///     255, 255, 255;
440    ///       0, 255,   0
441    /// );
442    /// assert_eq!(Mask::diamond(1), Mask::from_image(&diamond_1, 1, 1));
443    ///
444    /// // Mask::diamond(2) is a 5x5 diamond centered at the origin.
445    /// let diamond_2 = gray_image!(
446    ///       0,   0, 255,   0,   0;
447    ///       0, 255, 255, 255,   0;
448    ///     255, 255, 255, 255, 255;
449    ///       0, 255, 255, 255,   0;
450    ///       0,   0, 255,   0,   0
451    /// );
452    /// assert_eq!(Mask::diamond(2), Mask::from_image(&diamond_2, 2, 2));
453    /// # }
454    /// ```
455    pub fn diamond(radius: u8) -> Self {
456        let cap = 1 + 2 * usize::from(radius) * (usize::from(radius) + 1);
457        let mut elements = Vec::with_capacity(cap);
458        let radius = i16::from(radius);
459        let points = (-radius..=radius)
460            .flat_map(|y| ((y.abs() - radius)..=(radius - y.abs())).map(move |x| Point::new(x, y)));
461        elements.extend(points);
462        Self::new(elements)
463    }
464
465    /// Creates a disk-shaped mask containing all points with `L2` norm at most `radius`.
466    ///
467    /// When computing distances using the L2 norm we take the ceiling of the true values.
468    /// This means that using the L2 norm gives the same results as the `L1` norm for `radius <= 2`.
469    ///
470    /// # Example
471    /// ```
472    /// # extern crate image;
473    /// # #[macro_use]
474    /// # extern crate imageproc;
475    /// # fn main() {
476    /// use imageproc::morphology::Mask;
477    ///
478    /// // For radius <= 2, Mask::disk(radius) is the same as Mask::diamond(radius).
479    /// let disk_2 = gray_image!(
480    ///       0,   0, 255,   0,   0;
481    ///       0, 231, 204, 101,   0;
482    ///     149, 193, 188, 137, 199;
483    ///       0, 222, 182, 114,   0;
484    ///       0,   0, 217,   0,   0
485    /// );
486    /// assert_eq!(Mask::disk(2), Mask::from_image(&disk_2, 2, 2));
487    ///
488    /// // Mask::disk(3) is a filled circle of radius 3.
489    /// let disk_3 = gray_image!(
490    ///       0,   0,   0, 255,   0,   0,   0;
491    ///       0, 255, 255, 255, 255, 255,   0;
492    ///       0, 255, 255, 255, 255, 255,   0;
493    ///     255, 255, 255, 255, 255, 255, 255;
494    ///       0, 255, 255, 255, 255, 255,   0;
495    ///       0, 255, 255, 255, 255, 255,   0;
496    ///       0,   0,   0, 255,   0,   0,   0
497    /// );
498    /// assert_eq!(Mask::disk(3), Mask::from_image(&disk_3, 3, 3));
499    /// # }
500    /// ```
501    pub fn disk(radius: u8) -> Self {
502        let radius_squared = u32::from(radius).pow(2);
503        let half_widths_per_height = std::iter::successors(
504            Some((-i16::from(radius), 0u8)),
505            |&(last_height, last_half_width)| {
506                if last_height == i16::from(radius) {
507                    return None;
508                };
509                let next_height = last_height + 1;
510                let height_squared = (u32::from(next_height.unsigned_abs())).pow(2);
511                let next_half_width = if next_height <= 0 {
512                    // upper part of the circle => increasing width
513                    (u32::from(last_half_width)..)
514                        .find(|x| (x + 1).pow(2) + height_squared > radius_squared)?
515                } else {
516                    // lower part of the circle => decreasing width
517                    (0u32..=last_half_width.into())
518                        .rev()
519                        .find(|&x| x.pow(2) + height_squared <= radius_squared)?
520                };
521                Some((next_height, next_half_width.try_into().unwrap()))
522            },
523        );
524        let cap = half_widths_per_height
525            .clone()
526            .map(|(_, half_width)| 2 * usize::from(half_width) + 1)
527            .sum();
528        let mut elements = Vec::with_capacity(cap);
529        let points = half_widths_per_height.flat_map(|(y, half_width)| {
530            (-i16::from(half_width)..=i16::from(half_width)).map(move |x| Point::new(x, y))
531        });
532        elements.extend(points);
533        Self::new(elements)
534    }
535}
536
537fn mask_reduce<F: Fn(u8, u8) -> u8>(
538    image: &GrayImage,
539    mask: &Mask,
540    neutral: u8,
541    operator: F,
542) -> GrayImage {
543    let mut result = GrayImage::from_pixel(image.width(), image.height(), Luma([neutral]));
544    for (y, line_group) in lines!(mask) {
545        let y = i64::from(y);
546        let line = line_group.collect::<Vec<_>>();
547        let input_rows = image
548            .chunks(image.width() as usize)
549            .skip(y.try_into().unwrap_or(0));
550        let output_rows = result
551            .chunks_mut(image.width() as usize)
552            .skip((-y).try_into().unwrap_or(0));
553        for (input_row, output_row) in input_rows.zip(output_rows) {
554            for x in line.iter().copied() {
555                let inputs = input_row.iter().skip(x.try_into().unwrap_or(0));
556                let outputs = output_row.iter_mut().skip((-x).try_into().unwrap_or(0));
557                for (&input, output) in inputs.zip(outputs) {
558                    *output = operator(input, *output);
559                }
560            }
561        }
562    }
563    result
564}
565
566/// Computes the morphologic dilation of `image` with the given mask.
567///
568/// For each input pixel, the output pixel will be the maximum of all pixels included
569/// in the mask at that position. If the mask doesn't intersect any input pixel at some point,
570/// it will default to a value of [`u8::MIN`].
571///
572/// # Examples
573/// ```
574/// # extern crate image;
575/// # #[macro_use]
576/// # extern crate imageproc;
577/// # fn main() {
578/// use image::GrayImage;
579/// use imageproc::morphology::{Mask, grayscale_dilate};
580///
581/// let image = gray_image!(
582///     7,   0,   0,   0,   0,   0;
583///     0,   0,   0,   0,   0,   0;
584///     0,   0,  99,   0,   0,   0;
585///     0,   0,   0,   0,   0,   0;
586///     0,   0,   0,   0,   0, 222
587/// );
588///
589/// // Using a diamond mask
590/// let diamond_dilated = gray_image!(
591///     7,   7,   0,   0,   0,   0;
592///     7,   0,  99,   0,   0,   0;
593///     0,  99,  99,  99,   0,   0;
594///     0,   0,  99,   0,   0, 222;
595///     0,   0,   0,   0, 222, 222
596/// );
597/// assert_pixels_eq!(grayscale_dilate(&image, &Mask::diamond(1)), diamond_dilated);
598///
599/// // Using a disk mask
600/// let disk_dilated = gray_image!(
601///    99,  99,  99,  99,  99,   0;
602///    99,  99,  99,  99,  99, 222;
603///    99,  99,  99, 222, 222, 222;
604///    99,  99,  99, 222, 222, 222;
605///    99,  99, 222, 222, 222, 222
606/// );
607/// assert_pixels_eq!(grayscale_dilate(&image, &Mask::disk(3)), disk_dilated);
608///
609/// // Using a square mask
610/// let square_dilated = gray_image!(
611///     7,   7,   0,   0,   0,   0;
612///     7,  99,  99,  99,   0,   0;
613///     0,  99,  99,  99,   0,   0;
614///     0,  99,  99,  99, 222, 222;
615///     0,   0,   0,   0, 222, 222
616/// );
617/// assert_pixels_eq!(grayscale_dilate(&image, &Mask::square(1)), square_dilated);
618///
619/// // Using an arbitrary mask
620/// let column_mask = Mask::from_image(
621///     &gray_image!(
622///       255;
623///       255;
624///       255;
625///       255
626///     ),  
627///     0,  1
628/// );
629/// let column_dilated = gray_image!(
630///     7,   0,  99,   0,   0,   0;
631///     7,   0,  99,   0,   0,   0;
632///     0,   0,  99,   0,   0, 222;
633///     0,   0,  99,   0,   0, 222;
634///     0,   0,   0,   0,   0, 222
635/// );
636/// assert_pixels_eq!(grayscale_dilate(&image, &column_mask), column_dilated);
637/// # }
638/// ```
639pub fn grayscale_dilate(image: &GrayImage, mask: &Mask) -> GrayImage {
640    mask_reduce(image, mask, u8::MIN, u8::max)
641}
642
643/// Computes the morphologic erosion of `image` with the given mask.
644///
645/// For each input pixel, the output pixel will be the minimum of all pixels included
646/// in the mask at that position. If the mask doesn't intersect any input pixel at some point,
647/// it will default to a value of [`u8::MAX`].
648///
649/// # Examples
650/// ```
651/// # extern crate image;
652/// # #[macro_use]
653/// # extern crate imageproc;
654/// # fn main() {
655/// use image::GrayImage;
656/// use imageproc::morphology::{Mask, grayscale_erode};
657///
658/// let image = gray_image!(
659///     7,  99,  99,  99,  99, 222;
660///    99,  99,  99,  99,  99, 222;
661///    99,  99,  99,  99, 222, 222;
662///     7,  99,  99,  99, 222, 222;
663///    99,  99,  99, 222, 222, 222
664/// );
665///
666/// // Using a diamond mask
667/// let diamond_eroded = gray_image!(
668///     7,   7,  99,  99,  99,  99;
669///     7,  99,  99,  99,  99,  99;
670///     7,  99,  99,  99,  99, 222;
671///     7,   7,  99,  99,  99, 222;
672///     7,  99,  99,  99, 222, 222
673/// );
674/// assert_pixels_eq!(grayscale_erode(&image, &Mask::diamond(1)), diamond_eroded);
675///
676/// // Using a disk mask
677/// let disk_eroded = gray_image!(
678///     7,   7,   7,   7,  99,  99;
679///     7,   7,   7,  99,  99,  99;
680///     7,   7,   7,  99,  99,  99;
681///     7,   7,   7,   7,  99,  99;
682///     7,   7,   7,  99,  99,  99
683/// );
684/// assert_pixels_eq!(grayscale_erode(&image, &Mask::disk(3)), disk_eroded);
685///
686/// // Using a square mask
687/// let square_eroded = gray_image!(
688///     7,   7,  99,  99,  99,  99;
689///     7,   7,  99,  99,  99,  99;
690///     7,   7,  99,  99,  99,  99;
691///     7,   7,  99,  99,  99, 222;
692///     7,   7,  99,  99,  99, 222
693/// );
694/// assert_pixels_eq!(grayscale_erode(&image, &Mask::square(1)), square_eroded);
695///
696/// // Using an arbitrary mask
697/// let column_mask = Mask::from_image(
698///     &gray_image!(
699///       255;
700///       255;
701///       255;
702///       255
703///     ),  
704///     0,  1
705/// );
706/// let column_eroded = gray_image!(
707///     7,  99,  99,  99,  99, 222;
708///     7,  99,  99,  99,  99, 222;
709///     7,  99,  99,  99,  99, 222;
710///     7,  99,  99,  99, 222, 222;
711///     7,  99,  99,  99, 222, 222
712/// );
713/// assert_pixels_eq!(grayscale_erode(&image, &column_mask), column_eroded);
714/// # }
715/// ```
716pub fn grayscale_erode(image: &GrayImage, mask: &Mask) -> GrayImage {
717    mask_reduce(image, mask, u8::MAX, u8::min)
718}
719
720/// Grayscale erosion followed by grayscale dilation.
721///
722/// See the [`grayscale_dilate()`]
723/// and [`grayscale_erode()`]
724/// documentation for definitions of dilation and erosion.
725///
726////// # Examples
727/// ```
728/// # extern crate image;
729/// # #[macro_use]
730/// # extern crate imageproc;
731/// # fn main() {
732/// use image::GrayImage;
733/// use imageproc::morphology::{Mask, grayscale_open};
734///
735/// let image = gray_image!(
736///   100,  99,  99,  99, 222,  99;
737///    99,  99,  99, 222, 222, 222;
738///    99,   7,  99,  99, 222,  99;
739///     7,   7,   7,  99,  99,  99;
740///    99,   7,  99,  99,  99,  99
741/// );
742///
743/// // Isolated regions of foreground pixels are removed,
744/// // while isolated zones of background are maintained
745/// let image_opened = gray_image!(
746///    99,  99,  99,  99,  99,  99;
747///    99,  99,  99,  99,  99,  99;
748///     7,   7,  99,  99,  99,  99;
749///     7,   7,   7,  99,  99,  99;
750///     7,   7,   7,  99,  99,  99
751/// );
752/// assert_pixels_eq!(grayscale_open(&image, &Mask::square(1)), image_opened);
753///
754/// // grayscale_open is idempotent - applying it a second time has no effect.
755/// assert_pixels_eq!(grayscale_open(&image_opened, &Mask::square(1)), image_opened);
756/// # }
757/// ```
758pub fn grayscale_open(image: &GrayImage, mask: &Mask) -> GrayImage {
759    grayscale_dilate(&grayscale_erode(image, mask), mask)
760}
761
762/// Grayscale dilation followed by grayscale erosion.
763///
764/// See the [`grayscale_dilate()`]
765/// and [`grayscale_erode()`]
766/// documentation for definitions of dilation and erosion.
767///
768////// # Examples
769/// ```
770/// # extern crate image;
771/// # #[macro_use]
772/// # extern crate imageproc;
773/// # fn main() {
774/// use image::GrayImage;
775/// use imageproc::morphology::{Mask, grayscale_close};
776///
777/// let image = gray_image!(
778///    50,  99,  99,  99, 222,  99;
779///    99,  99,  99, 222, 222, 222;
780///    99,   7,  99,  99, 222,  99;
781///     7,   7,   7,  99,  99,  99;
782///    99,   7,  99,  99,  99,  99
783/// );
784///
785/// // Isolated regions of background pixels are removed,
786/// // while isolated zones of foreground pixels are maintained
787/// let image_closed = gray_image!(
788///    99,  99,  99, 222, 222, 222;
789///    99,  99,  99, 222, 222, 222;
790///    99,  99,  99,  99, 222, 222;
791///    99,  99,  99,  99,  99,  99;
792///    99,  99,  99,  99,  99,  99
793/// );
794/// assert_pixels_eq!(grayscale_close(&image, &Mask::square(1)), image_closed);
795///
796/// // grayscale_close is idempotent - applying it a second time has no effect.
797/// assert_pixels_eq!(grayscale_close(&image_closed, &Mask::square(1)), image_closed);
798/// # }
799/// ```
800pub fn grayscale_close(image: &GrayImage, mask: &Mask) -> GrayImage {
801    grayscale_erode(&grayscale_dilate(image, mask), mask)
802}
803
804#[cfg(test)]
805mod tests {
806    use super::*;
807
808    #[test]
809    fn test_dilate_point_l1_0() {
810        let image = gray_image!(
811              0,   0,   0,   0,   0;
812              0,   0,   0,   0,   0;
813              0,   0, 255,   0,   0;
814              0,   0,   0,   0,   0;
815              0,   0,   0,   0,   0
816        );
817        let dilated = dilate(&image, Norm::L1, 0);
818        assert_pixels_eq!(dilated, image);
819    }
820
821    #[test]
822    fn test_dilate_point_l1_1() {
823        let image = gray_image!(
824              0,   0,   0,   0,   0;
825              0,   0,   0,   0,   0;
826              0,   0, 255,   0,   0;
827              0,   0,   0,   0,   0;
828              0,   0,   0,   0,   0
829        );
830        let dilated = dilate(&image, Norm::L1, 1);
831
832        let expected = gray_image!(
833              0,   0,   0,   0,   0;
834              0,   0, 255,   0,   0;
835              0, 255, 255, 255,   0;
836              0,   0, 255,   0,   0;
837              0,   0,   0,   0,   0
838        );
839        assert_pixels_eq!(dilated, expected);
840    }
841
842    #[test]
843    fn test_dilate_point_l1_2() {
844        let image = gray_image!(
845              0,   0,   0,   0,   0;
846              0,   0,   0,   0,   0;
847              0,   0, 255,   0,   0;
848              0,   0,   0,   0,   0;
849              0,   0,   0,   0,   0
850        );
851        let dilated = dilate(&image, Norm::L1, 2);
852
853        let expected = gray_image!(
854              0,   0, 255,   0,   0;
855              0, 255, 255, 255,   0;
856            255, 255, 255, 255, 255;
857              0, 255, 255, 255,   0;
858              0,   0, 255,   0,   0
859        );
860        assert_pixels_eq!(dilated, expected);
861    }
862
863    #[test]
864    fn test_dilate_point_l1_4() {
865        let image = gray_image!(
866              0,   0,   0,   0,   0,   0,   0,   0,   0;
867              0,   0,   0,   0,   0,   0,   0,   0,   0;
868              0,   0,   0,   0,   0,   0,   0,   0,   0;
869              0,   0,   0,   0,   0,   0,   0,   0,   0;
870              0,   0,   0,   0, 255,   0,   0,   0,   0;
871              0,   0,   0,   0,   0,   0,   0,   0,   0;
872              0,   0,   0,   0,   0,   0,   0,   0,   0;
873              0,   0,   0,   0,   0,   0,   0,   0,   0;
874              0,   0,   0,   0,   0,   0,   0,   0,   0
875        );
876        let dilated = dilate(&image, Norm::L1, 4);
877
878        let expected = gray_image!(
879              0,   0,   0,   0, 255,   0,   0,   0,   0;
880              0,   0,   0, 255, 255, 255,   0,   0,   0;
881              0,   0, 255, 255, 255, 255, 255,   0,   0;
882              0, 255, 255, 255, 255, 255, 255, 255,   0;
883            255, 255, 255, 255, 255, 255, 255, 255, 255;
884              0, 255, 255, 255, 255, 255, 255, 255,   0;
885              0,   0, 255, 255, 255, 255, 255,   0,   0;
886              0,   0,   0, 255, 255, 255,   0,   0,   0;
887              0,   0,   0,   0, 255,   0,   0,   0,   0
888        );
889
890        assert_pixels_eq!(dilated, expected);
891    }
892
893    #[test]
894    fn test_dilate_point_l2_0() {
895        let image = gray_image!(
896              0,   0,   0,   0,   0;
897              0,   0,   0,   0,   0;
898              0,   0, 255,   0,   0;
899              0,   0,   0,   0,   0;
900              0,   0,   0,   0,   0
901        );
902        let dilated = dilate(&image, Norm::L2, 0);
903        assert_pixels_eq!(dilated, image);
904    }
905
906    #[test]
907    fn test_dilate_point_l2_1() {
908        let image = gray_image!(
909              0,   0,   0,   0,   0;
910              0,   0,   0,   0,   0;
911              0,   0, 255,   0,   0;
912              0,   0,   0,   0,   0;
913              0,   0,   0,   0,   0
914        );
915        let dilated = dilate(&image, Norm::L2, 1);
916
917        let expected = gray_image!(
918              0,   0,   0,   0,   0;
919              0,   0, 255,   0,   0;
920              0, 255, 255, 255,   0;
921              0,   0, 255,   0,   0;
922              0,   0,   0,   0,   0
923        );
924        assert_pixels_eq!(dilated, expected);
925    }
926
927    #[test]
928    fn test_dilate_point_l2_2() {
929        let image = gray_image!(
930              0,   0,   0,   0,   0;
931              0,   0,   0,   0,   0;
932              0,   0, 255,   0,   0;
933              0,   0,   0,   0,   0;
934              0,   0,   0,   0,   0
935        );
936        let dilated = dilate(&image, Norm::L2, 2);
937
938        let expected = gray_image!(
939              0,   0, 255,   0,   0;
940              0, 255, 255, 255,   0;
941            255, 255, 255, 255, 255;
942              0, 255, 255, 255,   0;
943              0,   0, 255,   0,   0
944        );
945        assert_pixels_eq!(dilated, expected);
946    }
947
948    #[test]
949    fn test_dilate_point_l2_4() {
950        let image = gray_image!(
951            0,   0,   0,   0,   0,   0,   0,   0,   0;
952            0,   0,   0,   0,   0,   0,   0,   0,   0;
953            0,   0,   0,   0,   0,   0,   0,   0,   0;
954            0,   0,   0,   0,   0,   0,   0,   0,   0;
955            0,   0,   0,   0, 255,   0,   0,   0,   0;
956            0,   0,   0,   0,   0,   0,   0,   0,   0;
957            0,   0,   0,   0,   0,   0,   0,   0,   0;
958            0,   0,   0,   0,   0,   0,   0,   0,   0;
959            0,   0,   0,   0,   0,   0,   0,   0,   0
960        );
961        let dilated = dilate(&image, Norm::L2, 4);
962
963        let expected = gray_image!(
964              0,   0,   0,   0, 255,   0,   0,   0,   0;
965              0,   0, 255, 255, 255, 255, 255,   0,   0;
966              0, 255, 255, 255, 255, 255, 255, 255,   0;
967              0, 255, 255, 255, 255, 255, 255, 255,   0;
968            255, 255, 255, 255, 255, 255, 255, 255, 255;
969              0, 255, 255, 255, 255, 255, 255, 255,   0;
970              0, 255, 255, 255, 255, 255, 255, 255,   0;
971              0,   0, 255, 255, 255, 255, 255,   0,   0;
972              0,   0,   0,   0, 255,   0,   0,   0,   0
973        );
974        assert_pixels_eq!(dilated, expected);
975    }
976
977    #[test]
978    fn test_dilate_point_linf_0() {
979        let image = gray_image!(
980              0,   0,   0,   0,   0;
981              0,   0,   0,   0,   0;
982              0,   0, 255,   0,   0;
983              0,   0,   0,   0,   0;
984              0,   0,   0,   0,   0
985        );
986        let dilated = dilate(&image, Norm::LInf, 0);
987        assert_pixels_eq!(dilated, image);
988    }
989
990    #[test]
991    fn test_dilate_point_linf_1() {
992        let image = gray_image!(
993              0,   0,   0,   0,   0;
994              0,   0,   0,   0,   0;
995              0,   0, 255,   0,   0;
996              0,   0,   0,   0,   0;
997              0,   0,   0,   0,   0
998        );
999        let dilated = dilate(&image, Norm::LInf, 1);
1000
1001        let expected = gray_image!(
1002              0,   0,   0,   0,   0;
1003              0, 255, 255, 255,   0;
1004              0, 255, 255, 255,   0;
1005              0, 255, 255, 255,   0;
1006              0,   0,   0,   0,   0
1007        );
1008        assert_pixels_eq!(dilated, expected);
1009    }
1010
1011    #[test]
1012    fn test_dilate_point_linf_2() {
1013        let image = gray_image!(
1014              0,   0,   0,   0,   0;
1015              0,   0,   0,   0,   0;
1016              0,   0, 255,   0,   0;
1017              0,   0,   0,   0,   0;
1018              0,   0,   0,   0,   0
1019        );
1020        let dilated = dilate(&image, Norm::LInf, 2);
1021
1022        let expected = gray_image!(
1023            255, 255, 255, 255, 255;
1024            255, 255, 255, 255, 255;
1025            255, 255, 255, 255, 255;
1026            255, 255, 255, 255, 255;
1027            255, 255, 255, 255, 255
1028        );
1029        assert_pixels_eq!(dilated, expected);
1030    }
1031
1032    #[test]
1033    fn test_dilate_point_linf_4() {
1034        let image = gray_image!(
1035            0,   0,   0,   0,   0,   0,   0,   0,   0;
1036            0,   0,   0,   0,   0,   0,   0,   0,   0;
1037            0,   0,   0,   0,   0,   0,   0,   0,   0;
1038            0,   0,   0,   0,   0,   0,   0,   0,   0;
1039            0,   0,   0,   0, 255,   0,   0,   0,   0;
1040            0,   0,   0,   0,   0,   0,   0,   0,   0;
1041            0,   0,   0,   0,   0,   0,   0,   0,   0;
1042            0,   0,   0,   0,   0,   0,   0,   0,   0;
1043            0,   0,   0,   0,   0,   0,   0,   0,   0
1044        );
1045        let dilated = dilate(&image, Norm::LInf, 4);
1046
1047        let expected = gray_image!(
1048            255, 255, 255, 255, 255, 255, 255, 255, 255;
1049            255, 255, 255, 255, 255, 255, 255, 255, 255;
1050            255, 255, 255, 255, 255, 255, 255, 255, 255;
1051            255, 255, 255, 255, 255, 255, 255, 255, 255;
1052            255, 255, 255, 255, 255, 255, 255, 255, 255;
1053            255, 255, 255, 255, 255, 255, 255, 255, 255;
1054            255, 255, 255, 255, 255, 255, 255, 255, 255;
1055            255, 255, 255, 255, 255, 255, 255, 255, 255;
1056            255, 255, 255, 255, 255, 255, 255, 255, 255
1057        );
1058        assert_pixels_eq!(dilated, expected);
1059    }
1060
1061    #[test]
1062    fn test_erode_point_l1_0() {
1063        let image = gray_image!(
1064              0,   0,   0,   0,   0;
1065              0,   0,   0,   0,   0;
1066              0,   0, 255,   0,   0;
1067              0,   0,   0,   0,   0;
1068              0,   0,   0,   0,   0
1069        );
1070        let eroded = erode(&image, Norm::L1, 0);
1071        assert_pixels_eq!(eroded, image);
1072    }
1073
1074    #[test]
1075    fn test_erode_point_l1_1() {
1076        let image = gray_image!(
1077              0,   0,   0,   0,   0;
1078              0,   0,   0,   0,   0;
1079              0,   0, 255,   0,   0;
1080              0,   0,   0,   0,   0;
1081              0,   0,   0,   0,   0
1082        );
1083        let eroded = erode(&image, Norm::L1, 1);
1084
1085        let expected = gray_image!(
1086              0,   0,   0,   0,   0;
1087              0,   0,   0,   0,   0;
1088              0,   0,   0,   0,   0;
1089              0,   0,   0,   0,   0;
1090              0,   0,   0,   0,   0
1091        );
1092        assert_pixels_eq!(eroded, expected);
1093    }
1094
1095    #[test]
1096    fn test_erode_dented_wall_l1_4() {
1097        let image = gray_image!(
1098            255, 255, 255, 255, 255, 255, 255, 255,   0;
1099            255, 255, 255, 255, 255, 255, 255, 255,   0;
1100            255, 255, 255, 255, 255, 255, 255, 255,   0;
1101            255, 255, 255, 255, 255, 255, 255, 255,   0;
1102            255, 255, 255, 255, 255, 255,   0,   0,   0;
1103            255, 255, 255, 255, 255, 255, 255, 255,   0;
1104            255, 255, 255, 255, 255, 255, 255, 255,   0;
1105            255, 255, 255, 255, 255, 255, 255, 255,   0;
1106            255, 255, 255, 255, 255, 255, 255, 255,   0
1107        );
1108        let dilated = erode(&image, Norm::L1, 4);
1109
1110        let expected = gray_image!(
1111            255, 255, 255, 255,   0,   0,   0,   0,   0;
1112            255, 255, 255, 255,   0,   0,   0,   0,   0;
1113            255, 255, 255, 255,   0,   0,   0,   0,   0;
1114            255, 255, 255,   0,   0,   0,   0,   0,   0;
1115            255, 255,   0,   0,   0,   0,   0,   0,   0;
1116            255, 255, 255,   0,   0,   0,   0,   0,   0;
1117            255, 255, 255, 255,   0,   0,   0,   0,   0;
1118            255, 255, 255, 255,   0,   0,   0,   0,   0;
1119            255, 255, 255, 255,   0,   0,   0,   0,   0
1120        );
1121        assert_pixels_eq!(dilated, expected);
1122    }
1123
1124    #[test]
1125    fn test_erode_point_l2_0() {
1126        let image = gray_image!(
1127              0,   0,   0,   0,   0;
1128              0,   0,   0,   0,   0;
1129              0,   0, 255,   0,   0;
1130              0,   0,   0,   0,   0;
1131              0,   0,   0,   0,   0
1132        );
1133        let eroded = erode(&image, Norm::L2, 0);
1134        assert_pixels_eq!(eroded, image);
1135    }
1136
1137    #[test]
1138    fn test_erode_point_l2_1() {
1139        let image = gray_image!(
1140              0,   0,   0,   0,   0;
1141              0,   0,   0,   0,   0;
1142              0,   0, 255,   0,   0;
1143              0,   0,   0,   0,   0;
1144              0,   0,   0,   0,   0
1145        );
1146        let eroded = erode(&image, Norm::L2, 1);
1147
1148        let expected = gray_image!(
1149              0,   0,   0,   0,   0;
1150              0,   0,   0,   0,   0;
1151              0,   0,   0,   0,   0;
1152              0,   0,   0,   0,   0;
1153              0,   0,   0,   0,   0
1154        );
1155        assert_pixels_eq!(eroded, expected);
1156    }
1157
1158    #[test]
1159    fn test_erode_dented_wall_l2_4() {
1160        let image = gray_image!(
1161            255, 255, 255, 255, 255, 255, 255, 255,   0;
1162            255, 255, 255, 255, 255, 255, 255, 255,   0;
1163            255, 255, 255, 255, 255, 255, 255, 255,   0;
1164            255, 255, 255, 255, 255, 255, 255, 255,   0;
1165            255, 255, 255, 255, 255, 255,   0,   0,   0;
1166            255, 255, 255, 255, 255, 255, 255, 255,   0;
1167            255, 255, 255, 255, 255, 255, 255, 255,   0;
1168            255, 255, 255, 255, 255, 255, 255, 255,   0;
1169            255, 255, 255, 255, 255, 255, 255, 255,   0
1170        );
1171        let dilated = erode(&image, Norm::L2, 4);
1172
1173        let expected = gray_image!(
1174            255, 255, 255, 255,   0,   0,   0,   0,   0;
1175            255, 255, 255, 255,   0,   0,   0,   0,   0;
1176            255, 255, 255,   0,   0,   0,   0,   0,   0;
1177            255, 255, 255,   0,   0,   0,   0,   0,   0;
1178            255, 255,   0,   0,   0,   0,   0,   0,   0;
1179            255, 255, 255,   0,   0,   0,   0,   0,   0;
1180            255, 255, 255,   0,   0,   0,   0,   0,   0;
1181            255, 255, 255, 255,   0,   0,   0,   0,   0;
1182            255, 255, 255, 255,   0,   0,   0,   0,   0
1183        );
1184        assert_pixels_eq!(dilated, expected);
1185    }
1186
1187    #[test]
1188    fn test_erode_point_linf_0() {
1189        let image = gray_image!(
1190              0,   0,   0,   0,   0;
1191              0,   0,   0,   0,   0;
1192              0,   0, 255,   0,   0;
1193              0,   0,   0,   0,   0;
1194              0,   0,   0,   0,   0
1195        );
1196        let eroded = erode(&image, Norm::LInf, 0);
1197        assert_pixels_eq!(eroded, image);
1198    }
1199
1200    #[test]
1201    fn test_erode_point_linf_1() {
1202        let image = gray_image!(
1203              0,   0,   0,   0,   0;
1204              0,   0,   0,   0,   0;
1205              0,   0, 255,   0,   0;
1206              0,   0,   0,   0,   0;
1207              0,   0,   0,   0,   0
1208        );
1209        let eroded = erode(&image, Norm::LInf, 1);
1210
1211        let expected = gray_image!(
1212              0,   0,   0,   0,   0;
1213              0,   0,   0,   0,   0;
1214              0,   0,   0,   0,   0;
1215              0,   0,   0,   0,   0;
1216              0,   0,   0,   0,   0
1217        );
1218        assert_pixels_eq!(eroded, expected);
1219    }
1220
1221    #[test]
1222    fn test_erode_dented_wall_linf_4() {
1223        let image = gray_image!(
1224            255, 255, 255, 255, 255, 255, 255, 255,   0;
1225            255, 255, 255, 255, 255, 255, 255, 255,   0;
1226            255, 255, 255, 255, 255, 255, 255, 255,   0;
1227            255, 255, 255, 255, 255, 255, 255, 255,   0;
1228            255, 255, 255, 255, 255, 255,   0,   0,   0;
1229            255, 255, 255, 255, 255, 255, 255, 255,   0;
1230            255, 255, 255, 255, 255, 255, 255, 255,   0;
1231            255, 255, 255, 255, 255, 255, 255, 255,   0;
1232            255, 255, 255, 255, 255, 255, 255, 255,   0
1233        );
1234        let dilated = erode(&image, Norm::LInf, 4);
1235
1236        let expected = gray_image!(
1237            255, 255,   0,   0,   0,   0,   0,   0,   0;
1238            255, 255,   0,   0,   0,   0,   0,   0,   0;
1239            255, 255,   0,   0,   0,   0,   0,   0,   0;
1240            255, 255,   0,   0,   0,   0,   0,   0,   0;
1241            255, 255,   0,   0,   0,   0,   0,   0,   0;
1242            255, 255,   0,   0,   0,   0,   0,   0,   0;
1243            255, 255,   0,   0,   0,   0,   0,   0,   0;
1244            255, 255,   0,   0,   0,   0,   0,   0,   0;
1245            255, 255,   0,   0,   0,   0,   0,   0,   0
1246        );
1247
1248        assert_pixels_eq!(dilated, expected);
1249    }
1250
1251    #[test]
1252    fn test_mask_from_image_equality() {
1253        let ring_mask_base = gray_image!(
1254             100,  75, 255, 222;
1255              84,   0,   0,   1;
1256              99,   0,   0,  22;
1257             255,   7, 255,  20
1258        );
1259
1260        let other_ring_mask_base = gray_image!(
1261             18, 172,  13,   5;
1262             45,   0,   0, 101;
1263            222,   0,   0,  93;
1264              1,   9, 212,  35
1265        );
1266
1267        assert_eq!(
1268            Mask::from_image(&ring_mask_base, 1, 1),
1269            Mask::from_image(&other_ring_mask_base, 1, 1)
1270        );
1271    }
1272
1273    #[test]
1274    fn test_mask_from_image_displacement_inequality() {
1275        let mask_base = gray_image!(
1276             100,  75, 255, 222;
1277              84,   0,   0,   1;
1278              99,   0,   0,  22;
1279             255,   7, 255,  20
1280        );
1281
1282        assert_ne!(
1283            Mask::from_image(&mask_base, 1, 1),
1284            Mask::from_image(&mask_base, 2, 2)
1285        );
1286    }
1287
1288    #[test]
1289    fn test_mask_from_image_empty() {
1290        let mask_base = gray_image!(0);
1291        assert!(Mask::from_image(&mask_base, 1, 1).elements.is_empty())
1292    }
1293
1294    /// this tests that it doesn't panic
1295    #[test]
1296    fn test_mask_from_image_outside() {
1297        let mask_base = gray_image!(
1298             100,  75, 255, 222;
1299              84,   0,   0,   1;
1300              99,   0,   0,  22;
1301             255,   7, 255,  20
1302        );
1303        let _ = Mask::from_image(&mask_base, 20, 20);
1304    }
1305
1306    #[test]
1307    #[should_panic]
1308    fn test_mask_from_image_out_of_bounds() {
1309        let mask_base = GrayImage::new(600, 5);
1310        Mask::from_image(&mask_base, 5, 5);
1311    }
1312
1313    #[test]
1314    fn test_masks_0() {
1315        let mask_base = gray_image!(72);
1316        assert_eq!(Mask::from_image(&mask_base, 0, 0), Mask::square(0));
1317        assert_eq!(Mask::from_image(&mask_base, 0, 0), Mask::diamond(0));
1318        assert_eq!(Mask::from_image(&mask_base, 0, 0), Mask::disk(0))
1319    }
1320
1321    #[test]
1322    fn test_mask_square_1() {
1323        let mask_base = gray_image!(
1324            72,  31, 148;
1325             2, 219, 173;
1326            48,   7, 200
1327        );
1328        assert_eq!(Mask::from_image(&mask_base, 1, 1), Mask::square(1));
1329    }
1330
1331    #[test]
1332    fn test_mask_square_2() {
1333        let mask_base = gray_image!(
1334            217, 188, 101, 222, 137;
1335            231, 204, 255, 182, 193;
1336            193, 101, 188, 217, 149;
1337            217, 188, 231, 222, 137;
1338            101, 204, 222, 255, 193
1339        );
1340        assert_eq!(Mask::from_image(&mask_base, 2, 2), Mask::square(2));
1341    }
1342
1343    #[test]
1344    fn test_mask_square_3() {
1345        let mask_base = gray_image!(
1346            217, 188, 101, 222, 137, 101, 222;
1347            231, 204, 255, 222, 137, 222, 255;
1348            193, 101, 188, 217, 217, 222, 188;
1349            217, 188, 231, 222, 137, 217, 149;
1350            193, 255, 193, 188, 231, 222, 188;
1351            217, 188, 231, 149, 101, 188, 149;
1352            101, 204, 222, 255, 193, 255, 182
1353        );
1354        assert_eq!(Mask::from_image(&mask_base, 3, 3), Mask::square(3));
1355    }
1356
1357    #[test]
1358    fn test_mask_diamond_1() {
1359        let mask_base = gray_image!(
1360             0,  31,   0;
1361             2, 219, 173;
1362             0,   7,   0
1363        );
1364        assert_eq!(Mask::from_image(&mask_base, 1, 1), Mask::diamond(1));
1365    }
1366
1367    #[test]
1368    fn test_mask_diamond_2() {
1369        let mask_base = gray_image!(
1370              0,   0, 255,   0,   0;
1371              0, 231, 204, 101,   0;
1372            149, 193, 188, 137, 199;
1373              0, 222, 182, 114,   0;
1374              0,   0, 217,   0,   0
1375        );
1376        assert_eq!(Mask::from_image(&mask_base, 2, 2), Mask::diamond(2));
1377    }
1378
1379    #[test]
1380    fn test_mask_diamond_3() {
1381        let mask_base = gray_image!(
1382              0,   0,   0, 222,   0,   0,   0;
1383              0,   0, 255, 222, 137,   0,   0;
1384              0, 101, 188, 217, 217, 222,   0;
1385            217, 188, 231, 222, 137, 217, 149;
1386              0, 255, 193, 188, 231, 222,   0;
1387              0,   0, 231, 149, 101,   0,   0;
1388              0,   0,   0, 255,   0,   0,   0
1389        );
1390        assert_eq!(Mask::from_image(&mask_base, 3, 3), Mask::diamond(3));
1391    }
1392
1393    #[test]
1394    fn test_mask_disk_1() {
1395        let mask_base = gray_image!(
1396             0,  31,   0;
1397             2, 219, 173;
1398             0,   7,   0
1399        );
1400        assert_eq!(Mask::from_image(&mask_base, 1, 1), Mask::disk(1));
1401    }
1402
1403    #[test]
1404    fn test_mask_disk_2() {
1405        let mask_base = gray_image!(
1406              0,   0, 255,   0,   0;
1407              0, 231, 204, 101,   0;
1408            149, 193, 188, 137, 199;
1409              0, 222, 182, 114,   0;
1410              0,   0, 217,   0,   0
1411        );
1412        assert_eq!(Mask::from_image(&mask_base, 2, 2), Mask::disk(2));
1413    }
1414
1415    #[test]
1416    fn test_mask_disk_3() {
1417        let mask_base = gray_image!(
1418              0,   0,   0, 222,   0,   0,   0;
1419              0, 149, 255, 222, 137, 101,   0;
1420              0, 101, 188, 217, 217, 222,   0;
1421            217, 188, 231, 222, 137, 217, 149;
1422              0, 255, 193, 188, 231, 222,   0;
1423              0, 137, 231, 149, 101, 188,   0;
1424              0,   0,   0, 255,   0,   0,   0
1425        );
1426        assert_eq!(Mask::from_image(&mask_base, 3, 3), Mask::disk(3));
1427    }
1428
1429    #[test]
1430    fn test_grayscale_dilate_0() {
1431        let image = gray_image!(
1432            217, 188, 101, 222, 137, 101, 222;
1433            231, 204, 255, 222, 137, 222, 255;
1434            193, 101, 188, 217, 217, 222, 188;
1435            217, 188, 231, 222, 137, 217, 149;
1436            193, 255, 193, 188, 231, 222, 188;
1437            217, 188, 231, 149, 101, 188, 149;
1438            101, 204, 222, 255, 193, 255, 182
1439        );
1440        assert_eq!(grayscale_dilate(&image, &Mask::square(0)), image.clone());
1441    }
1442
1443    #[test]
1444    fn test_grayscale_dilate_diamond_1() {
1445        let image = gray_image!(
1446             80,  80,  80,  80,  80,  80,  80;
1447             80,  80,  80,  80,  80, 212,  80;
1448             80,  80,  80,  80, 212, 212,  80;
1449              0,  80,  80,  80,  80,  80,  80;
1450              0,   0,  80,  80,  80,  80,  80;
1451              0,   0,   0,  80,  80,  80,  80;
1452              0,   0,   0,  80,  80,  80,  80
1453        );
1454        let dilated = gray_image!(
1455            80,  80,  80,  80,  80, 212,  80;
1456            80,  80,  80,  80, 212, 212, 212;
1457            80,  80,  80, 212, 212, 212, 212;
1458            80,  80,  80,  80, 212, 212,  80;
1459             0,  80,  80,  80,  80,  80,  80;
1460             0,   0,  80,  80,  80,  80,  80;
1461             0,   0,  80,  80,  80,  80,  80
1462        );
1463        assert_eq!(grayscale_dilate(&image, &Mask::diamond(1)), dilated);
1464    }
1465
1466    #[test]
1467    fn test_grayscale_dilate_diamond_3() {
1468        let image = gray_image!(
1469             80,  80,  80,  80,  80,  80,  80;
1470             80,  80,  80,  80,  80, 212,  80;
1471             80,  80,  80,  80, 212, 212,  80;
1472              0,  80,  80,  80,  80,  80,  80;
1473              0,   0,  80,  80,  80,  80,  80;
1474              0,   0,   0,  80,  80,  80,  80;
1475              0,   0,   0,  80,  80,  80,  80
1476        );
1477        let dilated = gray_image!(
1478            80,  80,  80, 212, 212, 212, 212;
1479            80,  80, 212, 212, 212, 212, 212;
1480            80, 212, 212, 212, 212, 212, 212;
1481            80,  80, 212, 212, 212, 212, 212;
1482            80,  80,  80, 212, 212, 212, 212;
1483            80,  80,  80,  80, 212, 212,  80;
1484            80,  80,  80,  80,  80,  80,  80
1485        );
1486        assert_eq!(grayscale_dilate(&image, &Mask::diamond(3)), dilated);
1487    }
1488
1489    #[test]
1490    fn test_grayscale_dilate_square_1() {
1491        let image = gray_image!(
1492             80,  80,  80,  80,  80,  80,  80;
1493             80,  80,  80,  80,  80, 212,  80;
1494             80,  80,  80,  80, 212, 212,  80;
1495              0,  80,  80,  80,  80,  80,  80;
1496              0,   0,  80,  80,  80,  80,  80;
1497              0,   0,   0,  80,  80,  80,  80;
1498              0,   0,   0,  80,  80,  80,  80
1499        );
1500        let dilated = gray_image!(
1501            80,  80,  80,  80, 212, 212, 212;
1502            80,  80,  80, 212, 212, 212, 212;
1503            80,  80,  80, 212, 212, 212, 212;
1504            80,  80,  80, 212, 212, 212, 212;
1505            80,  80,  80,  80,  80,  80,  80;
1506             0,  80,  80,  80,  80,  80,  80;
1507             0,   0,  80,  80,  80,  80,  80
1508        );
1509        assert_eq!(grayscale_dilate(&image, &Mask::square(1)), dilated);
1510    }
1511
1512    #[test]
1513    fn test_grayscale_dilate_square_3() {
1514        let image = gray_image!(
1515             80,  80,  80,  80,  80,  80,  80;
1516             80,  80,  80,  80,  80, 212,  80;
1517             80,  80,  80,  80, 212, 212,  80;
1518              0,  80,  80,  80,  80,  80,  80;
1519              0,   0,  80,  80,  80,  80,  80;
1520              0,   0,   0,  80,  80,  80,  80;
1521              0,   0,   0,  80,  80,  80,  80
1522        );
1523        let dilated = gray_image!(
1524            80, 212, 212, 212, 212, 212, 212;
1525            80, 212, 212, 212, 212, 212, 212;
1526            80, 212, 212, 212, 212, 212, 212;
1527            80, 212, 212, 212, 212, 212, 212;
1528            80, 212, 212, 212, 212, 212, 212;
1529            80, 212, 212, 212, 212, 212, 212;
1530            80,  80,  80,  80,  80,  80,  80
1531        );
1532        assert_eq!(grayscale_dilate(&image, &Mask::square(3)), dilated);
1533    }
1534
1535    #[test]
1536    fn test_grayscale_dilate_disk_1() {
1537        let image = gray_image!(
1538             80,  80,  80,  80,  80,  80,  80;
1539             80,  80,  80,  80,  80, 212,  80;
1540             80,  80,  80,  80, 212, 212,  80;
1541              0,  80,  80,  80,  80,  80,  80;
1542              0,   0,  80,  80,  80,  80,  80;
1543              0,   0,   0,  80,  80,  80,  80;
1544              0,   0,   0,  80,  80,  80,  80
1545        );
1546        let dilated = gray_image!(
1547            80,  80,  80,  80,  80, 212,  80;
1548            80,  80,  80,  80, 212, 212, 212;
1549            80,  80,  80, 212, 212, 212, 212;
1550            80,  80,  80,  80, 212, 212,  80;
1551             0,  80,  80,  80,  80,  80,  80;
1552             0,   0,  80,  80,  80,  80,  80;
1553             0,   0,  80,  80,  80,  80,  80
1554        );
1555        assert_eq!(grayscale_dilate(&image, &Mask::disk(1)), dilated);
1556    }
1557
1558    #[test]
1559    fn test_grayscale_dilate_disk_3() {
1560        let image = gray_image!(
1561             80,  80,  80,  80,  80,  80,  80;
1562             80,  80,  80,  80,  80, 212,  80;
1563             80,  80,  80,  80, 212, 212,  80;
1564              0,  80,  80,  80,  80,  80,  80;
1565              0,   0,  80,  80,  80,  80,  80;
1566              0,   0,   0,  80,  80,  80,  80;
1567              0,   0,   0,  80,  80,  80,  80
1568        );
1569        let dilated = gray_image!(
1570            80,  80, 212, 212, 212, 212, 212;
1571            80,  80, 212, 212, 212, 212, 212;
1572            80, 212, 212, 212, 212, 212, 212;
1573            80,  80, 212, 212, 212, 212, 212;
1574            80,  80, 212, 212, 212, 212, 212;
1575            80,  80,  80,  80, 212, 212,  80;
1576            80,  80,  80,  80,  80,  80,  80
1577        );
1578        assert_eq!(grayscale_dilate(&image, &Mask::disk(3)), dilated);
1579    }
1580
1581    #[test]
1582    fn test_grayscale_dilate_arbitrary() {
1583        let mask = Mask::from_image(
1584            &gray_image!(
1585                15, 7;
1586                0, 17;
1587                0, 253;
1588                0, 22
1589            ),
1590            1,
1591            2,
1592        );
1593        let image = gray_image!(
1594             80,  80,  80,  80,  80,  80,  80;
1595             80,  80,  80,  80,  80, 212,  80;
1596             80,  80,  80,  80, 212, 212,  80;
1597              0,  80,  80,  80,  80,  80,  80;
1598              0,   0,  80,  80,  80,  80,  80;
1599              0,   0,   0,  80,  80,  80,  80;
1600              0,   0,   0,  80,  80,  80,  80
1601        );
1602        let dilated = gray_image!(
1603            80,  80,  80,  80,  80, 212,  80;
1604            80,  80,  80,  80, 212, 212,  80;
1605            80,  80,  80,  80, 212, 212,  80;
1606            80,  80,  80,  80, 212, 212, 212;
1607            80,  80,  80,  80, 212, 212, 212;
1608             0,  80,  80,  80,  80,  80,  80;
1609             0,   0,  80,  80,  80,  80,  80
1610        );
1611        assert_eq!(grayscale_dilate(&image, &mask), dilated);
1612    }
1613
1614    #[test]
1615    fn test_grayscale_dilate_default_value() {
1616        let mask = Mask::from_image(&gray_image!(), 0, 0);
1617        let image = gray_image!(
1618             80,  80,  80,  80,  80,  80,  80;
1619             80,  80,  80,  80,  80, 212,  80;
1620             80,  80,  80,  80, 212, 212,  80;
1621              0,  80,  80,  80,  80,  80,  80;
1622              0,   0,  80,  80,  80,  80,  80;
1623              0,   0,   0,  80,  80,  80,  80;
1624              0,   0,   0,  80,  80,  80,  80
1625        );
1626        let dilated = gray_image!(
1627            u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN;
1628            u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN;
1629            u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN;
1630            u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN;
1631            u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN;
1632            u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN;
1633            u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN
1634        );
1635        assert_eq!(grayscale_dilate(&image, &mask), dilated);
1636    }
1637
1638    #[test]
1639    fn test_grayscale_erode_0() {
1640        let image = gray_image!(
1641            217, 188, 101, 222, 137, 101, 222;
1642            231, 204, 255, 222, 137, 222, 255;
1643            193, 101, 188, 217, 217, 222, 188;
1644            217, 188, 231, 222, 137, 217, 149;
1645            193, 255, 193, 188, 231, 222, 188;
1646            217, 188, 231, 149, 101, 188, 149;
1647            101, 204, 222, 255, 193, 255, 182
1648        );
1649        assert_eq!(grayscale_erode(&image, &Mask::square(0)), image.clone());
1650    }
1651
1652    #[test]
1653    fn test_grayscale_erode_diamond_1() {
1654        let image = gray_image!(
1655             80,  80,  80, 212, 212, 212, 212;
1656             80,  80,  80,  80, 212, 212, 212;
1657             80,  80,  80,  80, 212, 212, 212;
1658              0,  80,  80,  80,  80, 212, 212;
1659              0,   0,  80,  80,  80,  80,  80;
1660              0,   0,   0,  80,  80,  80,  80;
1661              0,   0,   0,  80,  80,  80,  80
1662        );
1663        let eroded = gray_image!(
1664            80,  80,  80,  80, 212, 212, 212;
1665            80,  80,  80,  80,  80, 212, 212;
1666             0,  80,  80,  80,  80, 212, 212;
1667             0,   0,  80,  80,  80,  80,  80;
1668             0,   0,   0,  80,  80,  80,  80;
1669             0,   0,   0,   0,  80,  80,  80;
1670             0,   0,   0,   0,  80,  80,  80
1671        );
1672        assert_eq!(grayscale_erode(&image, &Mask::diamond(1)), eroded);
1673    }
1674
1675    #[test]
1676    fn test_grayscale_erode_diamond_3() {
1677        let image = gray_image!(
1678            80,  80,  80, 212, 212, 212, 212;
1679            80,  80,  80,  80, 212, 212, 212;
1680            80,  80,  80,  80, 212, 212, 212;
1681             0,  80,  80,  80,  80, 212, 212;
1682             0,   0,  80,  80,  80,  80,  80;
1683             0,   0,   0,  80,  80,  80,  80;
1684             0,   0,   0,  80,  80,  80,  80
1685        );
1686        let eroded = gray_image!(
1687             0,  80,  80,  80,  80,  80, 212;
1688             0,   0,  80,  80,  80,  80,  80;
1689             0,   0,   0,  80,  80,  80,  80;
1690             0,   0,   0,   0,  80,  80,  80;
1691             0,   0,   0,   0,   0,  80,  80;
1692             0,   0,   0,   0,   0,   0,  80;
1693             0,   0,   0,   0,   0,   0,  80
1694        );
1695        assert_eq!(grayscale_erode(&image, &Mask::diamond(3)), eroded);
1696    }
1697
1698    #[test]
1699    fn test_grayscale_erode_square_1() {
1700        let image = gray_image!(
1701            80,  80,  80, 212, 212, 212, 212;
1702            80,  80,  80,  80, 212, 212, 212;
1703            80,  80,  80,  80, 212, 212, 212;
1704             0,  80,  80,  80,  80, 212, 212;
1705             0,   0,  80,  80,  80,  80,  80;
1706             0,   0,   0,  80,  80,  80,  80;
1707             0,   0,   0,  80,  80,  80,  80
1708        );
1709        let eroded = gray_image!(
1710            80,  80,  80,  80,  80, 212, 212;
1711            80,  80,  80,  80,  80, 212, 212;
1712             0,   0,  80,  80,  80,  80, 212;
1713             0,   0,   0,  80,  80,  80,  80;
1714             0,   0,   0,   0,  80,  80,  80;
1715             0,   0,   0,   0,  80,  80,  80;
1716             0,   0,   0,   0,  80,  80,  80
1717        );
1718        assert_eq!(grayscale_erode(&image, &Mask::square(1)), eroded);
1719    }
1720
1721    #[test]
1722    fn test_grayscale_erode_square_3() {
1723        let image = gray_image!(
1724            80,  80,  80, 212, 212, 212, 212;
1725            80,  80,  80,  80, 212, 212, 212;
1726            80,  80,  80,  80, 212, 212, 212;
1727             0,  80,  80,  80,  80, 212, 212;
1728             0,   0,  80,  80,  80,  80,  80;
1729             0,   0,   0,  80,  80,  80,  80;
1730             0,   0,   0,  80,  80,  80,  80
1731        );
1732        let eroded = gray_image!(
1733             0,   0,   0,   0,  80,  80,  80;
1734             0,   0,   0,   0,   0,  80,  80;
1735             0,   0,   0,   0,   0,   0,  80;
1736             0,   0,   0,   0,   0,   0,  80;
1737             0,   0,   0,   0,   0,   0,  80;
1738             0,   0,   0,   0,   0,   0,  80;
1739             0,   0,   0,   0,   0,   0,  80
1740        );
1741        assert_eq!(grayscale_erode(&image, &Mask::square(3)), eroded);
1742    }
1743
1744    #[test]
1745    fn test_grayscale_erode_disk_1() {
1746        let image = gray_image!(
1747             80,  80,  80, 212, 212, 212, 212;
1748             80,  80,  80,  80, 212, 212, 212;
1749             80,  80,  80,  80, 212, 212, 212;
1750              0,  80,  80,  80,  80, 212, 212;
1751              0,   0,  80,  80,  80,  80,  80;
1752              0,   0,   0,  80,  80,  80,  80;
1753              0,   0,   0,  80,  80,  80,  80
1754        );
1755        let eroded = gray_image!(
1756            80,  80,  80,  80, 212, 212, 212;
1757            80,  80,  80,  80,  80, 212, 212;
1758             0,  80,  80,  80,  80, 212, 212;
1759             0,   0,  80,  80,  80,  80,  80;
1760             0,   0,   0,  80,  80,  80,  80;
1761             0,   0,   0,   0,  80,  80,  80;
1762             0,   0,   0,   0,  80,  80,  80
1763        );
1764        assert_eq!(grayscale_erode(&image, &Mask::disk(1)), eroded);
1765    }
1766
1767    #[test]
1768    fn test_grayscale_erode_disk_3() {
1769        let image = gray_image!(
1770            80,  80,  80, 212, 212, 212, 212;
1771            80,  80,  80,  80, 212, 212, 212;
1772            80,  80,  80,  80, 212, 212, 212;
1773             0,  80,  80,  80,  80, 212, 212;
1774             0,   0,  80,  80,  80,  80,  80;
1775             0,   0,   0,  80,  80,  80,  80;
1776             0,   0,   0,  80,  80,  80,  80
1777        );
1778        let eroded = gray_image!(
1779             0,  80,  80,  80,  80,  80, 212;
1780             0,   0,   0,  80,  80,  80,  80;
1781             0,   0,   0,   0,  80,  80,  80;
1782             0,   0,   0,   0,   0,  80,  80;
1783             0,   0,   0,   0,   0,  80,  80;
1784             0,   0,   0,   0,   0,   0,  80;
1785             0,   0,   0,   0,   0,   0,  80
1786        );
1787        assert_eq!(grayscale_erode(&image, &Mask::disk(3)), eroded);
1788    }
1789
1790    #[test]
1791    fn test_grayscale_erode_arbitrary() {
1792        let mask = Mask::from_image(
1793            &gray_image!(
1794                15, 7;
1795                1, 17;
1796                0, 253;
1797                0, 22
1798            ),
1799            1,
1800            2,
1801        );
1802        let image = gray_image!(
1803            80,  80,  80, 212, 212, 212, 212;
1804            80,  80,  80,  80, 212, 212, 212;
1805            80,  80,  80,  80, 212, 212, 212;
1806             0,  80,  80,  80,  80, 212, 212;
1807             0,   0,  80,  80,  80,  80,  80;
1808             0,   0,   0,  80,  80,  80,  80;
1809             0,   0,   0,  80,  80,  80,  80
1810        );
1811        let eroded = gray_image!(
1812            80,  80,  80,  80, 212, 212, 212;
1813            80,  80,  80,  80, 212, 212, 212;
1814             0,  80,  80,  80,  80, 212, 212;
1815             0,   0,  80,  80,  80,  80,  80;
1816             0,   0,   0,  80,  80,  80,  80;
1817             0,   0,   0,  80,  80,  80,  80;
1818             0,   0,   0,   0,  80,  80,  80
1819        );
1820        assert_eq!(grayscale_erode(&image, &mask), eroded);
1821    }
1822
1823    #[test]
1824    fn test_grayscale_erode_default_value() {
1825        let mask = Mask::from_image(&gray_image!(), 0, 0);
1826        let image = gray_image!(
1827             80,  80,  80,  80,  80,  80,  80;
1828             80,  80,  80,  80,  80, 212,  80;
1829             80,  80,  80,  80, 212, 212,  80;
1830              0,  80,  80,  80,  80,  80,  80;
1831              0,   0,  80,  80,  80,  80,  80;
1832              0,   0,   0,  80,  80,  80,  80;
1833              0,   0,   0,  80,  80,  80,  80
1834        );
1835        let dilated = gray_image!(
1836            u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX;
1837            u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX;
1838            u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX;
1839            u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX;
1840            u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX;
1841            u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX;
1842            u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX
1843        );
1844        assert_eq!(grayscale_erode(&image, &mask), dilated);
1845    }
1846
1847    #[test]
1848    fn test_dilate_mut_matches_dilate() {
1849        let mut image = gray_image!(
1850              0,   0,   0,   0,   0;
1851              0,   0,   0,   0,   0;
1852              0,   0, 255,   0,   0;
1853              0,   0,   0,   0,   0;
1854              0,   0,   0,   0,   0
1855        );
1856        let expected = dilate(&image, Norm::LInf, 1);
1857
1858        dilate_mut(&mut image, Norm::LInf, 1);
1859
1860        assert_pixels_eq!(image, expected);
1861    }
1862
1863    #[test]
1864    fn test_erode_mut_matches_erode() {
1865        let mut image = gray_image!(
1866              0,   0,   0,   0,   0;
1867              0, 255, 255, 255,   0;
1868              0, 255, 255, 255,   0;
1869              0, 255, 255, 255,   0;
1870              0,   0,   0,   0,   0
1871        );
1872        let expected = erode(&image, Norm::LInf, 1);
1873
1874        erode_mut(&mut image, Norm::LInf, 1);
1875
1876        assert_pixels_eq!(image, expected);
1877    }
1878
1879    #[test]
1880    fn test_open_removes_small_cross() {
1881        let image = gray_image!(
1882              0,   0,   0,   0,   0;
1883              0,   0, 255,   0,   0;
1884              0, 255, 255, 255,   0;
1885              0,   0, 255,   0,   0;
1886              0,   0,   0,   0,   0
1887        );
1888        let expected = gray_image!(
1889              0,   0,   0,   0,   0;
1890              0,   0,   0,   0,   0;
1891              0,   0,   0,   0,   0;
1892              0,   0,   0,   0,   0;
1893              0,   0,   0,   0,   0
1894        );
1895
1896        assert_pixels_eq!(open(&image, Norm::LInf, 1), expected);
1897    }
1898
1899    #[test]
1900    fn test_open_mut_matches_open() {
1901        let mut image = gray_image!(
1902              0,   0,   0,   0,   0;
1903              0, 255, 255, 255,   0;
1904              0, 255, 255, 255,   0;
1905              0, 255, 255, 255,   0;
1906              0,   0,   0,   0,   0
1907        );
1908        let expected = open(&image, Norm::LInf, 1);
1909
1910        open_mut(&mut image, Norm::LInf, 1);
1911
1912        assert_pixels_eq!(image, expected);
1913    }
1914
1915    #[test]
1916    fn test_close_fills_small_hole() {
1917        let image = gray_image!(
1918            255, 255, 255, 255;
1919            255,   0,   0, 255;
1920            255,   0,   0, 255;
1921            255, 255, 255, 255
1922        );
1923        let expected = gray_image!(
1924            255, 255, 255, 255;
1925            255, 255, 255, 255;
1926            255, 255, 255, 255;
1927            255, 255, 255, 255
1928        );
1929
1930        assert_pixels_eq!(close(&image, Norm::LInf, 1), expected);
1931    }
1932
1933    #[test]
1934    fn test_close_mut_matches_close() {
1935        let mut image = gray_image!(
1936              0,   0,   0,   0,   0;
1937              0,   0,   0,   0,   0;
1938              0,   0, 255,   0,   0;
1939              0,   0,   0,   0,   0;
1940              0,   0,   0,   0,   0
1941        );
1942        let expected = close(&image, Norm::LInf, 1);
1943
1944        close_mut(&mut image, Norm::LInf, 1);
1945
1946        assert_pixels_eq!(image, expected);
1947    }
1948
1949    #[test]
1950    fn test_grayscale_open_expected_output() {
1951        let image = gray_image!(
1952          100,  99,  99,  99, 222,  99;
1953           99,  99,  99, 222, 222, 222;
1954           99,   7,  99,  99, 222,  99;
1955            7,   7,   7,  99,  99,  99;
1956           99,   7,  99,  99,  99,  99
1957        );
1958        let expected = gray_image!(
1959           99,  99,  99,  99,  99,  99;
1960           99,  99,  99,  99,  99,  99;
1961            7,   7,  99,  99,  99,  99;
1962            7,   7,   7,  99,  99,  99;
1963            7,   7,   7,  99,  99,  99
1964        );
1965
1966        assert_pixels_eq!(grayscale_open(&image, &Mask::square(1)), expected);
1967    }
1968
1969    #[test]
1970    fn test_grayscale_close_expected_output() {
1971        let image = gray_image!(
1972           50,  99,  99,  99, 222,  99;
1973           99,  99,  99, 222, 222, 222;
1974           99,   7,  99,  99, 222,  99;
1975            7,   7,   7,  99,  99,  99;
1976           99,   7,  99,  99,  99,  99
1977        );
1978        let expected = gray_image!(
1979           99,  99,  99, 222, 222, 222;
1980           99,  99,  99, 222, 222, 222;
1981           99,  99,  99,  99, 222, 222;
1982           99,  99,  99,  99,  99,  99;
1983           99,  99,  99,  99,  99,  99
1984        );
1985
1986        assert_pixels_eq!(grayscale_close(&image, &Mask::square(1)), expected);
1987    }
1988}
1989
1990#[cfg(not(miri))]
1991#[cfg(test)]
1992mod proptests {
1993    use super::*;
1994    use crate::proptest_utils::{arbitrary_image, arbitrary_image_with};
1995    use proptest::prelude::*;
1996
1997    fn reference_mask_disk(radius: u8) -> Mask {
1998        let range = -(radius as i16)..=(radius as i16);
1999        let elements = range
2000            .clone()
2001            .cartesian_product(range)
2002            .filter(|(y, x)| {
2003                (x.unsigned_abs() as u32).pow(2) + (y.unsigned_abs() as u32).pow(2)
2004                    <= (radius as u32).pow(2)
2005            })
2006            .map(|(y, x)| Point::new(x, y))
2007            .collect();
2008        Mask::new(elements)
2009    }
2010
2011    proptest! {
2012        #[test]
2013        fn proptest_mask_from_white_image(
2014            img in arbitrary_image_with(Just(255), 0..=511, 0..=511),
2015            x in any::<u8>(),
2016            y in any::<u8>(),
2017        ) {
2018            Mask::from_image(&img, x, y);
2019        }
2020
2021        #[test]
2022        fn proptest_mask_from_image(
2023            img in arbitrary_image(0..=511, 0..=511),
2024            x in any::<u8>(),
2025            y in any::<u8>(),
2026        ) {
2027            Mask::from_image(&img, x, y);
2028        }
2029
2030        #[test]
2031        fn proptest_mask_square(radius in any::<u8>()) {
2032            Mask::square(radius);
2033        }
2034
2035        #[test]
2036        fn proptest_mask_diamond(radius in any::<u8>()) {
2037            Mask::diamond(radius);
2038        }
2039
2040        #[test]
2041        fn proptest_mask_disk(radius in any::<u8>()) {
2042            let actual = Mask::disk(radius);
2043            let expected = reference_mask_disk(radius);
2044            prop_assert_eq!(actual, expected);
2045        }
2046    }
2047}
2048
2049#[cfg(not(miri))]
2050#[cfg(test)]
2051mod benches {
2052    use super::*;
2053    use ::test::*;
2054    use image::{GrayImage, Luma};
2055    use std::cmp::{max, min};
2056
2057    fn square() -> GrayImage {
2058        GrayImage::from_fn(500, 500, |x, y| {
2059            if min(x, y) > 100 && max(x, y) < 300 {
2060                Luma([255u8])
2061            } else {
2062                Luma([0u8])
2063            }
2064        })
2065    }
2066
2067    #[bench]
2068    fn bench_erode_l1_5(b: &mut Bencher) {
2069        let image = square();
2070        b.iter(|| {
2071            let dilated = dilate(&image, Norm::L1, 5);
2072            black_box(dilated);
2073        })
2074    }
2075
2076    #[bench]
2077    fn bench_dilate_l2_5(b: &mut Bencher) {
2078        let image = square();
2079        b.iter(|| {
2080            let dilated = dilate(&image, Norm::L2, 5);
2081            black_box(dilated);
2082        })
2083    }
2084
2085    #[bench]
2086    fn bench_dilate_linf_5(b: &mut Bencher) {
2087        let image = square();
2088        b.iter(|| {
2089            let dilated = dilate(&image, Norm::LInf, 5);
2090            black_box(dilated);
2091        })
2092    }
2093
2094    #[bench]
2095    fn bench_grayscale_mask_from_image(b: &mut Bencher) {
2096        let image = GrayImage::from_fn(200, 200, |x, y| Luma([(x + y % 3) as u8]));
2097        b.iter(|| {
2098            let mask = Mask::from_image(&image, 100, 100);
2099            black_box(mask);
2100        })
2101    }
2102
2103    macro_rules! bench_grayscale_mask {
2104        ($name:ident, $f:expr) => {
2105            #[bench]
2106            fn $name(b: &mut Bencher) {
2107                b.iter(|| {
2108                    let mask = $f(100);
2109                    black_box(mask);
2110                })
2111            }
2112        };
2113    }
2114
2115    bench_grayscale_mask!(bench_grayscale_square_mask, Mask::square);
2116    bench_grayscale_mask!(bench_grayscale_diamond_mask, Mask::diamond);
2117    bench_grayscale_mask!(bench_grayscale_disk_mask, Mask::disk);
2118
2119    macro_rules! bench_grayscale_operator {
2120        ($name:ident, $f:expr, $mask:expr, $img_size:expr) => {
2121            #[bench]
2122            fn $name(b: &mut Bencher) {
2123                let image =
2124                    GrayImage::from_fn($img_size, $img_size, |x, y| Luma([(x + y % 3) as u8]));
2125                let mask = $mask;
2126                b.iter(|| {
2127                    let processed = $f(&image, &mask);
2128                    black_box(processed);
2129                })
2130            }
2131        };
2132    }
2133
2134    bench_grayscale_operator!(
2135        bench_grayscale_op_erode_small_image_point,
2136        grayscale_erode,
2137        Mask::diamond(0),
2138        50
2139    );
2140    bench_grayscale_operator!(
2141        bench_grayscale_op_erode_medium_image_point,
2142        grayscale_erode,
2143        Mask::diamond(0),
2144        200
2145    );
2146    bench_grayscale_operator!(
2147        bench_grayscale_op_erode_big_image_point,
2148        grayscale_erode,
2149        Mask::diamond(0),
2150        1000
2151    );
2152    bench_grayscale_operator!(
2153        bench_grayscale_op_erode_small_image_diamond,
2154        grayscale_erode,
2155        Mask::diamond(5),
2156        50
2157    );
2158    bench_grayscale_operator!(
2159        bench_grayscale_op_erode_medium_image_diamond,
2160        grayscale_erode,
2161        Mask::diamond(5),
2162        200
2163    );
2164    bench_grayscale_operator!(
2165        bench_grayscale_op_erode_big_image_diamond,
2166        grayscale_erode,
2167        Mask::diamond(5),
2168        1000
2169    );
2170    bench_grayscale_operator!(
2171        bench_grayscale_op_erode_small_image_large_square,
2172        grayscale_erode,
2173        Mask::square(25),
2174        50
2175    );
2176    bench_grayscale_operator!(
2177        bench_grayscale_op_erode_medium_image_large_square,
2178        grayscale_erode,
2179        Mask::square(25),
2180        200
2181    );
2182
2183    bench_grayscale_operator!(
2184        bench_grayscale_op_dilate_small_image_point,
2185        grayscale_dilate,
2186        Mask::diamond(0),
2187        50
2188    );
2189    bench_grayscale_operator!(
2190        bench_grayscale_op_dilate_medium_image_point,
2191        grayscale_dilate,
2192        Mask::diamond(0),
2193        200
2194    );
2195    bench_grayscale_operator!(
2196        bench_grayscale_op_dilate_big_image_point,
2197        grayscale_dilate,
2198        Mask::diamond(0),
2199        1000
2200    );
2201    bench_grayscale_operator!(
2202        bench_grayscale_op_dilate_small_image_diamond,
2203        grayscale_dilate,
2204        Mask::diamond(5),
2205        50
2206    );
2207    bench_grayscale_operator!(
2208        bench_grayscale_op_dilate_medium_image_diamond,
2209        grayscale_dilate,
2210        Mask::diamond(5),
2211        200
2212    );
2213    bench_grayscale_operator!(
2214        bench_grayscale_op_dilate_big_image_diamond,
2215        grayscale_dilate,
2216        Mask::diamond(5),
2217        1000
2218    );
2219    bench_grayscale_operator!(
2220        bench_grayscale_op_dilate_small_image_large_square,
2221        grayscale_dilate,
2222        Mask::square(25),
2223        50
2224    );
2225    bench_grayscale_operator!(
2226        bench_grayscale_op_dilate_medium_image_large_square,
2227        grayscale_dilate,
2228        Mask::square(25),
2229        200
2230    );
2231}