#[cfg(feature = "std")]
mod indices;
#[cfg(feature = "std")]
use crate::lock::get_dst_index;
use crate::lock::get_indices;
use crate::{Error, RectU, Surface};
#[cfg(feature = "std")]
use indices::LockedIndices;
pub struct MaskedSurface<'s, S: AsRef<[P]> + AsMut<[P]>, P: Copy + Clone + Sized + Default> {
surface: Surface<'s, S, P>,
mask_color: P,
#[cfg(feature = "std")]
mask: Option<Vec<LockedIndices>>,
}
impl<'s, S: AsRef<[P]> + AsMut<[P]>, P: Copy + Clone + Sized + Default + Eq + PartialEq>
MaskedSurface<'s, S, P>
{
pub const fn new(surface: Surface<'s, S, P>, mask_color: P) -> Self {
Self {
surface,
mask_color,
#[cfg(feature = "std")]
mask: None,
}
}
pub const fn set_mask_color(&mut self, mask_color: P) -> Result<(), Error> {
#[cfg(feature = "std")]
if self.is_locked() {
Err(Error::Locked)
} else {
self.mask_color = mask_color;
Ok(())
}
#[cfg(not(feature = "std"))]
{
self.mask_color = mask_color;
Ok(())
}
}
#[cfg(feature = "std")]
pub fn lock(&mut self) {
if self.is_locked() {
return;
}
self.mask = {
let mut mask = vec![];
for y in 0..self.surface.size.height {
let i0 = self.surface.get_index(0, y);
let i1 = i0 + self.surface.size.width;
if self.surface.buffer.as_ref()[i0..i1]
.iter()
.all(|p| self.should_blit_pixel(*p))
{
mask.push(LockedIndices::Row { start: i0, end: i1 })
} else {
mask.extend((i0..i1).filter_map(|i| {
if self.should_blit_pixel(self.surface.buffer.as_ref()[i]) {
Some(LockedIndices::Pixel(i))
} else {
None
}
}))
}
}
Some(mask)
};
}
#[cfg(feature = "std")]
pub const fn is_locked(&self) -> bool {
self.mask.is_some()
}
#[cfg(feature = "std")]
pub fn unlock(&mut self) {
self.mask = None;
}
pub const fn surface(&self) -> &Surface<'s, S, P> {
&self.surface
}
pub const fn surface_mut(&mut self) -> Result<&mut Surface<'s, S, P>, Error> {
#[cfg(feature = "std")]
if self.is_locked() {
Err(Error::Locked)
} else {
Ok(&mut self.surface)
}
#[cfg(not(feature = "std"))]
Ok(&mut self.surface)
}
pub fn blit<B: AsRef<[P]> + AsMut<[P]>>(
&self,
other: &mut Surface<'s, B, P>,
) -> Result<(), Error> {
let (destination_rect, blit_area) = self.surface.get_blit_params(other.size)?;
#[cfg(feature = "std")]
match self.mask.as_ref() {
Some(mask) => {
self.blit_locked(destination_rect, mask, other);
}
None => {
self.blit_unlocked(destination_rect, blit_area, other);
}
}
#[cfg(not(feature = "std"))]
self.blit_unlocked(destination_rect, blit_area, other);
Ok(())
}
#[cfg(feature = "std")]
fn blit_locked<B: AsRef<[P]> + AsMut<[P]>>(
&self,
destination_rect: RectU,
mask: &[LockedIndices],
other: &mut Surface<'_, B, P>,
) {
let dst_len = other.buffer().len();
mask.iter().for_each(|m| match m {
LockedIndices::Pixel(i) => {
let i = *i;
if let Some(dst_index) =
get_dst_index(i, &destination_rect, dst_len, &self.surface, other)
{
self.blit_pixel(
self.surface.buffer.as_ref()[i],
&mut other.buffer_mut()[dst_index],
);
}
}
LockedIndices::Row { start, end } => {
let src_i0 = *start;
let mut src_i1 = *end;
if let Some(dst_i0) =
get_dst_index(src_i0, &destination_rect, dst_len, &self.surface, other)
{
let mut dst_i1 = dst_i0 + (src_i1 - src_i0);
if dst_i1 >= dst_len {
dst_i1 = dst_len;
src_i1 = src_i0 + (dst_i1 - dst_i0);
}
other.buffer.as_mut()[dst_i0..dst_i1]
.copy_from_slice(&self.surface.buffer.as_ref()[src_i0..src_i1]);
}
}
});
}
fn blit_unlocked<B: AsRef<[P]> + AsMut<[P]>>(
&self,
destination_rect: RectU,
blit_area: RectU,
other: &mut Surface<'_, B, P>,
) {
(0..blit_area.size.height).for_each(|y| {
(0..blit_area.size.width).for_each(|x| {
let (src_index, dst_index) =
get_indices(x, y, &destination_rect, &blit_area, &self.surface, other);
if self.should_blit_pixel(self.surface.buffer.as_ref()[src_index]) {
self.blit_pixel(
self.surface.buffer.as_ref()[src_index],
&mut other.buffer_mut()[dst_index],
);
}
})
});
}
fn should_blit_pixel(&self, pixel: P) -> bool {
pixel != self.mask_color
}
fn blit_pixel(&self, top: P, bottom: &mut P) {
*bottom = top;
}
}
#[cfg(feature = "png")]
#[cfg(test)]
mod tests {
use super::*;
use crate::png::Png;
use crate::{PositionI, Rgb8Surface, Size};
use std::env::current_dir;
const SRC_W: usize = 32;
const SRC_H: usize = 17;
const DST_W: usize = 64;
const DST_H: usize = 64;
#[test]
fn test_blit_mask() {
let position = PositionI { x: 2, y: 12 };
let src_size = Size {
width: SRC_W,
height: SRC_H,
};
let mut dst = Surface::new_filled(
Size {
width: DST_W,
height: DST_H,
},
[0u8, 0, 0],
);
let src_color = [255u8, 255, 255];
let mask_color = [255, 0, 255];
let mut src = Surface::new_filled(src_size, src_color);
src.set_position(position, &dst).unwrap();
for pixel in src.buffer.chunks_exact_mut(3) {
pixel[0] = mask_color;
}
let mut src = MaskedSurface::new(src, mask_color);
src.blit(&mut dst).unwrap();
Rgb8Surface::write_png(
&dst,
current_dir()
.unwrap()
.join("test_output")
.join("mask_unlocked.png"),
)
.unwrap();
src.lock();
src.blit(&mut dst).unwrap();
Rgb8Surface::write_png(
&dst,
current_dir()
.unwrap()
.join("test_output")
.join("mask_locked.png"),
)
.unwrap();
}
}