Files
vtracer/crates/vtracer/tests/pipeline.rs
T
Chris Tsang e46c971845 Rewrite into a vectorization framework (pillars 1–4)
Replace the 0.6.x single-pipeline crate with a stage-based framework, per
docs/design/. Implements Motivation pillars 1–4 (frontend, curve fitting,
color fitting, optimizer); mosaic (5) and bindings are deferred.

Workspace:
- crates/vtracer      — the framework library (wasm-safe, no I/O)
- crates/vtracer-cli  — thin CLI wrapper (clap 4 + image I/O)
- cmdapp/ and webapp/ excluded from the workspace (git-preserved)

Stages behind object-safe traits, composed by a Pipeline driver:
- Frontend: ColorClusterFrontend (+ transparency keying), BinaryFrontend
- CurveFitter: Pixel / Polygon / Spline (region tracing via visioncortex)
- ColorFitter: Identity, FixedPalette (OKLab-nearest), AutoQuantize
  (area-weighted median cut), MergeAdjacent
- OptimizerPass: QuantizePass, SimplifyPass
- SvgWriter: relative/absolute shortest encoding, H/V/S shorthands,
  compact number formatting, <g fill> grouping

visioncortex is a path dependency on the local 0.9.0 checkout.

Verified: 14 unit/integration tests pass; framework builds for
wasm32-unknown-unknown; CLI output renders faithfully via rsvg.
2026-07-23 22:27:24 +01:00

89 lines
2.5 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.
//! End-to-end pipeline smoke tests over synthetic images.
use vtracer::{ColorImage, ColorMode, Config, FitMode, Hierarchical};
/// Build a `size × size` image split into two vertical color bands.
fn two_band_image(size: usize) -> ColorImage {
let mut pixels = Vec::with_capacity(size * size * 4);
for _y in 0..size {
for x in 0..size {
let (r, g, b) = if x < size / 2 {
(220, 40, 40)
} else {
(40, 40, 220)
};
pixels.extend_from_slice(&[r, g, b, 255]);
}
}
ColorImage {
pixels,
width: size,
height: size,
}
}
fn assert_valid_svg(svg: &str) {
assert!(svg.contains("<svg"), "missing <svg> element:\n{svg}");
assert!(svg.trim_end().ends_with("</svg>"), "missing </svg> close");
assert!(svg.contains("<path"), "expected at least one path:\n{svg}");
}
#[test]
fn default_color_pipeline_produces_svg() {
let img = two_band_image(32);
let svg = Config::default().build().unwrap().to_svg(&img).unwrap();
assert_valid_svg(&svg);
}
#[test]
fn all_fit_modes_produce_svg() {
let img = two_band_image(32);
for mode in [FitMode::Pixel, FitMode::Polygon, FitMode::Spline] {
let config = Config {
mode,
..Config::default()
};
let svg = config.build().unwrap().to_svg(&img).unwrap();
assert_valid_svg(&svg);
}
}
#[test]
fn binary_pipeline_produces_svg() {
let img = two_band_image(32);
let config = Config {
color_mode: ColorMode::Binary,
..Config::default()
};
let svg = config.build().unwrap().to_svg(&img).unwrap();
assert_valid_svg(&svg);
}
#[test]
fn optimize_levels_shrink_or_match() {
let img = two_band_image(48);
let mut sizes = Vec::new();
for level in [0u8, 1, 2] {
let config = Config {
optimize: level,
..Config::default()
};
let svg = config.build().unwrap().to_svg(&img).unwrap();
assert_valid_svg(&svg);
sizes.push(svg.len());
}
// Higher optimization should never produce larger output than level 0.
assert!(sizes[1] <= sizes[0], "opt1 {} > opt0 {}", sizes[1], sizes[0]);
assert!(sizes[2] <= sizes[0], "opt2 {} > opt0 {}", sizes[2], sizes[0]);
}
#[test]
fn cutout_is_reported_unsupported() {
let config = Config {
hierarchical: Hierarchical::Cutout,
..Config::default()
};
let err = config.build().err().expect("cutout should be unsupported");
assert!(err.to_string().contains("mosaic"));
}