use crate::kernels::features::Feature;
use crate::kernels::geometry::{js_floor, Rect};
pub struct Mask<M: AsRef<[u8]> = Vec<u8>> {
pub width: usize,
pub height: usize,
pub factor: u32,
pub data: M,
}
pub struct Region<M: AsRef<[u8]> = Vec<u8>> {
pub rect: Rect,
pub exclusions: Vec<Rect>,
pub crop: Option<Rect>,
pub solid: bool,
pub mask: Option<Mask<M>>,
}
#[inline]
fn clamp_index(value: i32, len: usize) -> usize {
value.clamp(0, len as i32 - 1) as usize
}
impl<M: AsRef<[u8]>> Region<M> {
pub fn contains(&self, x: f64, y: f64, native_width: f64, native_height: f64) -> bool {
if !self.rect.contains(x, y) || self.exclusions.iter().any(|r| r.contains(x, y)) {
return false;
}
if self.crop.is_some_and(|c| !c.contains(x, y)) {
return false;
}
if self.solid {
return true;
}
let Some(mask) = &self.mask else {
return true;
};
let (xx, yy) = if mask.factor > 0 {
let f = mask.factor as f64;
(
clamp_index(js_floor(x / f), mask.width),
clamp_index(js_floor(y / f), mask.height),
)
} else {
(
clamp_index(js_floor(x * mask.width as f64 / native_width), mask.width),
clamp_index(
js_floor(y * mask.height as f64 / native_height),
mask.height,
),
)
};
mask.data.as_ref()[yy * mask.width + xx] != 0
}
}
pub fn filter_features<M: AsRef<[u8]>>(
region: &Region<M>,
features: &[Feature],
factor: f64,
native_width: f64,
native_height: f64,
) -> Vec<Feature> {
features
.iter()
.filter(|p| {
region.contains(
p.x as f64 * factor,
p.y as f64 * factor,
native_width,
native_height,
)
})
.cloned()
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mask_lookup_uses_floor_by_factor_and_clamps_the_partial_cell() {
let region = Region {
rect: Rect {
x: 0.0,
y: 0.0,
width: 10.0,
height: 10.0,
},
exclusions: vec![],
crop: None,
solid: false,
mask: Some(Mask {
width: 3,
height: 3,
factor: 4,
data: vec![1, 0, 0, 0, 1, 0, 0, 0, 1],
}),
};
assert!(region.contains(0.0, 0.0, 10.0, 10.0));
assert!(!region.contains(4.0, 0.0, 10.0, 10.0));
assert!(region.contains(9.0, 9.0, 10.0, 10.0));
assert!(!region.contains(10.0, 9.0, 10.0, 10.0));
}
#[test]
fn filter_features_scales_by_factor_before_testing_containment() {
let region: Region = Region {
rect: Rect {
x: 0.0,
y: 0.0,
width: 4.0,
height: 10.0,
},
exclusions: vec![],
crop: None,
solid: true,
mask: None,
};
let inside = Feature {
x: 1,
y: 1,
score: 0.0,
descriptor: [0; crate::kernels::features::DESCRIPTOR_WORDS],
};
let outside = Feature {
x: 3,
y: 1,
score: 0.0,
descriptor: [0; crate::kernels::features::DESCRIPTOR_WORDS],
};
let kept = filter_features(®ion, &[inside.clone(), outside], 2.0, 10.0, 10.0);
assert_eq!(kept, vec![inside]);
}
}