Skip to main content

rvimage_domain/
lib.rs

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