Files
vectorcraft/crates/engine/tests/it/affinity_local.rs
T
aiongg 8cc69ed45c CI: one test binary per crate, nextest, line tables only (#3855)
* CI: line tables only for workspace crates, no debug info for dependencies

* CI: one integration-test binary per crate

Each crate's tests/*.rs files become modules of tests/it/main.rs. Files that change
process-wide state (Color Settings, the font database, installed plug-ins, the working
directory), the fuzz sweeps and the test targets the Affinity corpus workflow names stay
their own binaries.

* Docs: name the integration tests' new paths

* CI: xtask test with nextest and doctests, one wasm check, clippy --keep-going

cargo xtask ci runs the tests with cargo nextest (cargo test when it is not installed),
builds them with --lib --bins --tests, runs the doctests as their own step, and checks
the wasm crates in one cargo check --keep-going call.
2026-10-23 21:36:10 -04:00

84 lines
4.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Local Affinity documents (never committed): every file under `AFFINITY_SAMPLES` (searched
//! recursively) opens as editable art, and each artboard is compared with the thumbnail Affinity
//! stored in the file. Reports the mean difference per artboard (0–255, both on white); with
//! `AFFINITY_SAMPLES_OUT` set, writes each board's thumbnail crop and render side by side there.
//! Skipped when `AFFINITY_SAMPLES` is unset (as in CI).
use std::path::{Path, PathBuf};
fn files(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else { return };
for e in entries.flatten() {
let p = e.path();
if p.is_dir() {
files(&p, out);
} else if p
.extension()
.and_then(|x| x.to_str())
.is_some_and(|x| ["af", "afdesign", "afpub", "afphoto"].contains(&x.to_ascii_lowercase().as_str()))
{
out.push(p);
}
}
}
fn on_white(p: &[u8]) -> [f64; 3] {
let a = f64::from(p[3]) / 255.0;
[0, 1, 2].map(|i| f64::from(p[i]) * a + 255.0 * (1.0 - a))
}
#[test]
fn local_affinity_documents_render_like_their_thumbnails() {
let Some(root) = std::env::var_os("AFFINITY_SAMPLES").map(PathBuf::from) else { return };
let out = std::env::var_os("AFFINITY_SAMPLES_OUT").map(PathBuf::from);
let mut paths = Vec::new();
files(&root, &mut paths);
paths.sort();
for path in paths {
let name = path.file_name().unwrap().to_string_lossy().to_string();
let bytes = std::fs::read(&path).unwrap();
let loaded = vectorcraft_engine::cmd::fileio::load(&name, &bytes).unwrap();
assert!(!loaded.preview_only, "{name}: {:?}", loaded.warnings);
let d = &loaded.doc;
eprintln!("{name}: {} artboards, {} layers", d.artboards.len(), d.layers.len());
for w in &loaded.warnings {
eprintln!(" {w}");
}
let Ok(preview) = vectorcraft_affinity::preview(&bytes) else { continue };
let thumb = image::load_from_memory(preview.png).unwrap().to_rgba8();
let union = d.artboards.iter().map(|a| a.rect).reduce(|a, b| a.union(b)).unwrap();
let scale = f64::from(thumb.width()) / union.width();
let mut total = 0.0;
for (i, board) in d.artboards.iter().enumerate() {
let r = board.rect;
let crop = |v: f64| (v * scale).round().max(0.0) as u32;
let (x, y) = (crop(r.x0 - union.x0).min(thumb.width() - 1), crop(r.y0 - union.y0).min(thumb.height() - 1));
let (w, h) = (crop(r.width()).min(thumb.width() - x).max(1), crop(r.height()).min(thumb.height() - y).max(1));
let expected = image::imageops::crop_imm(&thumb, x, y, w, h).to_image();
let k = (512.0 / r.width().max(r.height())).max(scale);
let rendered = vectorcraft_render::Renderer::new().render_region(d, r, k, false);
let big = image::RgbaImage::from_raw(rendered.width, rendered.height, rendered.to_straight()).unwrap();
let got = image::imageops::resize(&big, w, h, image::imageops::FilterType::Triangle);
let (mut sum, mut n) = (0.0, 0usize);
for (p, q) in expected.as_raw().as_chunks::<4>().0.iter().zip(got.as_raw().as_chunks::<4>().0.iter()) {
let (a, b) = (on_white(p), on_white(q));
sum += (0..3).map(|c| (a[c] - b[c]).abs()).sum::<f64>();
n += 3;
}
let diff = sum / n.max(1) as f64;
total += diff;
eprintln!(" board {i:3} {:<30} {diff:6.2}", board.name);
if let Some(dir) = &out {
let thumb_big = image::imageops::resize(&expected, big.width(), big.height(), image::imageops::FilterType::Triangle);
let mut pair = image::RgbaImage::from_pixel(big.width() * 2 + 8, big.height(), image::Rgba([255, 255, 255, 255]));
image::imageops::overlay(&mut pair, &thumb_big, 0, 0);
image::imageops::overlay(&mut pair, &big, i64::from(big.width()) + 8, 0);
let stem = path.file_stem().unwrap().to_string_lossy().to_string();
std::fs::create_dir_all(dir).unwrap();
pair.save(dir.join(format!("{stem}-{i:03}.png"))).unwrap();
}
}
eprintln!(" mean over boards: {:.2}", total / d.artboards.len().max(1) as f64);
}
}