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}