use crate::error::Error;
use crate::rect::{RectI, RectU};
use crate::{PositionI, PositionU, Size};
#[cfg(not(feature = "std"))]
use core::marker::PhantomData;
#[cfg(feature = "std")]
use std::marker::PhantomData;
#[cfg(feature = "std")]
mod type_aliases {
use super::Surface;
pub type L8Surface<'s> = Surface<'s, Vec<u8>, u8>;
pub type La8Surface<'s> = Surface<'s, Vec<[u8; 2]>, [u8; 2]>;
pub type Rgb8Surface<'s> = Surface<'s, Vec<[u8; 3]>, [u8; 3]>;
pub type Rgba8Surface<'s> = Surface<'s, Vec<[u8; 4]>, [u8; 4]>;
pub type L32Surface<'s> = Surface<'s, Vec<f32>, f32>;
pub type La32Surface<'s> = Surface<'s, Vec<[f32; 2]>, [f32; 2]>;
pub type Rgba32Surface<'s> = Surface<'s, Vec<[f32; 4]>, [f32; 4]>;
}
#[cfg(feature = "std")]
pub use type_aliases::*;
pub struct Surface<'s, S: AsRef<[P]> + AsMut<[P]>, P: Copy + Clone + Sized + Default> {
pub(crate) size: Size,
pub(crate) buffer: S,
pub(crate) destination_rect: Option<RectU>,
pub(crate) blit_area: Option<RectU>,
pub(crate) _p: PhantomData<&'s P>,
}
#[cfg(feature = "std")]
impl<P: Copy + Clone + Sized + Default> Surface<'_, Vec<P>, P> {
pub fn new(size: Size) -> Self {
Self {
size,
buffer: vec![P::default(); size.width * size.height],
destination_rect: None,
blit_area: None,
_p: PhantomData,
}
}
pub fn new_filled(size: Size, color: P) -> Self {
Self {
size,
buffer: vec![color; size.width * size.height],
destination_rect: None,
blit_area: None,
_p: PhantomData,
}
}
}
impl<'s, P: Copy + Clone + Sized + Default> Surface<'s, &'s mut [P], P> {
pub fn new(size: Size, buffer: &'s mut [P]) -> Result<Self, Error> {
let len = buffer.len();
if size.width * size.height == len {
Ok(Self {
size,
buffer,
destination_rect: None,
blit_area: None,
_p: PhantomData,
})
} else {
Err(Error::InvalidSize { len, size })
}
}
}
impl<S: AsRef<[P]> + AsMut<[P]>, P: Copy + Clone + Sized + Default> Surface<'_, S, P> {
pub fn blit<B: AsRef<[P]> + AsMut<[P]>>(
&self,
other: &mut Surface<'_, B, P>,
) -> Result<(), Error> {
let (destination_rect, blit_area) = self.get_blit_params(other.size)?;
(0..blit_area.size.height).for_each(|src_y| {
let src_index = self.get_index(
blit_area.position.x, src_y + blit_area.position.y, );
let dst_index = other.get_index(
destination_rect.position.x, src_y + destination_rect.position.y, );
other.buffer.as_mut()[dst_index..dst_index + blit_area.size.width].copy_from_slice(
&self.buffer.as_ref()[src_index..src_index + blit_area.size.width],
);
});
Ok(())
}
pub fn fill(&mut self, color: P) {
self.buffer.as_mut().fill(color);
}
pub const fn set_position<B: AsRef<[P]> + AsMut<[P]>>(
&mut self,
position: PositionI,
destination: &Surface<'_, B, P>,
) -> Result<RectU, Error> {
let rect = RectI {
position,
size: self.size,
};
let destination_rect = RectI::from_size(destination.size);
match rect.clip(destination_rect) {
Some(rect) => {
self.destination_rect = Some(rect);
Ok(rect)
}
None => Err(Error::InvalidDestinationRect(rect, destination_rect)),
}
}
pub const fn get_position(&self) -> Option<PositionU> {
match self.destination_rect {
Some(rect) => Some(rect.position),
None => None,
}
}
pub const fn set_area(&mut self, area: Option<RectI>) -> Result<Option<RectU>, Error> {
match self.destination_rect {
Some(destination_rect) => match area {
Some(area) => {
let rect = RectI::from_size(destination_rect.size);
match area.clip(rect) {
Some(area) => {
self.blit_area = Some(area);
Ok(self.blit_area)
}
None => Err(Error::InvalidArea(area)),
}
}
None => Ok(None),
},
None => Err(Error::AreaBeforePosition),
}
}
pub fn rows(&self) -> impl Iterator<Item = &[P]> {
self.buffer.as_ref().chunks_exact(self.size.width)
}
pub fn rows_mut(&mut self) -> impl Iterator<Item = &mut [P]> {
self.buffer.as_mut().chunks_exact_mut(self.size.width)
}
pub fn get_pixel_checked(&self, position: PositionU) -> Result<P, Error> {
if position.x < self.size.width && position.y < self.size.height {
Ok(self.get_pixel_unchecked(position))
} else {
Err(Error::PixelPosition {
position,
size: self.size,
})
}
}
pub fn get_pixel_unchecked(&self, position: PositionU) -> P {
let index = self.get_index(position.x, position.y);
self.buffer.as_ref()[index]
}
pub fn set_pixel_checked(&mut self, position: PositionU, color: P) -> Result<(), Error> {
if position.x < self.size.width && position.y < self.size.height {
self.set_pixel_unchecked(position, color);
Ok(())
} else {
Err(Error::PixelPosition {
position,
size: self.size,
})
}
}
pub fn set_pixel_unchecked(&mut self, position: PositionU, color: P) {
let index = self.get_index(position.x, position.y);
self.buffer.as_mut()[index] = color;
}
pub const fn get_index(&self, x: usize, y: usize) -> usize {
x + y * self.size.width
}
pub const fn reset(&mut self) {
self.blit_area = None;
self.destination_rect = None;
}
pub const fn get_size(&self) -> Size {
self.size
}
pub fn buffer(&self) -> &[P] {
self.buffer.as_ref()
}
pub fn buffer_mut(&mut self) -> &mut [P] {
self.buffer.as_mut()
}
pub(crate) fn get_blit_params(&self, destination_size: Size) -> Result<(RectU, RectU), Error> {
match self.destination_rect {
Some(destination_rect) => {
if destination_rect.overlaps(&RectU::from_size(destination_size)) {
let blit_area = match self.blit_area {
Some(rect) => rect,
None => RectU {
position: PositionU::ZERO,
size: destination_rect.size,
},
};
Ok((destination_rect, blit_area))
} else {
Err(Error::NoOverlap)
}
}
None => Err(Error::NoDestinationRect),
}
}
}
#[cfg(feature = "bytes")]
impl<
S: AsRef<[P]> + AsMut<[P]>,
P: Copy + Clone + Sized + Default + bytemuck::Zeroable + bytemuck::Pod,
> Surface<'_, S, P>
{
pub fn bytes(&self) -> &[u8] {
bytemuck::cast_slice::<P, u8>(self.buffer())
}
pub fn bytes_mut(&mut self) -> &mut [u8] {
bytemuck::cast_slice_mut::<P, u8>(self.buffer_mut())
}
}
#[cfg(feature = "std")]
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "png")]
use crate::png::Png;
#[cfg(feature = "png")]
use std::env::current_dir;
#[cfg(feature = "png")]
use std::io::Cursor;
const SRC_W: usize = 32;
const SRC_H: usize = 17;
const DST_W: usize = 64;
const DST_H: usize = 64;
#[cfg(feature = "png")]
#[test]
fn test_blit() {
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 mut src = Surface::new_filled(src_size, [255u8, 255, 255]);
src.set_position(position, &dst).unwrap();
let rect = src.destination_rect.unwrap();
assert_eq!(rect.position, PositionU { x: 2, y: 12 });
assert_eq!(rect.size, src_size);
src.blit(&mut dst).unwrap();
Rgb8Surface::write_png(
&dst,
current_dir().unwrap().join("test_output").join("blit.png"),
)
.unwrap();
}
#[cfg(feature = "png")]
#[test]
fn test_clip() {
blit_clipped("clip_positive.png", DST_W as isize - 12, 16);
blit_clipped("clip_negative.png", -8, -8);
}
#[test]
fn test_area() {
let position = PositionI { x: 2, y: 12 };
let src_size = Size::new(SRC_W, SRC_H);
let dst = Surface::new_filled(Size::new(DST_W, DST_H), [0u8, 0, 0]);
let mut src = Surface::new_filled(src_size, [255u8, 255, 255]);
src.set_position(position, &dst).unwrap();
let size = Size::new(5, 5);
let area = src
.set_area(Some(RectI {
position: PositionI::ZERO,
size,
}))
.unwrap()
.unwrap();
assert_eq!(area.position, PositionU::ZERO);
assert_eq!(area.size, size);
let size = Size::new(70, 80);
let area = src
.set_area(Some(RectI {
position: PositionI::ZERO,
size,
}))
.unwrap()
.unwrap();
assert_eq!(area.position, PositionU::ZERO);
assert_eq!(area.size, src.size);
let position = PositionI::new(6, 8);
let area = src
.set_area(Some(RectI { position, size }))
.unwrap()
.unwrap();
assert_eq!(
area.position,
PositionU {
x: position.x.cast_unsigned(),
y: position.y.cast_unsigned(),
}
);
assert_eq!(
area.size,
Size {
width: SRC_W - position.x.cast_unsigned(),
height: SRC_H - position.y.cast_unsigned(),
}
);
let position = PositionI::new(-5, -5);
let area = src
.set_area(Some(RectI { position, size }))
.unwrap()
.unwrap();
assert_eq!(area.position, PositionU::ZERO);
assert_eq!(area.size, src.size);
let position = PositionI::new(-50, -5);
assert!(src.set_area(Some(RectI { position, size })).is_err());
}
#[cfg(feature = "png")]
fn blit_clipped(name: &str, x: isize, y: isize) {
let src_size = Size::new(SRC_W, SRC_H);
let mut dst = Surface::new_filled(Size::new(DST_W, DST_H), [0u8, 0, 0]);
let mut src = Surface::new_filled(src_size, [0u8, 255, 255]);
src.set_position(PositionI { x, y }, &dst).unwrap();
src.blit(&mut dst).unwrap();
Rgb8Surface::write_png(&dst, current_dir().unwrap().join("test_output").join(name))
.unwrap();
}
#[cfg(feature = "png")]
#[test]
fn test_src_area() {
const D: usize = 128;
const SIZE: Size = Size::new(D, D);
let mut dst = Rgb8Surface::new_filled(SIZE, [255, 255, 255]);
let mut src =
Rgb8Surface::read_png(Cursor::new(include_bytes!("../test_images/text.png"))).unwrap();
src.set_position(PositionI::new(12, 13), &dst).unwrap();
src.set_area(Some(RectI {
position: PositionI::new(20, 3),
size: Size::new(50, 70),
}))
.unwrap();
src.blit(&mut dst).unwrap();
Rgb8Surface::write_png(
&dst,
current_dir()
.unwrap()
.join("test_output")
.join("clipped_text.png"),
)
.unwrap();
}
}