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