1mod bb;
2mod canvas;
3mod core;
4mod line;
5mod polygon;
6pub mod result;
7pub use bb::{BB, BbF, BbI, BbS};
8pub use canvas::{
9 Canvas, access_mask_abs, access_mask_rel, canvases_to_image, mask_to_rle_rowmajor,
10 rle_bb_to_image_colmajor, rle_image_to_bb_colmajor, rle_image_to_bb_rowmajor,
11 rle_to_mask_rowmajor, rle_to_mask_rowmajor_inplace,
12};
13pub use core::{
14 Calc, Circle, CoordinateBox, OutOfBoundsMode, Point, PtF, PtI, PtS, ShapeF, ShapeI, TPtF, TPtI,
15 TPtS, color_with_intensity, dist_lineseg_point, max_from_partial, min_from_partial,
16};
17pub use line::{BrushLine, Line, RenderTargetOrShape, bresenham_iter};
18pub use polygon::Polygon;
19pub use result::{RvError, RvResult, to_rv};
20use serde::{Deserialize, Serialize};
21
22#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
23pub enum GeoFig {
24 BB(BbF),
25 Poly(Polygon),
26}
27
28impl GeoFig {
29 #[must_use]
30 pub fn max_squaredist(&self, other: &Self) -> (PtF, PtF, TPtF) {
31 match self {
32 Self::BB(bb) => match other {
33 GeoFig::BB(bb_other) => bb.max_squaredist(bb_other.points_iter()),
34 GeoFig::Poly(poly_other) => bb.max_squaredist(poly_other.points_iter()),
35 },
36 Self::Poly(poly) => match other {
37 GeoFig::BB(bb_other) => poly.max_squaredist(bb_other.points_iter()),
38 GeoFig::Poly(poly_other) => poly.max_squaredist(poly_other.points_iter()),
39 },
40 }
41 }
42 #[must_use]
43 pub fn has_overlap(&self, other: &BbF) -> bool {
44 match self {
45 Self::BB(bb) => bb.has_overlap(other),
46 Self::Poly(poly) => poly.has_overlap(other),
47 }
48 }
49 pub fn translate(
50 self,
51 p: Point<f64>,
52 shape: ShapeI,
53 oob_mode: OutOfBoundsMode<f64>,
54 ) -> Option<Self> {
55 match self {
56 Self::BB(bb) => bb.translate(p.x, p.y, shape, oob_mode).map(GeoFig::BB),
57 Self::Poly(poly) => poly.translate(p.x, p.y, shape, oob_mode).map(GeoFig::Poly),
58 }
59 }
60 #[must_use]
61 pub fn point(&self, idx: usize) -> PtF {
62 match &self {
63 GeoFig::BB(bb) => bb.corner(idx),
64 GeoFig::Poly(p) => p.points()[idx],
65 }
66 }
67 #[must_use]
68 pub fn follow_movement(
69 self,
70 from: PtF,
71 to: PtF,
72 shape: ShapeI,
73 oob_mode: OutOfBoundsMode<f64>,
74 ) -> Option<Self> {
75 let x_shift = (to.x - from.x) as TPtF;
76 let y_shift = (to.y - from.y) as TPtF;
77 self.translate(
78 Point {
79 x: x_shift,
80 y: y_shift,
81 },
82 shape,
83 oob_mode,
84 )
85 }
86
87 #[must_use]
88 pub fn points(&self) -> Vec<PtF> {
89 match self {
90 GeoFig::BB(bb) => bb.points_iter().collect(),
91 GeoFig::Poly(poly) => poly.points_iter().collect(),
92 }
93 }
94
95 #[must_use]
96 pub fn points_normalized(&self, w: f64, h: f64) -> Vec<PtF> {
97 fn convert(iter: impl Iterator<Item = PtF>, w: f64, h: f64) -> Vec<PtF> {
98 iter.map(|p| Point {
99 x: p.x / w,
100 y: p.y / h,
101 })
102 .collect()
103 }
104 match self {
105 GeoFig::BB(bb) => convert(bb.points_iter(), w, h),
106 GeoFig::Poly(poly) => convert(poly.points_iter(), w, h),
107 }
108 }
109}
110impl Default for GeoFig {
111 fn default() -> Self {
112 Self::BB(BbF::default())
113 }
114}
115
116#[must_use]
118pub fn shape_scaled(shape_unscaled: ShapeF, shape_win: ShapeI) -> (TPtF, TPtF) {
119 let w_ratio = shape_unscaled.w / TPtF::from(shape_win.w);
120 let h_ratio = shape_unscaled.h / TPtF::from(shape_win.h);
121 let ratio = w_ratio.max(h_ratio);
122 let w_new = shape_unscaled.w as TPtF / ratio;
123 let h_new = shape_unscaled.h as TPtF / ratio;
124 (w_new, h_new)
125}
126#[must_use]
128pub fn shape_unscaled(zoom_box: &Option<BbF>, shape_orig: ShapeI) -> ShapeF {
129 zoom_box.map_or(shape_orig.into(), |z| z.shape())
130}
131pub fn pos_transform<F>(
132 pos: PtF,
133 shape_orig: ShapeI,
134 shape_win: ShapeI,
135 zoom_box: &Option<BbF>,
136 transform: F,
137) -> PtF
138where
139 F: Fn(f64, f64, f64, f64) -> f64,
140{
141 let unscaled = shape_unscaled(zoom_box, shape_orig);
142 let (w_scaled, h_scaled) = shape_scaled(unscaled, shape_win);
143
144 let (x_off, y_off) = match zoom_box {
145 Some(c) => (c.x, c.y),
146 _ => (0.0, 0.0),
147 };
148
149 let (x, y) = pos.into();
150 let x_tf = transform(x, w_scaled, unscaled.w, x_off);
151 let y_tf = transform(y, h_scaled, unscaled.h, y_off);
152 (x_tf, y_tf).into()
153}
154
155#[must_use]
156pub fn make_test_bbs() -> Vec<BbF> {
157 let boxes = [
158 BbI {
159 x: 0,
160 y: 0,
161 w: 10,
162 h: 10,
163 },
164 BbI {
165 x: 5,
166 y: 5,
167 w: 10,
168 h: 10,
169 },
170 BbI {
171 x: 9,
172 y: 9,
173 w: 10,
174 h: 10,
175 },
176 ];
177 boxes.iter().map(|bb| (*bb).into()).collect()
178}
179
180pub fn make_test_geos() -> Vec<GeoFig> {
181 make_test_bbs().into_iter().map(GeoFig::BB).collect()
182}
183
184#[test]
185fn test_polygon() {
186 let bbs = make_test_bbs();
187 let poly = Polygon::from(bbs[2]);
188 assert_eq!(poly.enclosing_bb(), bbs[2]);
189 let corners = bbs[0].points_iter().collect::<Vec<_>>();
190 let ebb = BbF::from_vec(&corners).unwrap();
191 let poly = Polygon::from(ebb);
192 assert_eq!(poly.enclosing_bb(), ebb);
193}
194
195#[test]
196fn test_bb() {
197 let bb = BbI {
198 x: 10,
199 y: 10,
200 w: 10,
201 h: 10,
202 };
203 assert!(!bb.contains((20u32, 20u32)));
204 assert!(bb.contains((10u32, 10u32)));
205 assert!(bb.corner(0).equals((10, 10)));
206 assert!(bb.corner(1).equals((10, 19)));
207 assert!(bb.corner(2).equals((19, 19)));
208 assert!(bb.corner(3).equals((19, 10)));
209 assert!(bb.opposite_corner(0).equals((19, 19)));
210 assert!(bb.opposite_corner(1).equals((19, 10)));
211 assert!(bb.opposite_corner(2).equals((10, 10)));
212 assert!(bb.opposite_corner(3).equals((10, 19)));
213 for (c, i) in bb.points_iter().zip(0..4) {
214 assert_eq!(c, bb.corner(i));
215 }
216 let shape = ShapeI::new(100, 100);
217 let bb = <BbI as Into<BbF>>::into(bb);
218 let bb1 = bb.translate(1.0, 1.0, shape, OutOfBoundsMode::Deny);
219 assert_eq!(
220 bb1,
221 Some(
222 BbI {
223 x: 11,
224 y: 11,
225 w: 10,
226 h: 10
227 }
228 .into()
229 )
230 );
231 let shape = ShapeI::new(100, 100);
232 let bb1 = bb.shift_max(1.0, 1.0, shape);
233 assert_eq!(
234 bb1,
235 Some(
236 BbI {
237 x: 10,
238 y: 10,
239 w: 11,
240 h: 11
241 }
242 .into()
243 )
244 );
245 let bb1 = bb.shift_max(100.0, 1.0, shape);
246 assert_eq!(bb1, None);
247 let bb1 = bb.shift_max(-1.0, -2.0, shape);
248 assert_eq!(
249 bb1,
250 Some(
251 BbI {
252 x: 10,
253 y: 10,
254 w: 9,
255 h: 8
256 }
257 .into()
258 )
259 );
260 let bb1 = bb.shift_max(-100.0, -200.0, shape);
261 assert_eq!(bb1, None);
262 let bb_moved = bb
263 .follow_movement(
264 (5, 5).into(),
265 (6, 6).into(),
266 ShapeI::new(100, 100),
267 OutOfBoundsMode::Deny,
268 )
269 .unwrap();
270 assert_eq!(bb_moved, BbI::from_arr(&[11, 11, 10, 10]).into());
271}
272
273#[test]
274fn test_has_overlap() {
275 let bb1 = BbI::from_arr(&[5, 5, 10, 10]);
276 let bb2 = BbI::from_arr(&[5, 5, 10, 10]);
277 assert!(bb1.has_overlap(&bb2) && bb2.has_overlap(&bb1));
278 let bb2 = BbI::from_arr(&[0, 0, 10, 10]);
279 assert!(bb1.has_overlap(&bb2) && bb2.has_overlap(&bb1));
280 let bb2 = BbI::from_arr(&[0, 0, 11, 11]);
281 assert!(bb1.has_overlap(&bb2) && bb2.has_overlap(&bb1));
282 let bb2 = BbI::from_arr(&[2, 2, 5, 5]);
283 assert!(bb1.has_overlap(&bb2) && bb2.has_overlap(&bb1));
284 let bb2 = BbI::from_arr(&[5, 5, 9, 9]);
285 assert!(bb1.has_overlap(&bb2) && bb2.has_overlap(&bb1));
286 let bb2 = BbI::from_arr(&[7, 7, 12, 12]);
287 assert!(bb1.has_overlap(&bb2) && bb2.has_overlap(&bb1));
288 let bb2 = BbI::from_arr(&[17, 17, 112, 112]);
289 assert!(!bb1.has_overlap(&bb2) && !bb2.has_overlap(&bb1));
290 let bb2 = BbI::from_arr(&[17, 17, 112, 112]);
291 assert!(!bb1.has_overlap(&bb2) && !bb2.has_overlap(&bb1));
292 let bb2 = BbI::from_arr(&[17, 3, 112, 112]);
293 assert!(!bb1.has_overlap(&bb2) && !bb2.has_overlap(&bb1));
294 let bb2 = BbI::from_arr(&[3, 17, 112, 112]);
295 assert!(!bb1.has_overlap(&bb2) && !bb2.has_overlap(&bb1));
296}
297
298#[test]
299fn test_max_corner_dist() {
300 let bb1 = BbI::from_arr(&[5, 5, 11, 11]);
301 let bb2 = BbI::from_arr(&[5, 5, 11, 11]);
302 assert_eq!(
303 bb1.max_squaredist(bb2.points_iter()),
304 ((15, 5).into(), (5, 15).into(), 200)
305 );
306 let bb2 = BbI::from_arr(&[6, 5, 11, 11]);
307 assert_eq!(
308 bb1.max_squaredist(bb2.points_iter()),
309 ((5, 15).into(), (16, 5).into(), 221)
310 );
311 let bb2 = BbI::from_arr(&[15, 15, 11, 11]);
312 assert_eq!(
313 bb1.max_squaredist(bb2.points_iter()),
314 ((5, 5).into(), (25, 25).into(), 800)
315 );
316}
317
318#[test]
319fn test_intersect() {
320 let bb = BbI::from_arr(&[10, 15, 20, 10]);
321 assert_eq!(bb.intersect(bb), bb);
322 assert_eq!(
323 bb.intersect(BbI::from_arr(&[5, 7, 10, 10])),
324 BbI::from_arr(&[10, 15, 5, 2])
325 );
326 assert_eq!(bb.intersect_or_self(None), bb);
327 assert_eq!(
328 bb.intersect_or_self(Some(BbI::from_arr(&[5, 7, 10, 10]))),
329 BbI::from_arr(&[10, 15, 5, 2])
330 );
331}
332
333#[test]
334fn test_into() {
335 let pt: PtI = (10, 20).into();
336 assert_eq!(pt, PtI { x: 10, y: 20 });
337 let pt: PtF = (10i32, 20i32).into();
338 assert_eq!(pt, PtF { x: 10.0, y: 20.0 });
339 {
340 let box_int = BbI::from_arr(&[1, 2, 5, 6]);
341 let box_f: BbF = box_int.into();
342 assert_eq!(box_int, box_f.into());
343 }
344 {
345 let box_f = BbF::from_arr(&[23.0, 2.0, 15., 31.]);
346 let box_int: BbI = box_f.into();
347 assert_eq!(box_int, box_f.into());
348 }
349}