1use crate::kernels::features::Feature;
10use crate::kernels::geometry::{js_floor, Rect};
11
12pub struct Mask<M: AsRef<[u8]> = Vec<u8>> {
13 pub width: usize,
14 pub height: usize,
15 pub factor: u32,
17 pub data: M,
18}
19
20pub struct Region<M: AsRef<[u8]> = Vec<u8>> {
21 pub rect: Rect,
22 pub exclusions: Vec<Rect>,
23 pub crop: Option<Rect>,
24 pub solid: bool,
25 pub mask: Option<Mask<M>>,
26}
27
28#[inline]
29fn clamp_index(value: i32, len: usize) -> usize {
30 value.clamp(0, len as i32 - 1) as usize
31}
32
33impl<M: AsRef<[u8]>> Region<M> {
34 pub fn contains(&self, x: f64, y: f64, native_width: f64, native_height: f64) -> bool {
35 if !self.rect.contains(x, y) || self.exclusions.iter().any(|r| r.contains(x, y)) {
36 return false;
37 }
38 if self.crop.is_some_and(|c| !c.contains(x, y)) {
39 return false;
40 }
41 if self.solid {
42 return true;
43 }
44 let Some(mask) = &self.mask else {
45 return true;
46 };
47 let (xx, yy) = if mask.factor > 0 {
48 let f = mask.factor as f64;
49 (
50 clamp_index(js_floor(x / f), mask.width),
51 clamp_index(js_floor(y / f), mask.height),
52 )
53 } else {
54 (
55 clamp_index(js_floor(x * mask.width as f64 / native_width), mask.width),
56 clamp_index(
57 js_floor(y * mask.height as f64 / native_height),
58 mask.height,
59 ),
60 )
61 };
62 mask.data.as_ref()[yy * mask.width + xx] != 0
63 }
64}
65
66pub fn filter_features<M: AsRef<[u8]>>(
70 region: &Region<M>,
71 features: &[Feature],
72 factor: f64,
73 native_width: f64,
74 native_height: f64,
75) -> Vec<Feature> {
76 features
77 .iter()
78 .filter(|p| {
79 region.contains(
80 p.x as f64 * factor,
81 p.y as f64 * factor,
82 native_width,
83 native_height,
84 )
85 })
86 .cloned()
87 .collect()
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn mask_lookup_uses_floor_by_factor_and_clamps_the_partial_cell() {
96 let region = Region {
97 rect: Rect {
98 x: 0.0,
99 y: 0.0,
100 width: 10.0,
101 height: 10.0,
102 },
103 exclusions: vec![],
104 crop: None,
105 solid: false,
106 mask: Some(Mask {
107 width: 3,
108 height: 3,
109 factor: 4,
110 data: vec![1, 0, 0, 0, 1, 0, 0, 0, 1],
111 }),
112 };
113 assert!(region.contains(0.0, 0.0, 10.0, 10.0));
114 assert!(!region.contains(4.0, 0.0, 10.0, 10.0));
115 assert!(region.contains(9.0, 9.0, 10.0, 10.0));
116 assert!(!region.contains(10.0, 9.0, 10.0, 10.0));
117 }
118
119 #[test]
120 fn filter_features_scales_by_factor_before_testing_containment() {
121 let region: Region = Region {
122 rect: Rect {
123 x: 0.0,
124 y: 0.0,
125 width: 4.0,
126 height: 10.0,
127 },
128 exclusions: vec![],
129 crop: None,
130 solid: true,
131 mask: None,
132 };
133 let inside = Feature {
134 x: 1,
135 y: 1,
136 score: 0.0,
137 descriptor: [0; crate::kernels::features::DESCRIPTOR_WORDS],
138 };
139 let outside = Feature {
140 x: 3,
141 y: 1,
142 score: 0.0,
143 descriptor: [0; crate::kernels::features::DESCRIPTOR_WORDS],
144 };
145 let kept = filter_features(®ion, &[inside.clone(), outside], 2.0, 10.0, 10.0);
146 assert_eq!(kept, vec![inside]);
147 }
148}