Skip to main content

imageproc/
rect.rs

1//! Basic manipulation of rectangles.
2
3/// A rectangular region of non-zero width and height.
4/// # Examples
5/// ```
6/// use imageproc::rect::Rect;
7/// use imageproc::rect::Region;
8///
9/// // Construct a rectangle with top-left corner at (4, 5), width 6 and height 7.
10/// let rect = Rect::at(4, 5).of_size(6, 7);
11///
12/// // Contains top-left point:
13/// assert_eq!(rect.left(), 4);
14/// assert_eq!(rect.top(), 5);
15/// assert!(rect.contains(rect.left(), rect.top()));
16///
17/// // Contains bottom-right point, at (left + width - 1, top + height - 1):
18/// assert_eq!(rect.right(), 9);
19/// assert_eq!(rect.bottom(), 11);
20/// assert!(rect.contains(rect.right(), rect.bottom()));
21/// ```
22#[derive(Copy, Clone, Debug, PartialEq, Eq)]
23pub struct Rect {
24    left: i32,
25    top: i32,
26    width: u32,
27    height: u32,
28}
29
30/// A geometrical representation of a set of 2D points with coordinate type T.
31pub trait Region<T> {
32    /// Whether this region contains the given point.
33    fn contains(&self, x: T, y: T) -> bool;
34}
35
36impl Rect {
37    /// Reduces possibility of confusing coordinates and dimensions
38    /// when specifying rects.
39    ///
40    /// See the [struct-level documentation](Rect) for examples.
41    pub fn at(x: i32, y: i32) -> RectPosition {
42        RectPosition { left: x, top: y }
43    }
44
45    /// Smallest y-coordinate reached by rect.
46    ///
47    /// See the [struct-level documentation](Rect) for examples.
48    pub fn top(&self) -> i32 {
49        self.top
50    }
51
52    /// Smallest x-coordinate reached by rect.
53    ///
54    /// See the [struct-level documentation](Rect) for examples.
55    pub fn left(&self) -> i32 {
56        self.left
57    }
58
59    /// Greatest y-coordinate reached by rect.
60    ///
61    /// See the [struct-level documentation](Rect) for examples.
62    pub fn bottom(&self) -> i32 {
63        self.top + i32::try_from(self.height).unwrap() - 1
64    }
65
66    /// Greatest x-coordinate reached by rect.
67    ///
68    /// See the [struct-level documentation](Rect) for examples.
69    pub fn right(&self) -> i32 {
70        self.left + i32::try_from(self.width).unwrap() - 1
71    }
72
73    /// Width of rect.
74    pub fn width(&self) -> u32 {
75        self.width
76    }
77
78    /// Height of rect.
79    pub fn height(&self) -> u32 {
80        self.height
81    }
82
83    /// Returns the intersection of self and other, or none if they are are disjoint.
84    ///
85    /// # Examples
86    /// ```
87    /// use imageproc::rect::Rect;
88    /// use imageproc::rect::Region;
89    ///
90    /// // Intersecting a rectangle with itself
91    /// let r = Rect::at(4, 5).of_size(6, 7);
92    /// assert_eq!(r.intersect(r), Some(r));
93    ///
94    /// // Intersecting overlapping but non-equal rectangles
95    /// let r = Rect::at(0, 0).of_size(5, 5);
96    /// let s = Rect::at(1, 4).of_size(10, 12);
97    /// let i = Rect::at(1, 4).of_size(4, 1);
98    /// assert_eq!(r.intersect(s), Some(i));
99    ///
100    /// // Intersecting disjoint rectangles
101    /// let r = Rect::at(0, 0).of_size(5, 5);
102    /// let s = Rect::at(10, 10).of_size(100, 12);
103    /// assert_eq!(r.intersect(s), None);
104    /// ```
105    pub fn intersect(&self, other: Rect) -> Option<Rect> {
106        let left = self.left.max(other.left);
107        let top = self.top.max(other.top);
108        let right = self.right().min(other.right());
109        let bottom = self.bottom().min(other.bottom());
110
111        if right < left || bottom < top {
112            return None;
113        }
114
115        let width = u32::try_from(right - left).unwrap() + 1;
116        let height = u32::try_from(bottom - top).unwrap() + 1;
117        Some(Rect::at(left, top).of_size(width, height))
118    }
119}
120
121impl Region<i32> for Rect {
122    fn contains(&self, x: i32, y: i32) -> bool {
123        self.left <= x && x <= self.right() && self.top <= y && y <= self.bottom()
124    }
125}
126
127impl Region<f32> for Rect {
128    fn contains(&self, x: f32, y: f32) -> bool {
129        self.left as f32 <= x
130            && x <= self.right() as f32
131            && self.top as f32 <= y
132            && y <= self.bottom() as f32
133    }
134}
135
136/// Position of the top left of a rectangle.
137/// Only used when building a [`Rect`].
138#[derive(Copy, Clone, Debug, PartialEq, Eq)]
139pub struct RectPosition {
140    left: i32,
141    top: i32,
142}
143
144impl RectPosition {
145    /// Construct a rectangle from a position and size. Width and height
146    /// are required to be strictly positive.
147    ///
148    /// See the [`Rect`] documentation for examples.
149    pub fn of_size(self, width: u32, height: u32) -> Rect {
150        assert!(width > 0, "width must be strictly positive");
151        assert!(height > 0, "height must be strictly positive");
152        Rect {
153            left: self.left,
154            top: self.top,
155            width,
156            height,
157        }
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::{Rect, Region};
164
165    #[test]
166    #[should_panic]
167    fn test_rejects_empty_rectangle() {
168        Rect::at(1, 2).of_size(0, 1);
169    }
170
171    #[test]
172    fn test_contains_i32() {
173        let r = Rect::at(5, 5).of_size(6, 6);
174        assert!(r.contains(5, 5));
175        assert!(r.contains(10, 10));
176        assert!(!r.contains(10, 11));
177        assert!(!r.contains(11, 10));
178    }
179
180    #[test]
181    fn test_contains_f32() {
182        let r = Rect::at(5, 5).of_size(6, 6);
183        assert!(r.contains(5f32, 5f32));
184        assert!(!r.contains(10.1f32, 10f32));
185    }
186}
187
188#[cfg(not(miri))]
189#[cfg(test)]
190mod proptests {
191    use super::*;
192    use proptest::prelude::*;
193
194    proptest! {
195        #[test]
196        fn proptest_intersect(
197            (x1, y1) in (-50..50, -50..50),
198            (w1, h1) in (1..50u32, 1..50u32),
199
200            (x2, y2) in (-50..50, -50..50),
201            (w2, h2) in (1..50u32, 1..50u32),
202        ) {
203            let rect1 = Rect::at(x1, y1).of_size(w1, h1);
204            let rect2 = Rect::at(x2, y2).of_size(w2, h2);
205
206            if let Some(intersect) = rect1.intersect(rect2) {
207                assert!(intersect.width() > 0);
208                assert!(intersect.height() > 0);
209            };
210        }
211    }
212}