rvimage_domain/
lib.rs

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/// shape of the image that fits into the window
116#[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/// shape without scaling to window
126#[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}