1use std::{
2 fmt::Display,
3 ops::{Neg, Range},
4 str::FromStr,
5};
6
7use serde::{Deserialize, Serialize};
8
9use super::{
10 core::{
11 clamp_sub_zero, max_from_partial, max_squaredist, min_from_partial, CoordinateBox, Max,
12 Min, Shape,
13 },
14 Calc, OutOfBoundsMode, Point, PtF, PtI, TPtF, TPtI, TPtS,
15};
16use crate::{
17 result::{to_rv, RvError, RvResult},
18 rverr, ShapeI,
19};
20
21pub type BbI = BB<TPtI>;
22pub type BbS = BB<TPtS>;
23pub type BbF = BB<TPtF>;
24
25#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
26pub struct BB<T> {
27 pub x: T,
28 pub y: T,
29 pub w: T,
30 pub h: T,
31}
32
33impl<T> BB<T>
34where
35 T: Calc + CoordinateBox,
36{
37 pub fn from_arr(a: &[T; 4]) -> Self {
39 BB {
40 x: a[0],
41 y: a[1],
42 w: a[2],
43 h: a[3],
44 }
45 }
46
47 pub fn merge(&self, other: Self) -> Self {
48 let x = self.x.min(other.x);
49 let y = self.y.min(other.y);
50 let x_max = self.x_max().max(other.x_max());
51 let y_max = self.y_max().max(other.y_max());
52 BB::from_points((x, y).into(), (x_max, y_max).into())
53 }
54
55 pub fn from_points_iter(points: impl Iterator<Item = Point<T>> + Clone) -> RvResult<Self> {
57 let x_iter = points.clone().map(|p| p.x);
58 let y_iter = points.map(|p| p.y);
59 let min_x = x_iter
60 .clone()
61 .min_by(min_from_partial)
62 .ok_or_else(|| rverr!("empty iterator"))?;
63 let min_y = y_iter
64 .clone()
65 .min_by(min_from_partial)
66 .ok_or_else(|| rverr!("empty iterator"))?;
67 let max_x = x_iter
68 .max_by(max_from_partial)
69 .ok_or_else(|| rverr!("empty iterator"))?;
70 let max_y = y_iter
71 .max_by(max_from_partial)
72 .ok_or_else(|| rverr!("empty iterator"))?;
73 Ok(BB::from_points(
74 Point { x: min_x, y: min_y },
75 Point { x: max_x, y: max_y },
76 ))
77 }
78 pub fn from_vec(points: &[Point<T>]) -> RvResult<Self> {
79 Self::from_points_iter(points.iter().copied())
80 }
81
82 pub fn distance_to_boundary(&self, pos: Point<T>) -> T {
83 let dx = (self.x - pos.x).abs();
84 let dw = ((self.x + self.w) - pos.x).abs();
85 let dy = (self.y - pos.y).abs();
86 let dh = ((self.y + self.h) - pos.y).abs();
87 dx.min(dw).min(dy).min(dh)
88 }
89
90 pub fn split_horizontally(&self, y: T) -> (Self, Self) {
91 let top = BB::from_arr(&[self.x, self.y, self.w, y - self.y]);
92 let btm = BB::from_arr(&[self.x, y, self.w, self.y_max() - y]);
93 (top, btm)
94 }
95 pub fn split_vertically(&self, x: T) -> (Self, Self) {
96 let left = BB::from_arr(&[self.x, self.y, x - self.x, self.h]);
97 let right = BB::from_arr(&[x, self.y, self.x_max() - x, self.h]);
98 (left, right)
99 }
100 #[must_use]
101 pub fn from_shape_int(shape: ShapeI) -> Self {
102 BB {
103 x: T::from(0),
104 y: T::from(0),
105 w: T::from(shape.w),
106 h: T::from(shape.h),
107 }
108 }
109
110 pub fn from_shape(shape: Shape<T>) -> Self {
111 BB {
112 x: T::from(0),
113 y: T::from(0),
114 w: shape.w,
115 h: shape.h,
116 }
117 }
118
119 pub fn y_max(&self) -> T {
120 self.y + self.h - T::size_addon()
122 }
123
124 pub fn x_max(&self) -> T {
125 self.x + self.w - T::size_addon()
127 }
128
129 pub fn intersect(self, other: BB<T>) -> BB<T> {
130 BB::from_points(
131 Point {
132 x: self.x.max(other.x),
133 y: self.y.max(other.y),
134 },
135 Point {
136 x: self.x_max().min(other.x_max()),
137 y: self.y_max().min(other.y_max()),
138 },
139 )
140 }
141
142 pub fn points(&self) -> [Point<T>; 4] {
143 [
144 self.corner(0),
145 self.corner(1),
146 self.corner(2),
147 self.corner(3),
148 ]
149 }
150
151 pub fn intersect_or_self(&self, other: Option<BB<T>>) -> BB<T> {
152 if let Some(other) = other {
153 self.intersect(other)
154 } else {
155 *self
156 }
157 }
158
159 pub fn max_squaredist<'a>(
161 &'a self,
162 other: impl Iterator<Item = Point<T>> + 'a + Clone,
163 ) -> (Point<T>, Point<T>, T) {
164 max_squaredist(self.points_iter(), other)
165 }
166
167 pub fn min_max(&self, axis: usize) -> (T, T) {
168 if axis == 0 {
169 (self.x, self.x + self.w)
170 } else {
171 (self.y, self.y + self.h)
172 }
173 }
174
175 #[allow(clippy::needless_lifetimes)]
180 pub fn points_iter<'a>(&'a self) -> impl Iterator<Item = Point<T>> + 'a + Clone {
181 (0..4).map(|idx| self.corner(idx))
182 }
183
184 pub fn corner(&self, idx: usize) -> Point<T> {
185 let (x, y, w, h) = (self.x, self.y, self.w, self.h);
186 match idx {
187 0 => Point { x, y },
188 1 => Point {
189 x,
190 y: y + h - T::size_addon(),
191 },
192 2 => (x + w - T::size_addon(), y + h - T::size_addon()).into(),
193 3 => (x + w - T::size_addon(), y).into(),
194 _ => panic!("bounding boxes only have 4, {idx} is out of bounds"),
195 }
196 }
197 pub fn opposite_corner(&self, idx: usize) -> Point<T> {
198 self.corner((idx + 2) % 4)
199 }
200
201 pub fn shape(&self) -> Shape<T> {
202 Shape {
203 w: self.w,
204 h: self.h,
205 }
206 }
207
208 pub fn from_points(p1: Point<T>, p2: Point<T>) -> Self {
209 let x_min = p1.x.min(p2.x);
210 let y_min = p1.y.min(p2.y);
211 let x_max = p1.x.max(p2.x);
212 let y_max = p1.y.max(p2.y);
213 Self {
214 x: x_min,
215 y: y_min,
216 w: x_max - x_min + T::size_addon(), h: y_max - y_min + T::size_addon(),
218 }
219 }
220
221 pub fn x_range(&self) -> Range<T> {
222 self.x..(self.x + self.w)
223 }
224
225 pub fn y_range(&self) -> Range<T> {
226 self.y..(self.y + self.h)
227 }
228
229 pub fn center_f(&self) -> (f64, f64)
230 where
231 T: Into<f64>,
232 {
233 (
234 self.w.into() * 0.5 + self.x.into(),
235 self.h.into() * 0.5 + self.y.into(),
236 )
237 }
238
239 pub fn min(&self) -> Point<T> {
240 Point {
241 x: self.x,
242 y: self.y,
243 }
244 }
245
246 pub fn max(&self) -> Point<T> {
247 Point {
248 x: self.x_max(),
249 y: self.y_max(),
250 }
251 }
252
253 pub fn covers_y(&self, y: T) -> bool {
254 self.y_max() >= y && self.y <= y
255 }
256 pub fn covers_x(&self, x: T) -> bool {
257 self.x_max() >= x && self.x <= x
258 }
259
260 pub fn contains<P>(&self, p: P) -> bool
261 where
262 P: Into<Point<T>>,
263 {
264 let p = p.into();
265 self.covers_x(p.x) && self.covers_y(p.y)
266 }
267
268 pub fn contains_bb(&self, other: Self) -> bool {
269 self.contains(other.min()) && self.contains(other.max())
270 }
271
272 pub fn is_contained_in_image(&self, shape: ShapeI) -> bool {
273 self.x + self.w <= shape.w.into() && self.y + self.h <= shape.h.into()
274 }
275
276 pub fn new_shape_checked(
277 x: T,
278 y: T,
279 w: T,
280 h: T,
281 orig_im_shape: ShapeI,
282 mode: OutOfBoundsMode<T>,
283 ) -> Option<Self> {
284 match mode {
285 OutOfBoundsMode::Deny => {
286 if x < T::zero() || y < T::zero() || w < T::one() || h < T::one() {
287 None
288 } else {
289 let bb = Self { x, y, w, h };
290 if bb.is_contained_in_image(orig_im_shape) {
291 Some(bb)
292 } else {
293 None
294 }
295 }
296 }
297 OutOfBoundsMode::Resize(min_bb_shape) => {
298 let bb = Self {
299 x: x.min(clamp_sub_zero(orig_im_shape.w.into(), min_bb_shape.w)),
300 y: y.min(clamp_sub_zero(orig_im_shape.h.into(), min_bb_shape.h)),
301 w: (w + x.min(T::zero())).max(min_bb_shape.w),
302 h: (h + y.min(T::zero())).max(min_bb_shape.h),
303 };
304 let mut bb_resized = bb.intersect(BB::from_shape_int(orig_im_shape));
305 bb_resized.w = bb_resized.w.max(min_bb_shape.w);
306 bb_resized.h = bb_resized.h.max(min_bb_shape.h);
307 Some(bb_resized)
308 }
309 }
310 }
311
312 pub fn has_overlap(&self, other: &Self) -> bool {
313 if self.points_iter().any(|c| other.contains(c)) {
314 true
315 } else {
316 other.points_iter().any(|c| self.contains(c))
317 }
318 }
319
320 pub fn rot90_with_image_ntimes(&self, shape: ShapeI, n: u8) -> Self
321 where
322 T: Neg<Output = T>,
323 {
324 let p_min = self.min().rot90_with_image_ntimes(shape, n);
325 let p_max = self.max().rot90_with_image_ntimes(shape, n);
326 Self::from_points(p_min, p_max)
327 }
328}
329
330impl BbF {
331 #[must_use]
332 pub fn translate(
333 self,
334 x_shift: f64,
335 y_shift: f64,
336 shape: ShapeI,
337 oob_mode: OutOfBoundsMode<f64>,
338 ) -> Option<Self> {
339 let x = self.x + x_shift;
340 let y = self.y + y_shift;
341 Self::new_shape_checked(x, y, self.w, self.h, shape, oob_mode)
342 }
343 #[must_use]
344 pub fn follow_movement(
345 &self,
346 from: PtF,
347 to: PtF,
348 shape: ShapeI,
349 oob_mode: OutOfBoundsMode<f64>,
350 ) -> Option<Self> {
351 let x_shift = to.x - from.x;
352 let y_shift = to.y - from.y;
353 self.translate(x_shift, y_shift, shape, oob_mode)
354 }
355
356 #[must_use]
357 pub fn new_fit_to_image(x: f64, y: f64, w: f64, h: f64, shape: ShapeI) -> Self {
358 let clip = |var: f64, size_bx: f64, size_im: f64| {
359 if var < 0.0 {
360 let size_bx = size_bx + var;
361 (0.0, size_bx.min(size_im))
362 } else {
363 (var, (size_bx + var).min(size_im) - var)
364 }
365 };
366 let (x, w) = clip(x, w, shape.w.into());
367 let (y, h) = clip(y, h, shape.h.into());
368
369 Self::from_arr(&[x, y, w, h])
370 }
371
372 #[must_use]
373 pub fn center_scale(
374 &self,
375 x_factor: f64,
376 y_factor: f64,
377 shape: ShapeI,
378 center: Option<PtF>,
379 ) -> Self {
380 let x = self.x;
381 let y = self.y;
382 let w = self.w;
383 let h = self.h;
384 let c = center.unwrap_or(PtF {
385 x: w * 0.5 + x,
386 y: h * 0.5 + y,
387 });
388 let topleft = (c.x + x_factor * (x - c.x), c.y + y_factor * (y - c.y));
389 let btmright = (
390 c.x + x_factor * (x + w - c.x),
391 c.y + y_factor * (y + h - c.y),
392 );
393 let (x_tl, y_tl) = topleft;
394 let (x_br, y_br) = btmright;
395 let w = x_br - x_tl;
396 let h = y_br - y_tl;
397 let x = x_tl.round();
398 let y = y_tl.round();
399
400 Self::new_fit_to_image(x, y, w, h, shape)
401 }
402
403 #[must_use]
404 pub fn shift_max(&self, x_shift: f64, y_shift: f64, shape: ShapeI) -> Option<Self> {
405 let (w, h) = (self.w + x_shift, self.h + y_shift);
406 Self::new_shape_checked(self.x, self.y, w, h, shape, OutOfBoundsMode::Deny)
407 }
408
409 #[must_use]
410 pub fn shift_min(&self, x_shift: f64, y_shift: f64, shape: ShapeI) -> Option<Self> {
411 let (x, y) = (self.x + x_shift, self.y + y_shift);
412 let (w, h) = (self.w - x_shift, self.h - y_shift);
413 Self::new_shape_checked(x, y, w, h, shape, OutOfBoundsMode::Deny)
414 }
415
416 #[must_use]
417 pub fn all_corners_close(&self, other: BbF) -> bool {
418 fn close_floats(a: f64, b: f64) -> bool {
419 (a - b).abs() < 1e-8
420 }
421 close_floats(self.x, other.x)
422 && close_floats(self.y, other.y)
423 && close_floats(self.w, other.w)
424 && close_floats(self.h, other.h)
425 }
426}
427
428impl From<BbF> for BbI {
429 fn from(box_f: BbF) -> Self {
430 let p_min: PtI = box_f.min().into();
431 let p_max: PtI = box_f.max().into();
432 let x = p_min.x;
433 let y = p_min.y;
434 let x_max = p_max.x - TPtI::size_addon();
435 let y_max = p_max.y - TPtI::size_addon();
436 BbI::from_points((x, y).into(), (x_max, y_max).into())
437 }
438}
439impl From<BbI> for BbF {
440 fn from(box_int: BbI) -> Self {
441 let x = box_int.min().x;
442 let y = box_int.min().y;
443 let x_max = box_int.max().x + TPtI::size_addon();
444 let y_max = box_int.max().y + TPtI::size_addon();
445 BbF::from_points((x, y).into(), (x_max, y_max).into())
446 }
447}
448
449impl From<BbI> for BbS {
450 fn from(bb: BbI) -> Self {
451 BbS::from_points(bb.min().into(), bb.max().into())
452 }
453}
454impl From<BbS> for BbI {
455 fn from(bb: BbS) -> Self {
456 BbI::from_points(bb.min().into(), bb.max().into())
457 }
458}
459
460impl BbI {
461 #[must_use]
462 pub fn expand(&self, x_expand: TPtI, y_expand: TPtI, shape: ShapeI) -> Self {
463 let (x, y) = (
464 self.x.saturating_sub(x_expand),
465 self.y.saturating_sub(y_expand),
466 );
467 let (w, h) = (self.w + 2 * x_expand, self.h + 2 * y_expand);
468 let (w, h) = (w.clamp(1, shape.w), h.clamp(1, shape.h));
469 Self { x, y, w, h }
470 }
471}
472
473impl<T> From<&[T; 4]> for BB<T>
474where
475 T: Calc + CoordinateBox,
476{
477 fn from(a: &[T; 4]) -> Self {
478 Self::from_arr(a)
479 }
480}
481
482impl Display for BbI {
483 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
484 let bb_str = format!("[{}, {}, {} ,{}]", self.x, self.y, self.w, self.h);
485 f.write_str(bb_str.as_str())
486 }
487}
488impl FromStr for BbI {
489 type Err = RvError;
490 fn from_str(s: &str) -> RvResult<Self> {
491 let err_parse = rverr!("could not parse '{}' into a bounding box", s);
492 let mut int_iter = s[1..(s.len() - 1)]
493 .split(',')
494 .map(|cse| cse.trim().parse::<u32>().map_err(to_rv));
495 let x = int_iter.next().ok_or_else(|| err_parse.clone())??;
496 let y = int_iter.next().ok_or_else(|| err_parse.clone())??;
497 let w = int_iter.next().ok_or_else(|| err_parse.clone())??;
498 let h = int_iter.next().ok_or(err_parse)??;
499 Ok(BbI { x, y, w, h })
500 }
501}
502
503#[cfg(test)]
504use crate::PtS;
505
506#[test]
507fn test_rot() {
508 let shape = Shape::new(150, 123);
509 let p_min = PtS { x: 1, y: 3 };
510 let p_max = PtS { x: 6, y: 15 };
511 let bb = BB::from_points(p_min, p_max);
512 for n in 0..6 {
513 let b_rotated = BB::from_points(
514 p_min.rot90_with_image_ntimes(shape, n),
515 p_max.rot90_with_image_ntimes(shape, n),
516 );
517 assert_eq!(b_rotated, bb.rot90_with_image_ntimes(shape, n));
518 }
519 let shape = Shape::new(5, 10);
520 let p_min = PtF { x: 1.0, y: 2.0 };
521 let p_max = PtF { x: 2.0, y: 4.0 };
522 let bb = BB::from_points(p_min, p_max);
523 let p_min = PtF { x: 2.0, y: 4.0 };
524 let p_max = PtF { x: 4.0, y: 3.0 };
525 let bb_ref_1 = BB::from_points(p_min, p_max);
526 assert_eq!(bb.rot90_with_image_ntimes(shape, 1), bb_ref_1);
527}
528
529#[test]
530fn test_expand() {
531 let bb = BbI::from_arr(&[0, 0, 10, 10]).expand(1, 1, Shape::new(10, 10));
532 assert_eq!(bb, BbI::from_arr(&[0, 0, 10, 10]));
533
534 let bb = BbI::from_arr(&[5, 5, 10, 10]).expand(1, 2, Shape::new(20, 20));
535 assert_eq!(bb, BbI::from_arr(&[4, 3, 12, 14]));
536}