simple-blit 2.0.0

Provides simple blitting from one surface to another with some possible transformations.
Documentation
extern crate alloc;

use self::predefined::Predefined;
use crate::{blit, point, size, GenericSurface, Surface, Transform};
use alloc::format;
use proptest::{
    prelude::prop,
    prop_assert_eq,
    test_runner::{Config, TestRunner},
};

mod predefined {
    use crate::{size, GenericSurface, Transform};

    #[rustfmt::skip]
    const TOP_LEFT: [u8; 9] = [
        1, 2, 3,
        2, 3, 4,
        3, 4, 5,
    ];

    #[rustfmt::skip]
    const TOP_LEFT_2X: [u8; 36] = [
        1, 1, 2, 2, 3, 3,
        1, 1, 2, 2, 3, 3,
        2, 2, 3, 3, 4, 4,
        2, 2, 3, 3, 4, 4,
        3, 3, 4, 4, 5, 5,
        3, 3, 4, 4, 5, 5,
    ];

    #[rustfmt::skip]
    const TOP_RIGHT: [u8; 9] = [
        3, 2, 1,
        4, 3, 2,
        5, 4, 3,
    ];

    #[rustfmt::skip]
    const TOP_RIGHT_2X: [u8; 36] = [
        3, 3, 2, 2, 1, 1,
        3, 3, 2, 2, 1, 1,
        4, 4, 3, 3, 2, 2,
        4, 4, 3, 3, 2, 2,
        5, 5, 4, 4, 3, 3,
        5, 5, 4, 4, 3, 3,
    ];

    #[rustfmt::skip]
    const BOTTOM_LEFT: [u8; 9] = [
        3, 4, 5,
        2, 3, 4,
        1, 2, 3,
    ];

    #[rustfmt::skip]
    const BOTTOM_LEFT_2X: [u8; 36] = [
        3, 3, 4, 4, 5, 5,
        3, 3, 4, 4, 5, 5,
        2, 2, 3, 3, 4, 4,
        2, 2, 3, 3, 4, 4,
        1, 1, 2, 2, 3, 3,
        1, 1, 2, 2, 3, 3,
    ];

    #[rustfmt::skip]
    const BOTTOM_RIGHT: [u8; 9] = [
        5, 4, 3,
        4, 3, 2,
        3, 2, 1,
    ];

    #[rustfmt::skip]
    const BOTTOM_RIGHT_2X: [u8; 36] = [
        5, 5, 4, 4, 3, 3,
        5, 5, 4, 4, 3, 3,
        4, 4, 3, 3, 2, 2,
        4, 4, 3, 3, 2, 2,
        3, 3, 2, 2, 1, 1,
        3, 3, 2, 2, 1, 1,
    ];

    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub enum Predefined {
        TopLeft(bool),
        TopRight(bool),
        BottomLeft(bool),
        BottomRight(bool),
    }

    impl Predefined {
        #[inline]
        pub fn is_scaled(self) -> bool {
            match self {
                Predefined::TopLeft(s) => s,
                Predefined::TopRight(s) => s,
                Predefined::BottomLeft(s) => s,
                Predefined::BottomRight(s) => s,
            }
        }

        pub fn surface(self) -> GenericSurface<&'static [u8], u8> {
            use Predefined::*;

            let (slice, size) = match self {
                TopLeft(false) => (&TOP_LEFT[..], size(3, 3)),
                TopLeft(true) => (&TOP_LEFT_2X[..], size(6, 6)),
                TopRight(false) => (&TOP_RIGHT[..], size(3, 3)),
                TopRight(true) => (&TOP_RIGHT_2X[..], size(6, 6)),
                BottomLeft(false) => (&BOTTOM_LEFT[..], size(3, 3)),
                BottomLeft(true) => (&BOTTOM_LEFT_2X[..], size(6, 6)),
                BottomRight(false) => (&BOTTOM_RIGHT[..], size(3, 3)),
                BottomRight(true) => (&BOTTOM_RIGHT_2X[..], size(6, 6)),
            };

            GenericSurface::new(slice, size).unwrap()
        }

        pub fn transform(self, tr: Transform) -> Self {
            use Predefined::*;
            use Transform::*;

            match tr {
                UpScale { x: 1, y: 1 } => self,
                UpScale { x: 2, y: 2 } if self == TopLeft(false) => TopLeft(true),
                UpScale { x: 2, y: 2 } if self == TopRight(false) => TopRight(true),
                UpScale { x: 2, y: 2 } if self == BottomLeft(false) => BottomRight(true),
                UpScale { x: 2, y: 2 } if self == BottomRight(false) => BottomRight(true),
                UpScale { .. } => panic!("not supported for auto tests"),

                FlipHorizontal if matches!(self, TopLeft(_)) => TopRight(self.is_scaled()),
                FlipHorizontal if matches!(self, TopRight(_)) => TopLeft(self.is_scaled()),
                FlipHorizontal if matches!(self, BottomLeft(_)) => BottomRight(self.is_scaled()),
                FlipHorizontal if matches!(self, BottomRight(_)) => BottomLeft(self.is_scaled()),
                FlipHorizontal => unreachable!(),

                FlipVertical if matches!(self, TopLeft(_)) => BottomLeft(self.is_scaled()),
                FlipVertical if matches!(self, TopRight(_)) => BottomRight(self.is_scaled()),
                FlipVertical if matches!(self, BottomLeft(_)) => TopLeft(self.is_scaled()),
                FlipVertical if matches!(self, BottomRight(_)) => TopRight(self.is_scaled()),
                FlipVertical => unreachable!(),

                FlipBoth if matches!(self, TopLeft(_)) => BottomRight(self.is_scaled()),
                FlipBoth if matches!(self, TopRight(_)) => BottomLeft(self.is_scaled()),
                FlipBoth if matches!(self, BottomLeft(_)) => TopRight(self.is_scaled()),
                FlipBoth if matches!(self, BottomRight(_)) => TopLeft(self.is_scaled()),
                FlipBoth => unreachable!(),

                Rotate90Cw if matches!(self, TopLeft(_)) => TopRight(self.is_scaled()),
                Rotate90Cw if matches!(self, TopRight(_)) => BottomRight(self.is_scaled()),
                Rotate90Cw if matches!(self, BottomRight(_)) => BottomLeft(self.is_scaled()),
                Rotate90Cw if matches!(self, BottomLeft(_)) => TopLeft(self.is_scaled()),
                Rotate90Cw => unreachable!(),

                Rotate90Ccw if matches!(self, TopLeft(_)) => BottomLeft(self.is_scaled()),
                Rotate90Ccw if matches!(self, BottomLeft(_)) => BottomRight(self.is_scaled()),
                Rotate90Ccw if matches!(self, BottomRight(_)) => TopRight(self.is_scaled()),
                Rotate90Ccw if matches!(self, TopRight(_)) => TopLeft(self.is_scaled()),
                Rotate90Ccw => unreachable!(),

                Rotate180 if matches!(self, TopLeft(_)) => BottomRight(self.is_scaled()),
                Rotate180 if matches!(self, TopRight(_)) => BottomLeft(self.is_scaled()),
                Rotate180 if matches!(self, BottomLeft(_)) => TopRight(self.is_scaled()),
                Rotate180 if matches!(self, BottomRight(_)) => TopLeft(self.is_scaled()),
                Rotate180 => unreachable!(),
            }
        }
    }
}

#[test]
fn transforms() {
    let transforms = prop::collection::vec(
        prop::sample::select(
            &[
                Transform::FlipHorizontal,
                Transform::FlipVertical,
                Transform::FlipBoth,
                Transform::Rotate90Cw,
                Transform::Rotate90Ccw,
                Transform::Rotate180,
            ][..],
        ),
        0..=12,
    );

    let sources = prop::sample::select(
        &[
            Predefined::TopLeft(false),
            Predefined::TopRight(false),
            Predefined::BottomLeft(false),
            Predefined::BottomRight(false),
        ][..],
    );

    let mut runner = TestRunner::new(Config::with_cases(10_000));

    let result = runner.run(&(sources, transforms), |(src, transforms)| {
        let desired = transforms
            .iter()
            .copied()
            .fold(src, |src, tr| src.transform(tr))
            .surface();

        let mut dest_array = [0_u8; 9];
        let mut dest_array_scaled = [0_u8; 36];
        let mut dest;

        if desired.surface_size() == size(3, 3) {
            dest = GenericSurface::new(&mut dest_array[..], size(3, 3)).unwrap();
        } else {
            dest = GenericSurface::new(&mut dest_array_scaled[..], size(6, 6)).unwrap();
        }

        blit(&mut dest, src.surface(), &transforms);

        prop_assert_eq!(&*dest, &*desired);

        Ok(())
    });

    match result {
        Ok(()) => {}
        Err(error) => {
            panic!("{error}");
        }
    }
}

#[test]
fn simple() {
    let mut dest = [0_u8; 25];

    let mut dest_buf = GenericSurface::new(&mut dest, size(5, 5)).unwrap();

    let src = [1_u8; 16];

    let src_buf = GenericSurface::new(&src, size(4, 4)).unwrap();

    blit(
        dest_buf.offset_surface_mut(point(1, 1)),
        src_buf.sub_surface(point(0, 0), size(3, 3)),
        Default::default(),
    );

    #[rustfmt::skip]
    let correct: [u8; 25] = [
        0, 0, 0, 0, 0,
        0, 1, 1, 1, 0,
        0, 1, 1, 1, 0,
        0, 1, 1, 1, 0,
        0, 0, 0, 0, 0,
    ];

    assert_eq!(dest, correct);
}

#[test]
fn too_small() {
    let mut dest = [0_u8; 25];

    let dest_buf = GenericSurface::new(&mut dest, size(5, 5)).unwrap();

    let src = [1_u8; 16];

    let src_buf = GenericSurface::new(&src, size(4, 4)).unwrap();

    blit(
        dest_buf,
        src_buf.sub_surface(point(0, 0), size(6, 6)),
        Default::default(),
    );

    #[rustfmt::skip]
    let correct: [u8; 25] = [
        1, 1, 1, 1, 0,
        1, 1, 1, 1, 0,
        1, 1, 1, 1, 0,
        1, 1, 1, 1, 0,
        0, 0, 0, 0, 0,
    ];

    assert_eq!(dest, correct);
}

#[test]
fn test_subsurface() {
    let mut dest = [0_u8; 25];

    let dest_buf = GenericSurface::new(&mut dest, size(5, 5))
        .unwrap()
        .into_sub_surface(point(1, 1), size(2, 2));

    let src = [1_u8; 16];

    let src_buf = GenericSurface::new(&src, size(4, 4)).unwrap();

    blit(dest_buf, src_buf, &[]);

    #[rustfmt::skip]
    let correct: [u8; 25] = [
        0, 0, 0, 0, 0,
        0, 1, 1, 0, 0,
        0, 1, 1, 0, 0,
        0, 0, 0, 0, 0,
        0, 0, 0, 0, 0,
    ];

    assert_eq!(dest, correct);
}