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