use std::{fs, io::ErrorKind, ops::Deref};
use image::RgbaImage;
use rand::prelude::*;
use crow::{
glutin::{dpi::LogicalSize, event_loop::EventLoop, window::WindowBuilder},
target::{Offset, Scaled},
Context, DrawConfig, Texture,
};
type TestFn = fn(&mut Context) -> Result<RgbaImage, crow::Error>;
pub fn test(ctx: &mut Context, name: &str, f: TestFn) -> Result<(), ()> {
let res = f(ctx);
let actual_image = match res {
Ok(image) => image,
Err(_e) => {
eprintln!("TEST FAILED (runtime error): {}", name);
return Err(());
}
};
let expected = if let Ok(image) = image::open(format!("tests/expected/{}.png", name)) {
image.to_rgba8()
} else {
eprintln!("TEST FAILED (expected image not found): {}", name);
return Err(());
};
if actual_image.deref() != expected.deref() {
eprintln!("TEST FAILED (invalid return image): {}", name);
actual_image
.save(format!("tests/actual/{}.png", name))
.unwrap();
Err(())
} else {
Ok(())
}
}
fn simple(ctx: &mut Context) -> Result<RgbaImage, crow::Error> {
let mut a = Texture::new(ctx, (32, 32))?;
let mut b = Texture::new(ctx, (32, 32))?;
ctx.clear_color(&mut a, (1.0, 0.0, 0.0, 1.0));
ctx.clear_color(&mut b, (0.0, 1.0, 0.0, 1.0));
ctx.draw(&mut a, &b, (16, 16), &DrawConfig::default());
Ok(ctx.image_data(&a))
}
fn from_image(ctx: &mut Context) -> Result<RgbaImage, crow::Error> {
let mut a = Texture::new(ctx, (5, 5))?;
let b = Texture::from_image(
ctx,
RgbaImage::from_raw(
2,
2,
vec![
0, 0, 255, 255, 255, 255, 0, 255, 0, 255, 255, 255, 0, 0, 0, 255,
],
)
.unwrap(),
)?;
ctx.clear_color(&mut a, (1.0, 0.0, 0.0, 1.0));
ctx.draw(&mut a, &b, (1, 1), &DrawConfig::default());
Ok(ctx.image_data(&a))
}
fn color_modulation(ctx: &mut Context) -> Result<RgbaImage, crow::Error> {
let mut a = Texture::new(ctx, (32, 32))?;
let mut b = Texture::new(ctx, (32, 32))?;
ctx.clear_color(&mut a, (1.0, 0.0, 0.0, 1.0));
ctx.clear_color(&mut b, (0.5, 0.0, 0.5, 1.0));
ctx.draw(
&mut a,
&b,
(16, 16),
&DrawConfig {
color_modulation: [
[0.0, 0.0, 0.0, 0.0],
[1.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
..Default::default()
},
);
Ok(ctx.image_data(&a))
}
fn flip_vertically(ctx: &mut Context) -> Result<RgbaImage, crow::Error> {
let big = Texture::new(ctx, (48, 16))?;
let mut a = big.get_section((0, 0), (16, 16));
let mut b = big.get_section((16, 0), (16, 16));
let mut c = big.get_section((32, 0), (16, 16));
ctx.clear_color(&mut a, (1.0, 0.0, 0.0, 1.0));
ctx.clear_color(&mut b, (0.0, 1.0, 0.0, 1.0));
ctx.clear_color(&mut c, (0.0, 0.0, 1.0, 1.0));
ctx.draw(&mut c, &b, (0, 8), &DrawConfig::default());
ctx.draw(
&mut a,
&c,
(8, 0),
&DrawConfig {
flip_vertically: true,
..Default::default()
},
);
Ok(ctx.image_data(&a))
}
fn section_drawing(ctx: &mut Context) -> Result<RgbaImage, crow::Error> {
let mut target = Texture::new(ctx, (10, 10))?;
ctx.clear_color(&mut target, (0.0, 1.0, 0.0, 1.0));
let source = Texture::load(ctx, "textures/section_test.png")?;
let source = source.get_section((3, 4), (3, 2));
ctx.draw(&mut target, &source, (3, 5), &DrawConfig::default());
Ok(ctx.image_data(&target))
}
fn section_offset(ctx: &mut Context) -> Result<RgbaImage, crow::Error> {
let mut target = Texture::new(ctx, (10, 10))?;
ctx.clear_color(&mut target, (0.0, 1.0, 0.0, 1.0));
let source = Texture::load(ctx, "textures/section_test.png")?;
let source = source.get_section((3, 4), (3, 2));
ctx.draw(
&mut Offset::new(&mut target, (-2, -3)),
&source,
(1, 2),
&DrawConfig::default(),
);
Ok(ctx.image_data(&target))
}
fn section_flipped(ctx: &mut Context) -> Result<RgbaImage, crow::Error> {
let mut target = Texture::new(ctx, (10, 10))?;
ctx.clear_color(&mut target, (0.0, 1.0, 0.0, 1.0));
let source = Texture::load(ctx, "textures/section_test.png")?;
let source = source.get_section((3, 4), (3, 2));
ctx.draw(
&mut target,
&source,
(3, 5),
&DrawConfig {
flip_vertically: true,
flip_horizontally: true,
..Default::default()
},
);
Ok(ctx.image_data(&target))
}
fn section_scaled(ctx: &mut Context) -> Result<RgbaImage, crow::Error> {
let mut target = Texture::new(ctx, (10, 10))?;
ctx.clear_color(&mut target, (0.0, 1.0, 0.0, 1.0));
let source = Texture::load(ctx, "textures/section_test.png")?;
let source = source.get_section((3, 4), (3, 2));
ctx.draw(
&mut Scaled::new(&mut target, (2, 3)),
&source,
(1, 1),
&DrawConfig {
flip_vertically: true,
flip_horizontally: true,
..Default::default()
},
);
Ok(ctx.image_data(&target))
}
fn zero_section(ctx: &mut Context) -> Result<RgbaImage, crow::Error> {
let mut target = Texture::new(ctx, (10, 10))?;
ctx.clear_color(&mut target, (0.0, 1.0, 0.0, 1.0));
let source = Texture::load(ctx, "textures/section_test.png")?;
let source = source.get_section((3, 4), (0, 0));
ctx.draw(&mut target, &source, (3, 5), &DrawConfig::default());
Ok(ctx.image_data(&target))
}
fn debug_lines(ctx: &mut Context) -> Result<RgbaImage, crow::Error> {
let mut target = Texture::new(ctx, (10, 10))?;
ctx.clear_color(&mut target, (0.0, 1.0, 0.0, 1.0));
ctx.debug_line(&mut target, (2, 2), (2, 8), (1.0, 0.0, 0.0, 1.0));
ctx.debug_line(&mut target, (4, 9), (8, 9), (1.0, 0.0, 0.0, 1.0));
Ok(ctx.image_data(&target))
}
fn debug_rectangle(ctx: &mut Context) -> Result<RgbaImage, crow::Error> {
let mut target = Texture::new(ctx, (10, 10))?;
ctx.clear_color(&mut target, (1.0, 0.0, 0.0, 1.0));
ctx.debug_rectangle(&mut target, (1, 1), (4, 3), (0.0, 1.0, 0.0, 1.0));
Ok(ctx.image_data(&target))
}
fn lines_offset(ctx: &mut Context) -> Result<RgbaImage, crow::Error> {
let mut image = Texture::new(ctx, (10, 10))?;
let mut target = Offset::new(&mut image, (-1, -2));
ctx.clear_color(&mut target, (0.0, 1.0, 0.0, 1.0));
ctx.debug_line(&mut target, (1, 0), (1, 8), (1.0, 0.0, 0.0, 1.0));
ctx.debug_line(&mut target, (3, 7), (7, 7), (1.0, 0.0, 0.0, 1.0));
Ok(ctx.image_data(&image))
}
#[derive(Default)]
struct TestRunner(Vec<(&'static str, TestFn)>);
impl TestRunner {
fn add(&mut self, name: &'static str, f: TestFn) {
self.0.push((name, f))
}
fn run(mut self) -> i32 {
println!("\nrunning {} tests", self.0.len());
self.0.shuffle(&mut rand::thread_rng());
let mut ctx = Context::new(
WindowBuilder::new()
.with_inner_size(LogicalSize::new(720, 480))
.with_visible(false),
&EventLoop::new(),
)
.unwrap();
let mut success = 0;
let mut failed = 0;
for (name, f) in self.0 {
match test(&mut ctx, name, f) {
Ok(()) => success += 1,
Err(()) => failed += 1,
}
}
let (v, s) = if failed > 0 { (1, "FAILED") } else { (0, "ok") };
println!(
"test result: {}. {} passed; {} failed; 0 ignored; 0 measured; 0 filtered out\n",
s, success, failed,
);
v
}
}
fn main() {
fs::remove_dir_all("tests/actual")
.or_else(|e| {
if e.kind() == ErrorKind::NotFound {
Ok(())
} else {
Err(e)
}
})
.expect("unable to remove 'tests/actual'");
fs::create_dir("tests/actual").expect("unable to create 'tests/actual'");
let mut runner = TestRunner::default();
runner.add("simple", simple);
runner.add("from_image", from_image);
runner.add("color_modulation", color_modulation);
runner.add("flip_vertically", flip_vertically);
runner.add("section_drawing", section_drawing);
runner.add("section_offset", section_offset);
runner.add("section_flipped", section_flipped);
runner.add("section_scaled", section_scaled);
runner.add("zero_section", zero_section);
runner.add("debug_lines", debug_lines);
runner.add("debug_rectangle", debug_rectangle);
runner.add("lines_offset", lines_offset);
std::process::exit(runner.run())
}