From 48a76a59e05f9725de0f1d1f61fa8546ef46db32 Mon Sep 17 00:00:00 2001 From: gered Date: Sun, 26 Mar 2023 13:15:56 -0400 Subject: [PATCH] add visual tests for the new blending RgbaBitmap blit methods note that we're not testing all of the different blend combinations for each blit method. specific BlendFunction visual tests will be added --- ggdt/tests/graphics_rgba.rs | 440 ++++++++++++++++++ .../tests/ref/rgba/blended_rotozoom_blits.png | Bin 0 -> 6955 bytes .../blended_rotozoom_transparent_blits.png | Bin 0 -> 6955 bytes ggdt/tests/ref/rgba/blended_solid_blits.png | Bin 0 -> 5375 bytes .../ref/rgba/blended_solid_flipped_blits.png | Bin 0 -> 5530 bytes .../ref/rgba/blended_transparent_blits.png | Bin 0 -> 5692 bytes .../blended_transparent_flipped_blits.png | Bin 0 -> 5772 bytes 7 files changed, 440 insertions(+) create mode 100644 ggdt/tests/ref/rgba/blended_rotozoom_blits.png create mode 100644 ggdt/tests/ref/rgba/blended_rotozoom_transparent_blits.png create mode 100644 ggdt/tests/ref/rgba/blended_solid_blits.png create mode 100644 ggdt/tests/ref/rgba/blended_solid_flipped_blits.png create mode 100644 ggdt/tests/ref/rgba/blended_transparent_blits.png create mode 100644 ggdt/tests/ref/rgba/blended_transparent_flipped_blits.png diff --git a/ggdt/tests/graphics_rgba.rs b/ggdt/tests/graphics_rgba.rs index 344162c..d42a5cf 100644 --- a/ggdt/tests/graphics_rgba.rs +++ b/ggdt/tests/graphics_rgba.rs @@ -17,6 +17,18 @@ fn setup() -> RgbaBitmap { RgbaBitmap::new(SCREEN_WIDTH, SCREEN_HEIGHT).unwrap() } +fn setup_for_blending() -> RgbaBitmap { + let (texture, _) = RgbaBitmap::load_file(test_assets_file(Path::new("texture.lbm")).as_path()).unwrap(); + let mut screen = RgbaBitmap::new(SCREEN_WIDTH, SCREEN_HEIGHT).unwrap(); + //screen.clear(LIGHTER_BACKGROUND); + for y in 0..(SCREEN_HEIGHT as f32 / texture.height() as f32).ceil() as i32 { + for x in 0..(SCREEN_WIDTH as f32 / texture.width() as f32).ceil() as i32 { + screen.blit(RgbaBlitMethod::Solid, &texture, x * texture.width() as i32, y * texture.height() as i32); + } + } + screen +} + fn verify_visual(screen: &RgbaBitmap, source: &Path) -> bool { let (source_bmp, _) = RgbaBitmap::load_file(source).unwrap(); *screen == source_bmp @@ -366,6 +378,40 @@ fn generate_bitmap(width: i32, height: i32) -> RgbaBitmap { bitmap } +fn generate_bitmap_with_varied_alpha(width: i32, height: i32) -> RgbaBitmap { + let x_third = width / 3; + let y_third = height / 3; + + let mut bitmap = RgbaBitmap::new(width as u32, height as u32).unwrap(); + bitmap.clear(0); // alpha=0 + + bitmap.filled_rect(0, 0, x_third, y_third, 0x330000aa); + bitmap.filled_rect(x_third * 2 + 1, y_third * 2 + 1, width - 1, height - 1, 0x6600aa00); + bitmap.filled_rect(0, y_third * 2 + 1, x_third, height - 1, 0x9900aaaa); + bitmap.filled_rect(x_third * 2 + 1, 0, width - 1, y_third, 0xccaa0000); + bitmap.filled_rect(x_third, y_third, x_third * 2 + 1, y_third * 2 + 1, COLOR_MAGENTA); + bitmap.rect(0, 0, width - 1, height - 1, COLOR_BROWN); + + bitmap +} + +fn generate_solid_bitmap_with_varied_alpha(width: i32, height: i32) -> RgbaBitmap { + let x_third = width / 3; + let y_third = height / 3; + + let mut bitmap = RgbaBitmap::new(width as u32, height as u32).unwrap(); + bitmap.clear(to_argb32(255, 0, 0, 0)); + + bitmap.filled_rect(0, 0, x_third, y_third, 0x330000aa); + bitmap.filled_rect(x_third * 2 + 1, y_third * 2 + 1, width - 1, height - 1, 0x6600aa00); + bitmap.filled_rect(0, y_third * 2 + 1, x_third, height - 1, 0x9900aaaa); + bitmap.filled_rect(x_third * 2 + 1, 0, width - 1, y_third, 0xccaa0000); + bitmap.filled_rect(x_third, y_third, x_third * 2 + 1, y_third * 2 + 1, COLOR_MAGENTA); + bitmap.rect(0, 0, width - 1, height - 1, COLOR_BROWN); + + bitmap +} + #[test] fn solid_blits() { use RgbaBlitMethod::*; @@ -433,6 +479,72 @@ fn solid_blits() { assert!(verify_visual(&screen, &path), "bitmap differs from source image: {:?}", path); } +#[test] +fn blended_solid_blits() { + use RgbaBlitMethod::*; + + let mut screen = setup_for_blending(); + + let bmp16 = generate_solid_bitmap_with_varied_alpha(16, 16); + let bmp12 = generate_solid_bitmap_with_varied_alpha(12, 12); + let bmp21 = generate_solid_bitmap_with_varied_alpha(21, 21); + let bmp3 = generate_solid_bitmap_with_varied_alpha(3, 3); + + let x = 40; + let y = 20; + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, x + 16, y + 48); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp12, x + 80, y + 48); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp21, x + 144, y + 48); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp3, x + 208, y + 48); + + let x = 40; + let y = 110; + unsafe { + screen.blit_unchecked(SolidBlended(BlendFunction::Blend), &bmp16, x + 16, y + 48); + screen.blit_unchecked(SolidBlended(BlendFunction::Blend), &bmp12, x + 80, y + 48); + screen.blit_unchecked(SolidBlended(BlendFunction::Blend), &bmp21, x + 144, y + 48); + screen.blit_unchecked(SolidBlended(BlendFunction::Blend), &bmp3, x + 208, y + 48); + } + + ////// + + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, -3, 46); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, -4, 76); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, -8, 106); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, -12, 136); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, -13, 166); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, -14, 196); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, -16, 226); + + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 46, -3); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 76, -4); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 106, -8); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 136, -12); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 166, -13); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 196, -14); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 226, -16); + + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 307, 46); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 308, 76); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 312, 106); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 316, 136); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 317, 166); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 318, 196); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 320, 226); + + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 46, 227); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 76, 228); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 106, 232); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 136, 236); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 166, 237); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 196, 238); + screen.blit(SolidBlended(BlendFunction::Blend), &bmp16, 226, 240); + + let path = reference_file(Path::new("blended_solid_blits.png")); + //screen.to_png_file(path.as_path(), PngFormat::RGBA).unwrap(); + assert!(verify_visual(&screen, &path), "bitmap differs from source image: {:?}", path); +} + #[test] fn solid_flipped_blits() { use RgbaBlitMethod::*; @@ -497,6 +609,69 @@ fn solid_flipped_blits() { assert!(verify_visual(&screen, &path), "bitmap differs from source image: {:?}", path); } +#[test] +fn blended_solid_flipped_blits() { + use RgbaBlitMethod::*; + + let mut screen = setup_for_blending(); + + let bmp = generate_solid_bitmap_with_varied_alpha(16, 16); + + let x = 40; + let y = 20; + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, x + 16, y + 48); + screen.blit(SolidFlippedBlended { horizontal_flip: true, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, x + 80, y + 48); + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: true, blend: BlendFunction::Blend }, &bmp, x + 144, y + 48); + screen.blit(SolidFlippedBlended { horizontal_flip: true, vertical_flip: true, blend: BlendFunction::Blend }, &bmp, x + 208, y + 48); + + let x = 40; + let y = 110; + unsafe { + screen.blit_unchecked(SolidFlippedBlended { horizontal_flip: false, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, x + 16, y + 48); + screen.blit_unchecked(SolidFlippedBlended { horizontal_flip: true, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, x + 80, y + 48); + screen.blit_unchecked(SolidFlippedBlended { horizontal_flip: false, vertical_flip: true, blend: BlendFunction::Blend }, &bmp, x + 144, y + 48); + screen.blit_unchecked(SolidFlippedBlended { horizontal_flip: true, vertical_flip: true, blend: BlendFunction::Blend }, &bmp, x + 208, y + 48); + } + + ////// + + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, -3, 46); + screen.blit(SolidFlippedBlended { horizontal_flip: true, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, -4, 76); + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: true, blend: BlendFunction::Blend }, &bmp, -8, 106); + screen.blit(SolidFlippedBlended { horizontal_flip: true, vertical_flip: true, blend: BlendFunction::Blend }, &bmp, -12, 136); + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: true, blend: BlendFunction::Blend }, &bmp, -13, 166); + screen.blit(SolidFlippedBlended { horizontal_flip: true, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, -14, 196); + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: true, blend: BlendFunction::Blend }, &bmp, -16, 226); + + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, 46, -3); + screen.blit(SolidFlippedBlended { horizontal_flip: true, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, 76, -4); + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: true, blend: BlendFunction::Blend }, &bmp, 106, -8); + screen.blit(SolidFlippedBlended { horizontal_flip: true, vertical_flip: true, blend: BlendFunction::Blend }, &bmp, 136, -12); + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, 166, -13); + screen.blit(SolidFlippedBlended { horizontal_flip: true, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, 196, -14); + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: true, blend: BlendFunction::Blend }, &bmp, 226, -16); + + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, 307, 46); + screen.blit(SolidFlippedBlended { horizontal_flip: true, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, 308, 76); + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: true, blend: BlendFunction::Blend }, &bmp, 312, 106); + screen.blit(SolidFlippedBlended { horizontal_flip: true, vertical_flip: true, blend: BlendFunction::Blend }, &bmp, 316, 136); + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, 317, 166); + screen.blit(SolidFlippedBlended { horizontal_flip: true, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, 318, 196); + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: true, blend: BlendFunction::Blend }, &bmp, 320, 226); + + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, 46, 227); + screen.blit(SolidFlippedBlended { horizontal_flip: true, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, 76, 228); + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: true, blend: BlendFunction::Blend }, &bmp, 106, 232); + screen.blit(SolidFlippedBlended { horizontal_flip: true, vertical_flip: true, blend: BlendFunction::Blend }, &bmp, 136, 236); + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, 166, 237); + screen.blit(SolidFlippedBlended { horizontal_flip: true, vertical_flip: false, blend: BlendFunction::Blend }, &bmp, 196, 238); + screen.blit(SolidFlippedBlended { horizontal_flip: false, vertical_flip: true, blend: BlendFunction::Blend }, &bmp, 226, 240); + + let path = reference_file(Path::new("blended_solid_flipped_blits.png")); + //screen.to_png_file(path.as_path(), PngFormat::RGBA).unwrap(); + assert!(verify_visual(&screen, &path), "bitmap differs from source image: {:?}", path); +} + #[test] fn transparent_blits() { use RgbaBlitMethod::*; @@ -566,6 +741,74 @@ fn transparent_blits() { assert!(verify_visual(&screen, &path), "bitmap differs from source image: {:?}", path); } +#[test] +fn blended_transparent_blits() { + use RgbaBlitMethod::*; + + let mut screen = setup_for_blending(); + + let bmp16 = generate_solid_bitmap_with_varied_alpha(16, 16); + let bmp12 = generate_solid_bitmap_with_varied_alpha(12, 12); + let bmp21 = generate_solid_bitmap_with_varied_alpha(21, 21); + let bmp3 = generate_solid_bitmap_with_varied_alpha(3, 3); + + let transparent_color = to_argb32(255, 0, 0, 0); + + let x = 40; + let y = 20; + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, x + 16, y + 48); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp12, x + 80, y + 48); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp21, x + 144, y + 48); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp3, x + 208, y + 48); + + let x = 40; + let y = 110; + unsafe { + screen.blit_unchecked(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, x + 16, y + 48); + screen.blit_unchecked(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp12, x + 80, y + 48); + screen.blit_unchecked(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp21, x + 144, y + 48); + screen.blit_unchecked(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp3, x + 208, y + 48); + } + + ////// + + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, -3, 46); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, -4, 76); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, -8, 106); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, -12, 136); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, -13, 166); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, -14, 196); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, -16, 226); + + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 46, -3); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 76, -4); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 106, -8); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 136, -12); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 166, -13); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 196, -14); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 226, -16); + + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 307, 46); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 308, 76); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 312, 106); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 316, 136); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 317, 166); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 318, 196); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 320, 226); + + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 46, 227); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 76, 228); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 106, 232); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 136, 236); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 166, 237); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 196, 238); + screen.blit(TransparentBlended { transparent_color, blend: BlendFunction::Blend }, &bmp16, 226, 240); + + let path = reference_file(Path::new("blended_transparent_blits.png")); + //screen.to_png_file(path.as_path(), PngFormat::RGBA).unwrap(); + assert!(verify_visual(&screen, &path), "bitmap differs from source image: {:?}", path); +} + #[test] fn transparent_flipped_blits() { use RgbaBlitMethod::*; @@ -632,6 +875,72 @@ fn transparent_flipped_blits() { assert!(verify_visual(&screen, &path), "bitmap differs from source image: {:?}", path); } +#[test] +fn blended_transparent_flipped_blits() { + use RgbaBlitMethod::*; + + let mut screen = setup_for_blending(); + + let bmp = generate_solid_bitmap_with_varied_alpha(16, 16); + + let transparent_color = to_argb32(255, 0, 0, 0); + let blend = BlendFunction::Blend; + + let x = 40; + let y = 20; + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: false, blend }, &bmp, x + 16, y + 48); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: true, vertical_flip: false, blend }, &bmp, x + 80, y + 48); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: true, blend }, &bmp, x + 144, y + 48); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: true, vertical_flip: true, blend }, &bmp, x + 208, y + 48); + + let x = 40; + let y = 110; + unsafe { + screen.blit_unchecked(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: false, blend }, &bmp, x + 16, y + 48); + screen.blit_unchecked(TransparentFlippedBlended { transparent_color, horizontal_flip: true, vertical_flip: false, blend }, &bmp, x + 80, y + 48); + screen.blit_unchecked(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: true, blend }, &bmp, x + 144, y + 48); + screen.blit_unchecked(TransparentFlippedBlended { transparent_color, horizontal_flip: true, vertical_flip: true, blend }, &bmp, x + 208, y + 48); + } + + ////// + + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: false, blend }, &bmp, -3, 46); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: true, vertical_flip: false, blend }, &bmp, -4, 76); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: true, blend }, &bmp, -8, 106); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: true, vertical_flip: true, blend }, &bmp, -12, 136); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: true, blend }, &bmp, -13, 166); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: true, vertical_flip: false, blend }, &bmp, -14, 196); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: true, blend }, &bmp, -16, 226); + + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: false, blend }, &bmp, 46, -3); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: true, vertical_flip: false, blend }, &bmp, 76, -4); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: true, blend }, &bmp, 106, -8); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: true, vertical_flip: true, blend }, &bmp, 136, -12); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: false, blend }, &bmp, 166, -13); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: true, vertical_flip: false, blend }, &bmp, 196, -14); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: true, blend }, &bmp, 226, -16); + + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: false, blend }, &bmp, 307, 46); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: true, vertical_flip: false, blend }, &bmp, 308, 76); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: true, blend }, &bmp, 312, 106); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: true, vertical_flip: true, blend }, &bmp, 316, 136); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: false, blend }, &bmp, 317, 166); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: true, vertical_flip: false, blend }, &bmp, 318, 196); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: true, blend }, &bmp, 320, 226); + + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: false, blend }, &bmp, 46, 227); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: true, vertical_flip: false, blend }, &bmp, 76, 228); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: true, blend }, &bmp, 106, 232); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: true, vertical_flip: true, blend }, &bmp, 136, 236); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: false, blend }, &bmp, 166, 237); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: true, vertical_flip: false, blend }, &bmp, 196, 238); + screen.blit(TransparentFlippedBlended { transparent_color, horizontal_flip: false, vertical_flip: true, blend }, &bmp, 226, 240); + + let path = reference_file(Path::new("blended_transparent_flipped_blits.png")); + //screen.to_png_file(path.as_path(), PngFormat::RGBA).unwrap(); + assert!(verify_visual(&screen, &path), "bitmap differs from source image: {:?}", path); +} + #[test] fn transparent_single_blits() { use RgbaBlitMethod::*; @@ -828,6 +1137,71 @@ fn rotozoom_blits() { assert!(verify_visual(&screen, &path), "bitmap differs from source image: {:?}", path); } +#[test] +fn blended_rotozoom_blits() { + use RgbaBlitMethod::*; + + let mut screen = setup_for_blending(); + + let bmp = generate_bitmap_with_varied_alpha(16, 16); + + let blend = BlendFunction::Blend; + + let x = 40; + let y = 20; + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, x + 16, y + 48); + screen.blit(RotoZoomBlended { angle: 0.3, scale_x: 1.5, scale_y: 1.0, blend }, &bmp, x + 80, y + 48); + screen.blit(RotoZoomBlended { angle: 0.6, scale_x: 1.0, scale_y: 1.5, blend }, &bmp, x + 144, y + 48); + screen.blit(RotoZoomBlended { angle: 2.0, scale_x: 0.7, scale_y: 0.7, blend }, &bmp, x + 208, y + 48); + + let x = 40; + let y = 110; + unsafe { + screen.blit_unchecked(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, x + 16, y + 48); + screen.blit_unchecked(RotoZoomBlended { angle: 0.3, scale_x: 1.5, scale_y: 1.0, blend }, &bmp, x + 80, y + 48); + screen.blit_unchecked(RotoZoomBlended { angle: 0.6, scale_x: 1.0, scale_y: 1.5, blend }, &bmp, x + 144, y + 48); + screen.blit_unchecked(RotoZoomBlended { angle: 2.0, scale_x: 0.7, scale_y: 0.7, blend }, &bmp, x + 208, y + 48); + } + + ////// + + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, -3, 46); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, -4, 76); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, -8, 106); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, -12, 136); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, -13, 166); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, -14, 196); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, -16, 226); + + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 46, -3); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 76, -4); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 106, -8); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 136, -12); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 166, -13); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 196, -14); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 226, -16); + + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 307, 46); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 308, 76); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 312, 106); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 316, 136); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 317, 166); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 318, 196); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 320, 226); + + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 46, 227); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 76, 228); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 106, 232); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 136, 236); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 166, 237); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 196, 238); + screen.blit(RotoZoomBlended { angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 226, 240); + + let path = reference_file(Path::new("blended_rotozoom_blits.png")); + //screen.to_png_file(path.as_path(), PngFormat::RGBA).unwrap(); + assert!(verify_visual(&screen, &path), "bitmap differs from source image: {:?}", path); +} + #[test] fn rotozoom_transparent_blits() { use RgbaBlitMethod::*; @@ -893,3 +1267,69 @@ fn rotozoom_transparent_blits() { //screen.to_png_file(path.as_path()).unwrap(); assert!(verify_visual(&screen, &path), "bitmap differs from source image: {:?}", path); } + +#[test] +fn blended_rotozoom_transparent_blits() { + use RgbaBlitMethod::*; + + let mut screen = setup_for_blending(); + + let bmp = generate_solid_bitmap_with_varied_alpha(16, 16); + + let transparent_color = to_argb32(255, 0, 0, 0); + let blend = BlendFunction::Blend; + + let x = 40; + let y = 20; + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, x + 16, y + 48); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 0.3, scale_x: 1.5, scale_y: 1.0, blend }, &bmp, x + 80, y + 48); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 0.6, scale_x: 1.0, scale_y: 1.5, blend }, &bmp, x + 144, y + 48); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 2.0, scale_x: 0.7, scale_y: 0.7, blend }, &bmp, x + 208, y + 48); + + let x = 40; + let y = 110; + unsafe { + screen.blit_unchecked(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, x + 16, y + 48); + screen.blit_unchecked(RotoZoomTransparentBlended { transparent_color, angle: 0.3, scale_x: 1.5, scale_y: 1.0, blend }, &bmp, x + 80, y + 48); + screen.blit_unchecked(RotoZoomTransparentBlended { transparent_color, angle: 0.6, scale_x: 1.0, scale_y: 1.5, blend }, &bmp, x + 144, y + 48); + screen.blit_unchecked(RotoZoomTransparentBlended { transparent_color, angle: 2.0, scale_x: 0.7, scale_y: 0.7, blend }, &bmp, x + 208, y + 48); + } + + ////// + + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, -3, 46); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, -4, 76); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, -8, 106); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, -12, 136); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, -13, 166); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, -14, 196); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, -16, 226); + + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 46, -3); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 76, -4); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 106, -8); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 136, -12); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 166, -13); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 196, -14); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 226, -16); + + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 307, 46); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 308, 76); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 312, 106); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 316, 136); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 317, 166); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 318, 196); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 320, 226); + + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 46, 227); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 76, 228); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 106, 232); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 136, 236); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 166, 237); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 196, 238); + screen.blit(RotoZoomTransparentBlended { transparent_color, angle: 1.3, scale_x: 1.0, scale_y: 1.0, blend }, &bmp, 226, 240); + + let path = reference_file(Path::new("blended_rotozoom_transparent_blits.png")); + //screen.to_png_file(path.as_path(), PngFormat::RGBA).unwrap(); + assert!(verify_visual(&screen, &path), "bitmap differs from source image: {:?}", path); +} diff --git a/ggdt/tests/ref/rgba/blended_rotozoom_blits.png b/ggdt/tests/ref/rgba/blended_rotozoom_blits.png new file mode 100644 index 0000000000000000000000000000000000000000..2a58d12c7b7ff88a505f3f08f731cceabcd70b4f GIT binary patch literal 6955 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 literal 0 HcmV?d00001 diff --git a/ggdt/tests/ref/rgba/blended_solid_flipped_blits.png b/ggdt/tests/ref/rgba/blended_solid_flipped_blits.png new file mode 100644 index 0000000000000000000000000000000000000000..e4f606849ecd888286156fd6505a98b69369771c GIT binary patch literal 5530 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 literal 0 HcmV?d00001 diff --git a/ggdt/tests/ref/rgba/blended_transparent_blits.png b/ggdt/tests/ref/rgba/blended_transparent_blits.png new file mode 100644 index 0000000000000000000000000000000000000000..572910361342b8176ecc8eace6f50c08bc2d7d0c GIT binary patch literal 5692 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 literal 0 HcmV?d00001 diff --git a/ggdt/tests/ref/rgba/blended_transparent_flipped_blits.png b/ggdt/tests/ref/rgba/blended_transparent_flipped_blits.png new file mode 100644 index 0000000000000000000000000000000000000000..7892d4f38c0f5266d2eaa0478a5244c911fa9d9f GIT binary patch literal 5772 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