Download libraries/path-bool/src/visual_tests.rs from openfree/graphite2: direct link, hf CLI and curl.
- Browser
- Download file 6.8 kB
-
https://huggingface.co/spaces/openfree/graphite2/resolve/main/libraries/path-bool/src/visual_tests.rs
- Command line
-
hf download hf://spaces/openfree/graphite2/libraries/path-bool/src/visual_tests.rs
-
curl -L -o visual_tests.rs https://huggingface.co/spaces/openfree/graphite2/resolve/main/libraries/path-bool/src/visual_tests.rs
6.8 kB
| use crate::path_boolean::{self, FillRule, PathBooleanOperation}; | |
| use crate::path_data::{path_from_path_data, path_to_path_data}; | |
| use core::panic; | |
| use glob::glob; | |
| use image::{DynamicImage, GenericImageView, RgbaImage}; | |
| use resvg::render; | |
| use resvg::tiny_skia::Transform; | |
| use resvg::usvg::{Options, Tree}; | |
| use std::fs; | |
| use std::path::PathBuf; | |
| use svg::parser::Event; | |
| const TOLERANCE: u8 = 84; | |
| fn get_fill_rule(fill_rule: &str) -> FillRule { | |
| match fill_rule { | |
| "evenodd" => FillRule::EvenOdd, | |
| _ => FillRule::NonZero, | |
| } | |
| } | |
| fn visual_tests() { | |
| let ops = [ | |
| ("union", PathBooleanOperation::Union), | |
| ("difference", PathBooleanOperation::Difference), | |
| ("intersection", PathBooleanOperation::Intersection), | |
| ("exclusion", PathBooleanOperation::Exclusion), | |
| ("division", PathBooleanOperation::Division), | |
| ("fracture", PathBooleanOperation::Fracture), | |
| ]; | |
| let folders: Vec<(String, PathBuf, &str, PathBooleanOperation)> = glob("visual-tests/*/") | |
| .expect("Failed to read glob pattern") | |
| .flat_map(|entry| { | |
| let dir = entry.expect("Failed to get directory entry"); | |
| ops.iter() | |
| .map(move |(op_name, op)| (dir.file_name().unwrap().to_string_lossy().into_owned(), dir.clone(), *op_name, *op)) | |
| }) | |
| .collect(); | |
| let mut failure = false; | |
| for (name, dir, op_name, op) in folders { | |
| let test_name = format!("{} {}", name, op_name); | |
| println!("Running test: {}", test_name); | |
| fs::create_dir_all(dir.join("test-results")).expect("Failed to create test-results directory"); | |
| let original_path = dir.join("original.svg"); | |
| let mut content = String::new(); | |
| let svg_tree = svg::open(&original_path, &mut content).expect("Failed to parse SVG"); | |
| let mut paths = Vec::new(); | |
| let mut first_path_attributes = String::new(); | |
| let mut width = String::new(); | |
| let mut height = String::new(); | |
| let mut view_box = String::new(); | |
| let mut transform = String::new(); | |
| for event in svg_tree { | |
| match event { | |
| Event::Tag("svg", svg::node::element::tag::Type::Start, attributes) => { | |
| width = attributes.get("width").map(|s| s.to_string()).unwrap_or_default(); | |
| height = attributes.get("height").map(|s| s.to_string()).unwrap_or_default(); | |
| view_box = attributes.get("viewBox").map(|s| s.to_string()).unwrap_or_default(); | |
| } | |
| Event::Tag("g", svg::node::element::tag::Type::Start, attributes) => { | |
| if let Some(transform_attr) = attributes.get("transform") { | |
| transform = transform_attr.to_string(); | |
| } | |
| } | |
| Event::Tag("path", svg::node::element::tag::Type::Empty, attributes) => { | |
| let data = attributes.get("d").map(|s| s.to_string()).expect("Path data not found"); | |
| let fill_rule = attributes.get("fill-rule").map(|v| v.to_string()).unwrap_or_else(|| "nonzero".to_string()); | |
| paths.push((data, fill_rule)); | |
| // Store attributes of the first path | |
| if first_path_attributes.is_empty() { | |
| for (key, value) in attributes.iter() { | |
| if key != "d" && key != "id" { | |
| first_path_attributes.push_str(&format!("{}=\"{}\" ", key, value)); | |
| } | |
| } | |
| } | |
| } | |
| _ => {} | |
| } | |
| } | |
| if (width.is_empty() || height.is_empty()) && !view_box.is_empty() { | |
| let vb: Vec<&str> = view_box.split_whitespace().collect(); | |
| if vb.len() == 4 { | |
| width = vb[2].to_string(); | |
| height = vb[3].to_string(); | |
| } | |
| } | |
| if width.is_empty() || height.is_empty() { | |
| panic!("Failed to extract width and height from SVG"); | |
| } | |
| let a_node = paths[0].clone(); | |
| let b_node = paths[1].clone(); | |
| let a = path_from_path_data(&a_node.0).unwrap(); | |
| let b = path_from_path_data(&b_node.0).unwrap(); | |
| let a_fill_rule = get_fill_rule(&a_node.1); | |
| let b_fill_rule = get_fill_rule(&b_node.1); | |
| let result = path_boolean::path_boolean(&a, a_fill_rule, &b, b_fill_rule, op).unwrap(); | |
| // Create the result SVG with correct dimensions and transform | |
| let mut result_svg = format!("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{}\" height=\"{}\" viewBox=\"{}\">", width, height, view_box); | |
| if !transform.is_empty() { | |
| result_svg.push_str(&format!("<g transform=\"{}\">", transform)); | |
| } | |
| for path in &result { | |
| result_svg.push_str(&format!("<path d=\"{}\" {}/>", path_to_path_data(path, 1e-4), first_path_attributes)); | |
| } | |
| if !transform.is_empty() { | |
| result_svg.push_str("</g>"); | |
| } | |
| result_svg.push_str("</svg>"); | |
| // Save the result SVG | |
| let destination_path = dir.join("test-results").join(format!("{}-ours.svg", op_name)); | |
| fs::write(&destination_path, &result_svg).expect("Failed to write result SVG"); | |
| // Render and compare images | |
| let ground_truth_path = dir.join(format!("{}.svg", op_name)); | |
| let ground_truth_svg = fs::read_to_string(&ground_truth_path).expect("Failed to read ground truth SVG"); | |
| let ours_image = render_svg(&result_svg); | |
| let ground_truth_image = render_svg(&ground_truth_svg); | |
| let ours_png_path = dir.join("test-results").join(format!("{}-ours.png", op_name)); | |
| ours_image.save(&ours_png_path).expect("Failed to save our PNG"); | |
| let ground_truth_png_path = dir.join("test-results").join(format!("{}.png", op_name)); | |
| ground_truth_image.save(&ground_truth_png_path).expect("Failed to save ground truth PNG"); | |
| failure |= compare_images(&ours_image, &ground_truth_image, TOLERANCE); | |
| // Check the number of paths | |
| let result_path_count = result.len(); | |
| let ground_truth_path_count = ground_truth_svg.matches("<path").count(); | |
| if result_path_count != ground_truth_path_count { | |
| failure = true; | |
| eprintln!("Number of paths doesn't match for test: {}", test_name); | |
| } | |
| } | |
| if failure { | |
| panic!("Some tests have failed"); | |
| } | |
| } | |
| fn render_svg(svg_code: &str) -> DynamicImage { | |
| let opts = Options::default(); | |
| let tree = Tree::from_str(svg_code, &opts).unwrap(); | |
| let pixmap_size = tree.size(); | |
| let (width, height) = (pixmap_size.width() as u32, pixmap_size.height() as u32); | |
| let mut pixmap = resvg::tiny_skia::Pixmap::new(width, height).unwrap(); | |
| let mut pixmap_mut = pixmap.as_mut(); | |
| render(&tree, Transform::default(), &mut pixmap_mut); | |
| DynamicImage::ImageRgba8(RgbaImage::from_raw(width, height, pixmap.data().to_vec()).unwrap()) | |
| } | |
| fn compare_images(img1: &DynamicImage, img2: &DynamicImage, tolerance: u8) -> bool { | |
| assert_eq!(img1.dimensions(), img2.dimensions(), "Image dimensions do not match"); | |
| for (x, y, pixel1) in img1.pixels() { | |
| let pixel2 = img2.get_pixel(x, y); | |
| for i in 0..4 { | |
| let difference = (pixel1[i] as i32 - pixel2[i] as i32).unsigned_abs() as u8; | |
| if difference > tolerance { | |
| println!("Difference {} larger than tolerance {} at [{}, {}], channel {}.", difference, tolerance, x, y, i); | |
| return true; | |
| } | |
| assert!( | |
| difference <= tolerance, | |
| "Difference {} larger than tolerance {} at [{}, {}], channel {}.", | |
| difference, | |
| tolerance, | |
| x, | |
| y, | |
| i | |
| ); | |
| } | |
| } | |
| false | |
| } | |