mirror of
https://github.com/visioncortex/vtracer.git
synced 2026-09-29 15:31:19 -07:00
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.
89 lines
2.5 KiB
Rust
89 lines
2.5 KiB
Rust
//! 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"));
|
||
}
|