use image::{DynamicImage, ImageError};
use std::{env, io, path::Path};
use julia_set::{Backend, ImageBuffer, Params, Render};
const IMAGE_SIZE: [u32; 2] = [360, 360];
fn generate_image<F, B: Backend<F> + Default>(function: F, params: &Params) -> ImageBuffer {
B::default()
.create_program(function)
.unwrap()
.render(params)
.unwrap()
}
fn compare_to_reference(reference_filename: &str, image: &ImageBuffer) {
const ROOT_PATH: &str = env!("CARGO_MANIFEST_DIR");
let reference_path = Path::new(ROOT_PATH)
.join("tests")
.join("__snapshots__")
.join(reference_filename);
let reference_image = match image::open(&reference_path) {
Ok(DynamicImage::ImageLuma8(image)) => Some(image),
Ok(_) => panic!("Unexpected image format"),
Err(ImageError::IoError(ref io_error)) if io_error.kind() == io::ErrorKind::NotFound => {
None
}
Err(other_error) => panic!("Error opening reference file: {:?}", other_error),
};
if let Some(ref reference_image) = reference_image {
let image_diff = ImageDiff::new(reference_image, &image);
println!("{}: {:?}", reference_filename, image_diff);
image_diff.assert_is_sound();
return;
} else if let Ok(snapshot_update) = env::var("SNAPSHOT_UPDATE") {
if snapshot_update == "1" {
image
.save(&reference_path)
.expect("Cannot save reference image");
return;
}
}
panic!("Snapshot `{}` not found", reference_filename);
}
#[derive(Debug)]
struct ImageDiff {
differing_pixels: f32,
mean_difference: f32,
}
impl ImageDiff {
const MAX_DIFFERING_PIXELS: f32 = 0.1;
const MAX_MEAN_DIFFERENCE: f32 = 2.0;
fn pixel_quantity(image: &ImageBuffer, quantity: u32) -> f32 {
let pixel_count = image.width() * image.height();
quantity as f32 / pixel_count as f32
}
fn new(expected: &ImageBuffer, actual: &ImageBuffer) -> Self {
assert_eq!(expected.width(), actual.width());
assert_eq!(expected.height(), actual.height());
let mut differing_count = 0_u32;
let mut total_diff = 0_u32;
for (expected_pixel, actual_pixel) in expected.pixels().zip(actual.pixels()) {
let diff = if expected_pixel[0] > actual_pixel[0] {
expected_pixel[0] - actual_pixel[0]
} else {
actual_pixel[0] - expected_pixel[0]
};
if diff > 0 {
differing_count += 1;
total_diff += diff as u32;
}
}
let differing_pixels = Self::pixel_quantity(&expected, differing_count);
let mean_difference = Self::pixel_quantity(&expected, total_diff);
Self {
differing_pixels,
mean_difference,
}
}
fn assert_is_sound(&self) {
assert!(
self.differing_pixels <= Self::MAX_DIFFERING_PIXELS,
"{:?}",
self
);
assert!(
self.mean_difference <= Self::MAX_MEAN_DIFFERENCE,
"{:?}",
self
);
}
}
mod cubic {
use super::*;
#[cfg(any(
feature = "dyn_cpu_backend",
feature = "opencl_backend",
feature = "vulkan_backend"
))]
fn create_function() -> julia_set::Function {
"z * z * z - 0.39".parse().unwrap()
}
const SNAPSHOT_FILENAME: &str = "cubic.png";
fn render_params() -> Params {
Params::new(IMAGE_SIZE, 2.5).with_infinity_distance(2.5)
}
#[test]
#[cfg(feature = "dyn_cpu_backend")]
fn cpu_backend() {
let image = generate_image::<_, julia_set::Cpu>(&create_function(), &render_params());
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
#[test]
#[cfg(feature = "cpu_backend")]
fn cpu_backend_with_native_function() {
let image = generate_image::<_, julia_set::Cpu>(|z| z * z * z - 0.39, &render_params());
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
#[test]
#[cfg(feature = "vulkan_backend")]
fn vulkan_backend() {
let image = generate_image::<_, julia_set::Vulkan>(&create_function(), &render_params());
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
#[test]
#[cfg(feature = "opencl_backend")]
fn opencl_backend() {
let image = generate_image::<_, julia_set::OpenCl>(&create_function(), &render_params());
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
}
mod exp {
use super::*;
use num_complex::Complex32;
#[cfg(any(
feature = "dyn_cpu_backend",
feature = "opencl_backend",
feature = "vulkan_backend"
))]
fn create_function() -> julia_set::Function {
"exp(z ^ -4) + 0.15i".parse().unwrap()
}
const SNAPSHOT_FILENAME: &str = "exp.png";
fn render_params() -> Params {
Params::new(IMAGE_SIZE, 4.0).with_infinity_distance(9.0)
}
#[test]
#[cfg(feature = "dyn_cpu_backend")]
fn cpu_backend() {
let image = generate_image::<_, julia_set::Cpu>(&create_function(), &render_params());
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
#[test]
#[cfg(feature = "cpu_backend")]
fn cpu_backend_with_native_function() {
let image = generate_image::<_, julia_set::Cpu>(
|z: Complex32| z.powi(-4).exp() + Complex32::new(0.0, 0.15),
&render_params(),
);
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
#[test]
#[cfg(feature = "vulkan_backend")]
fn vulkan_backend() {
let image = generate_image::<_, julia_set::Vulkan>(&create_function(), &render_params());
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
#[test]
#[cfg(feature = "opencl_backend")]
fn opencl_backend() {
let image = generate_image::<_, julia_set::OpenCl>(&create_function(), &render_params());
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
}
mod flower {
use super::*;
use num_complex::Complex32;
#[cfg(any(
feature = "dyn_cpu_backend",
feature = "opencl_backend",
feature = "vulkan_backend"
))]
fn create_function() -> julia_set::Function {
"0.8*z + z/atanh(z^-4)".parse().unwrap()
}
const SNAPSHOT_FILENAME: &str = "flower.png";
fn render_params() -> Params {
Params::new(IMAGE_SIZE, 2.0).with_infinity_distance(10.0)
}
#[test]
#[cfg(feature = "dyn_cpu_backend")]
fn cpu_backend() {
let image = generate_image::<_, julia_set::Cpu>(&create_function(), &render_params());
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
#[test]
#[cfg(feature = "cpu_backend")]
fn cpu_backend_with_native_function() {
let image = generate_image::<_, julia_set::Cpu>(
|z: Complex32| z * 0.8 + z / z.powi(-4).atanh(),
&render_params(),
);
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
#[test]
#[cfg(feature = "vulkan_backend")]
fn vulkan_backend() {
let image = generate_image::<_, julia_set::Vulkan>(&create_function(), &render_params());
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
#[test]
#[cfg(feature = "opencl_backend")]
fn opencl_backend() {
let image = generate_image::<_, julia_set::OpenCl>(&create_function(), &render_params());
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
}
mod hills {
use super::*;
use num_complex::Complex32;
#[cfg(any(
feature = "dyn_cpu_backend",
feature = "opencl_backend",
feature = "vulkan_backend"
))]
fn create_function() -> julia_set::Function {
"1i * acosh(cosh(1i * z) - arg(z)^-2) - 0.05 + 0.05i"
.parse()
.unwrap()
}
const SNAPSHOT_FILENAME: &str = "hills.png";
fn render_params() -> Params {
Params::new(IMAGE_SIZE, 8.0)
.with_view_center([-9.41, 0.0])
.with_infinity_distance(5.0)
}
#[test]
#[cfg(feature = "dyn_cpu_backend")]
fn cpu_backend() {
let image = generate_image::<_, julia_set::Cpu>(&create_function(), &render_params());
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
#[test]
#[cfg(feature = "cpu_backend")]
fn cpu_backend_with_native_function() {
let image = generate_image::<_, julia_set::Cpu>(
|z: Complex32| {
Complex32::i() * ((Complex32::i() * z).cosh() - z.arg().powi(-2)).acosh()
+ Complex32::new(-0.05, 0.05)
},
&render_params(),
);
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
#[test]
#[cfg(feature = "vulkan_backend")]
fn vulkan_backend() {
let image = generate_image::<_, julia_set::Vulkan>(&create_function(), &render_params());
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
#[test]
#[cfg(feature = "opencl_backend")]
fn opencl_backend() {
let image = generate_image::<_, julia_set::OpenCl>(&create_function(), &render_params());
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
}
mod spiral {
use super::*;
use num_complex::Complex32;
#[cfg(any(
feature = "dyn_cpu_backend",
feature = "opencl_backend",
feature = "vulkan_backend"
))]
fn create_function() -> julia_set::Function {
"z + tanh(sqrt(z)) - 0.18 + 0.5i".parse().unwrap()
}
const SNAPSHOT_FILENAME: &str = "spiral.png";
fn render_params() -> Params {
Params::new(IMAGE_SIZE, 2.3)
.with_view_center([-6.84, 1.15])
.with_infinity_distance(9.0)
}
#[test]
#[cfg(feature = "dyn_cpu_backend")]
fn cpu_backend() {
let image = generate_image::<_, julia_set::Cpu>(&create_function(), &render_params());
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
#[test]
#[cfg(feature = "cpu_backend")]
fn cpu_backend_with_native_function() {
let image = generate_image::<_, julia_set::Cpu>(
|z: Complex32| z + z.sqrt().tanh() + Complex32::new(-0.18, 0.5),
&render_params(),
);
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
#[test]
#[cfg(feature = "vulkan_backend")]
fn vulkan_backend() {
let image = generate_image::<_, julia_set::Vulkan>(&create_function(), &render_params());
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
#[test]
#[cfg(feature = "opencl_backend")]
fn opencl_backend() {
let image = generate_image::<_, julia_set::OpenCl>(&create_function(), &render_params());
compare_to_reference(SNAPSHOT_FILENAME, &image);
}
}