use crate::error::{OxiGdalError, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Mask {
width: usize,
height: usize,
data: Vec<u64>,
}
impl Mask {
#[inline]
fn words_for(pixel_count: usize) -> usize {
pixel_count.saturating_add(63) / 64
}
#[inline]
fn coords(i: usize) -> (usize, u32) {
(i / 64, (i % 64) as u32)
}
fn clear_tail_bits(&mut self) {
let pixel_count = self.width * self.height;
let tail = pixel_count % 64;
if tail != 0 {
if let Some(last) = self.data.last_mut() {
*last &= (1u64 << tail).wrapping_sub(1);
}
}
}
#[inline]
fn check_dims(&self, other: &Mask) -> Result<()> {
if self.width != other.width || self.height != other.height {
Err(OxiGdalError::InvalidParameter {
parameter: "other",
message: format!(
"Mask dimension mismatch: self={}×{}, other={}×{}",
self.width, self.height, other.width, other.height
),
})
} else {
Ok(())
}
}
}
impl Mask {
#[must_use]
pub fn new(width: usize, height: usize) -> Self {
let words = Self::words_for(width * height);
Self {
width,
height,
data: vec![0u64; words],
}
}
#[must_use]
pub fn new_filled(width: usize, height: usize) -> Self {
let mut mask = Self {
width,
height,
data: vec![!0u64; Self::words_for(width * height)],
};
mask.clear_tail_bits();
mask
}
pub fn from_slice(width: usize, height: usize, values: &[bool]) -> Result<Self> {
let pixel_count = width * height;
if values.len() != pixel_count {
return Err(OxiGdalError::InvalidParameter {
parameter: "values",
message: format!(
"Slice length {} does not match {}×{} = {} pixels",
values.len(),
width,
height,
pixel_count
),
});
}
let mut mask = Self::new(width, height);
for (i, &v) in values.iter().enumerate() {
if v {
let (word, bit) = Self::coords(i);
mask.data[word] |= 1u64 << bit;
}
}
Ok(mask)
}
}
impl Mask {
#[must_use]
#[inline]
pub const fn width(&self) -> usize {
self.width
}
#[must_use]
#[inline]
pub const fn height(&self) -> usize {
self.height
}
#[must_use]
#[inline]
pub fn pixel_count(&self) -> usize {
self.width * self.height
}
#[must_use]
pub fn get(&self, x: usize, y: usize) -> bool {
debug_assert!(
x < self.width,
"x={} out of bounds (width={})",
x,
self.width
);
debug_assert!(
y < self.height,
"y={} out of bounds (height={})",
y,
self.height
);
let i = y * self.width + x;
let (word, bit) = Self::coords(i);
if word < self.data.len() {
(self.data[word] >> bit) & 1 == 1
} else {
false
}
}
pub fn set(&mut self, x: usize, y: usize, value: bool) {
debug_assert!(
x < self.width,
"x={} out of bounds (width={})",
x,
self.width
);
debug_assert!(
y < self.height,
"y={} out of bounds (height={})",
y,
self.height
);
let i = y * self.width + x;
let (word, bit) = Self::coords(i);
if word < self.data.len() {
if value {
self.data[word] |= 1u64 << bit;
} else {
self.data[word] &= !(1u64 << bit);
}
}
}
}
impl Mask {
pub fn fill(&mut self, value: bool) {
let fill_word = if value { !0u64 } else { 0u64 };
for w in &mut self.data {
*w = fill_word;
}
if value {
self.clear_tail_bits();
}
}
pub fn fill_rect(&mut self, x: usize, y: usize, w: usize, h: usize, value: bool) {
let x_end = (x + w).min(self.width);
let y_end = (y + h).min(self.height);
for row in y..y_end {
for col in x..x_end {
self.set(col, row, value);
}
}
}
}
impl Mask {
pub fn and_assign(&mut self, other: &Mask) -> Result<()> {
self.check_dims(other)?;
for (a, b) in self.data.iter_mut().zip(other.data.iter()) {
*a &= b;
}
Ok(())
}
pub fn or_assign(&mut self, other: &Mask) -> Result<()> {
self.check_dims(other)?;
for (a, b) in self.data.iter_mut().zip(other.data.iter()) {
*a |= b;
}
Ok(())
}
pub fn not_in_place(&mut self) {
for w in &mut self.data {
*w = !*w;
}
self.clear_tail_bits();
}
pub fn xor_assign(&mut self, other: &Mask) -> Result<()> {
self.check_dims(other)?;
for (a, b) in self.data.iter_mut().zip(other.data.iter()) {
*a ^= b;
}
Ok(())
}
pub fn and(&self, other: &Mask) -> Result<Mask> {
self.check_dims(other)?;
let data: Vec<u64> = self
.data
.iter()
.zip(other.data.iter())
.map(|(a, b)| a & b)
.collect();
Ok(Mask {
width: self.width,
height: self.height,
data,
})
}
pub fn or(&self, other: &Mask) -> Result<Mask> {
self.check_dims(other)?;
let data: Vec<u64> = self
.data
.iter()
.zip(other.data.iter())
.map(|(a, b)| a | b)
.collect();
Ok(Mask {
width: self.width,
height: self.height,
data,
})
}
#[must_use]
pub fn not(&self) -> Mask {
let mut result = Mask {
width: self.width,
height: self.height,
data: self.data.iter().map(|w| !w).collect(),
};
result.clear_tail_bits();
result
}
}
impl Mask {
#[must_use]
pub fn count_set(&self) -> usize {
self.data.iter().map(|w| w.count_ones() as usize).sum()
}
#[must_use]
pub fn count_unset(&self) -> usize {
self.pixel_count() - self.count_set()
}
#[must_use]
pub fn all_set(&self) -> bool {
self.count_set() == self.pixel_count()
}
#[must_use]
pub fn all_unset(&self) -> bool {
self.data.iter().all(|&w| w == 0)
}
#[must_use]
pub fn any_set(&self) -> bool {
self.data.iter().any(|&w| w != 0)
}
}
impl Mask {
pub fn set_positions(&self) -> impl Iterator<Item = (usize, usize)> + '_ {
SetPositions {
mask: self,
word_idx: 0,
current_word: self.data.first().copied().unwrap_or(0),
pixel_base: 0,
}
}
}
struct SetPositions<'a> {
mask: &'a Mask,
word_idx: usize,
current_word: u64,
pixel_base: usize,
}
impl<'a> Iterator for SetPositions<'a> {
type Item = (usize, usize);
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.current_word != 0 {
let bit = self.current_word.trailing_zeros() as usize;
self.current_word &= self.current_word - 1;
let i = self.pixel_base + bit;
if i < self.mask.pixel_count() {
let x = i % self.mask.width;
let y = i / self.mask.width;
return Some((x, y));
}
continue;
}
self.word_idx += 1;
if self.word_idx >= self.mask.data.len() {
return None;
}
self.pixel_base = self.word_idx * 64;
self.current_word = self.mask.data[self.word_idx];
}
}
}
impl Mask {
pub fn from_nodata_f32(data: &[f32], width: usize, height: usize, nodata: f32) -> Result<Self> {
let pixel_count = width * height;
if data.len() != pixel_count {
return Err(OxiGdalError::InvalidParameter {
parameter: "data",
message: format!(
"Slice length {} ≠ {}×{} = {} pixels",
data.len(),
width,
height,
pixel_count
),
});
}
let nodata_is_nan = nodata.is_nan();
let mut mask = Self::new(width, height);
for (i, &v) in data.iter().enumerate() {
let is_nodata = if nodata_is_nan {
v.is_nan()
} else {
v == nodata
};
if is_nodata {
let (word, bit) = Self::coords(i);
mask.data[word] |= 1u64 << bit;
}
}
Ok(mask)
}
pub fn from_nodata_f64(data: &[f64], width: usize, height: usize, nodata: f64) -> Result<Self> {
let pixel_count = width * height;
if data.len() != pixel_count {
return Err(OxiGdalError::InvalidParameter {
parameter: "data",
message: format!(
"Slice length {} ≠ {}×{} = {} pixels",
data.len(),
width,
height,
pixel_count
),
});
}
let nodata_is_nan = nodata.is_nan();
let mut mask = Self::new(width, height);
for (i, &v) in data.iter().enumerate() {
let is_nodata = if nodata_is_nan {
v.is_nan()
} else {
v == nodata
};
if is_nodata {
let (word, bit) = Self::coords(i);
mask.data[word] |= 1u64 << bit;
}
}
Ok(mask)
}
pub fn from_nodata_i32(data: &[i32], width: usize, height: usize, nodata: i32) -> Result<Self> {
let pixel_count = width * height;
if data.len() != pixel_count {
return Err(OxiGdalError::InvalidParameter {
parameter: "data",
message: format!(
"Slice length {} ≠ {}×{} = {} pixels",
data.len(),
width,
height,
pixel_count
),
});
}
let mut mask = Self::new(width, height);
for (i, &v) in data.iter().enumerate() {
if v == nodata {
let (word, bit) = Self::coords(i);
mask.data[word] |= 1u64 << bit;
}
}
Ok(mask)
}
pub fn from_nodata_u8(data: &[u8], width: usize, height: usize, nodata: u8) -> Result<Self> {
let pixel_count = width * height;
if data.len() != pixel_count {
return Err(OxiGdalError::InvalidParameter {
parameter: "data",
message: format!(
"Slice length {} ≠ {}×{} = {} pixels",
data.len(),
width,
height,
pixel_count
),
});
}
let mut mask = Self::new(width, height);
for (i, &v) in data.iter().enumerate() {
if v == nodata {
let (word, bit) = Self::coords(i);
mask.data[word] |= 1u64 << bit;
}
}
Ok(mask)
}
}
impl Mask {
#[must_use]
pub fn to_bool_vec(&self) -> Vec<bool> {
let n = self.pixel_count();
let mut out = Vec::with_capacity(n);
for i in 0..n {
let (word, bit) = Self::coords(i);
out.push((self.data[word] >> bit) & 1 == 1);
}
out
}
}
impl Mask {
pub fn apply_to_f32(&self, data: &mut [f32], nodata_value: f32) {
let n = self.pixel_count().min(data.len());
for (i, elem) in data.iter_mut().enumerate().take(n) {
let (word, bit) = Self::coords(i);
if word < self.data.len() && (self.data[word] >> bit) & 1 == 1 {
*elem = nodata_value;
}
}
}
pub fn apply_to_f64(&self, data: &mut [f64], nodata_value: f64) {
let n = self.pixel_count().min(data.len());
for (i, elem) in data.iter_mut().enumerate().take(n) {
let (word, bit) = Self::coords(i);
if word < self.data.len() && (self.data[word] >> bit) & 1 == 1 {
*elem = nodata_value;
}
}
}
pub fn apply_to_u8(&self, data: &mut [u8], nodata_value: u8) {
let n = self.pixel_count().min(data.len());
for (i, elem) in data.iter_mut().enumerate().take(n) {
let (word, bit) = Self::coords(i);
if word < self.data.len() && (self.data[word] >> bit) & 1 == 1 {
*elem = nodata_value;
}
}
}
pub fn apply_to_i32(&self, data: &mut [i32], nodata_value: i32) {
let n = self.pixel_count().min(data.len());
for (i, elem) in data.iter_mut().enumerate().take(n) {
let (word, bit) = Self::coords(i);
if word < self.data.len() && (self.data[word] >> bit) & 1 == 1 {
*elem = nodata_value;
}
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used)]
use super::*;
#[test]
fn test_mask_basic_set_get() {
let mut mask = Mask::new(8, 8);
for y in 0..8 {
for x in 0..8 {
assert!(!mask.get(x, y), "({x},{y}) should be unset");
}
}
mask.set(0, 0, true);
mask.set(7, 7, true);
mask.set(3, 5, true);
assert!(mask.get(0, 0));
assert!(mask.get(7, 7));
assert!(mask.get(3, 5));
assert!(!mask.get(1, 1));
assert_eq!(mask.count_set(), 3);
}
#[test]
fn test_mask_fill_true() {
let mut mask = Mask::new(10, 10);
mask.fill(true);
assert_eq!(mask.count_set(), 100);
assert_eq!(mask.count_unset(), 0);
}
#[test]
fn test_mask_fill_false() {
let mut mask = Mask::new_filled(5, 5);
mask.fill(false);
assert_eq!(mask.count_set(), 0);
assert!(mask.all_unset());
}
#[test]
fn test_mask_fill_rect() {
let mut mask = Mask::new(10, 10);
mask.fill_rect(2, 2, 3, 3, true);
assert_eq!(mask.count_set(), 9);
for dy in 0..3 {
for dx in 0..3 {
assert!(mask.get(2 + dx, 2 + dy));
}
}
assert!(!mask.get(1, 2));
assert!(!mask.get(5, 5));
}
#[test]
fn test_mask_and_or_not() {
let mut a = Mask::new(4, 4); a.set(0, 0, true);
a.set(1, 1, true);
let mut b = Mask::new(4, 4); b.set(1, 1, true);
b.set(2, 2, true);
let and = a.and(&b).expect("and should succeed");
assert_eq!(and.count_set(), 1);
assert!(and.get(1, 1));
let or = a.or(&b).expect("or should succeed");
assert_eq!(or.count_set(), 3);
let not_a = a.not();
assert_eq!(not_a.count_set(), 14);
assert!(!not_a.get(0, 0));
assert!(!not_a.get(1, 1));
assert!(not_a.get(0, 1));
}
#[test]
fn test_mask_dimension_mismatch_err() {
let mut a = Mask::new(4, 4);
let b = Mask::new(4, 5);
assert!(a.and_assign(&b).is_err());
assert!(a.or_assign(&b).is_err());
assert!(a.xor_assign(&b).is_err());
assert!(a.and(&b).is_err());
assert!(a.or(&b).is_err());
}
#[test]
fn test_mask_from_slice_roundtrip() {
let values: Vec<bool> = (0..20usize).map(|i| i % 3 == 0).collect();
let mask = Mask::from_slice(4, 5, &values).expect("from_slice");
let back = mask.to_bool_vec();
assert_eq!(back, values);
}
#[test]
fn test_mask_from_slice_length_err() {
assert!(Mask::from_slice(4, 4, &[true; 10]).is_err());
}
#[test]
fn test_mask_from_nodata_f32() {
let nodata = -9999.0f32;
let data = vec![1.0f32, nodata, f32::NAN, 3.0, nodata];
let mask = Mask::from_nodata_f32(&data, 5, 1, nodata).expect("from_nodata_f32");
assert!(!mask.get(0, 0));
assert!(mask.get(1, 0));
assert!(!mask.get(2, 0)); assert!(!mask.get(3, 0));
assert!(mask.get(4, 0));
assert_eq!(mask.count_set(), 2);
}
#[test]
fn test_mask_from_nodata_f32_nan_nodata() {
let data = vec![1.0f32, f32::NAN, 2.0, f32::NAN];
let mask = Mask::from_nodata_f32(&data, 4, 1, f32::NAN).expect("from_nodata_f32 NaN");
assert!(!mask.get(0, 0));
assert!(mask.get(1, 0));
assert!(!mask.get(2, 0));
assert!(mask.get(3, 0));
}
#[test]
fn test_mask_from_nodata_u8_zero() {
let data: Vec<u8> = vec![0, 1, 0, 5, 0];
let mask = Mask::from_nodata_u8(&data, 5, 1, 0).expect("from_nodata_u8");
assert!(mask.get(0, 0));
assert!(!mask.get(1, 0));
assert!(mask.get(2, 0));
assert!(!mask.get(3, 0));
assert!(mask.get(4, 0));
assert_eq!(mask.count_set(), 3);
}
#[test]
fn test_mask_apply_to_f32() {
let mut mask = Mask::new(4, 1);
mask.set(1, 0, true);
mask.set(3, 0, true);
let mut data = vec![1.0f32, 2.0, 3.0, 4.0];
mask.apply_to_f32(&mut data, -9999.0);
assert!((data[0] - 1.0).abs() < 1e-7);
assert!((data[1] - (-9999.0)).abs() < 1e-1);
assert!((data[2] - 3.0).abs() < 1e-7);
assert!((data[3] - (-9999.0)).abs() < 1e-1);
}
#[test]
fn test_mask_apply_to_u8() {
let mut mask = Mask::new(3, 1);
mask.set(0, 0, true);
mask.set(2, 0, true);
let mut data: Vec<u8> = vec![10, 20, 30];
mask.apply_to_u8(&mut data, 0);
assert_eq!(data, vec![0, 20, 0]);
}
#[test]
fn test_mask_count_set_checkerboard() {
let values: Vec<bool> = (0..64usize).map(|i| i % 2 == 0).collect();
let mask = Mask::from_slice(8, 8, &values).expect("from_slice");
assert_eq!(mask.count_set(), 32);
assert_eq!(mask.count_unset(), 32);
}
#[test]
fn test_mask_set_positions_iterator() {
let mut mask = Mask::new(5, 5);
let positions = [(1usize, 0usize), (4, 2), (0, 4)];
for &(x, y) in &positions {
mask.set(x, y, true);
}
let mut collected: Vec<(usize, usize)> = mask.set_positions().collect();
collected.sort_unstable();
let mut expected = positions.to_vec();
expected.sort_unstable();
assert_eq!(collected, expected);
}
#[test]
fn test_mask_all_set_all_unset() {
let empty = Mask::new(0, 0);
assert!(empty.all_set());
assert!(empty.all_unset());
let zeros = Mask::new(10, 10);
assert!(zeros.all_unset());
assert!(!zeros.all_set());
let ones = Mask::new_filled(10, 10);
assert!(ones.all_set());
assert!(!ones.all_unset());
}
#[test]
fn test_mask_large_width_crosses_word_boundary() {
let mut mask = Mask::new(65, 1);
mask.set(63, 0, true); mask.set(64, 0, true); assert!(mask.get(63, 0));
assert!(mask.get(64, 0));
assert!(!mask.get(0, 0));
assert_eq!(mask.count_set(), 2);
}
#[test]
fn test_mask_single_pixel() {
let mut mask = Mask::new(1, 1);
assert!(!mask.get(0, 0));
assert!(mask.all_unset());
mask.set(0, 0, true);
assert!(mask.get(0, 0));
assert!(mask.all_set());
assert_eq!(mask.count_set(), 1);
mask.not_in_place();
assert!(!mask.get(0, 0));
assert!(mask.all_unset());
}
#[test]
fn test_mask_xor_self_is_zero() {
let mut mask = Mask::new(6, 6);
mask.fill_rect(0, 0, 3, 3, true);
let clone = mask.clone();
mask.xor_assign(&clone).expect("xor_assign");
assert!(mask.all_unset());
}
#[test]
fn test_mask_new_filled_all_set() {
let mask = Mask::new_filled(9, 7); assert_eq!(mask.pixel_count(), 63);
assert_eq!(mask.count_set(), 63);
assert!(mask.all_set());
}
#[test]
fn test_mask_not_in_place_tail_invariant() {
let mut mask = Mask::new(9, 7); mask.not_in_place(); assert_eq!(mask.count_set(), 63);
mask.not_in_place(); assert_eq!(mask.count_set(), 0);
}
#[test]
fn test_mask_or_idempotent() {
let mut a = Mask::new(4, 4);
a.fill_rect(0, 0, 2, 2, true);
let b = a.clone();
a.or_assign(&b).expect("or_assign");
assert_eq!(a.count_set(), 4);
}
#[test]
fn test_mask_to_bool_vec_roundtrip() {
let mask = Mask::new_filled(5, 3);
let bv = mask.to_bool_vec();
assert_eq!(bv.len(), 15);
assert!(bv.iter().all(|&b| b));
}
}