9 Commits

Author SHA1 Message Date
Chris Tsang 669a294814 App update 2026-09-12 15:22:46 +01:00
Chris Tsang f67142a895 App update 2026-09-10 13:39:21 +01:00
Chris Tsang 13435cfe1e App update 2026-09-08 20:46:03 +01:00
Chris Tsang cee33b5a58 Engine update
Rust / test (push) Has been cancelled
Rust / wasm-safety (core) (push) Has been cancelled
Rust / Node package (push) Has been cancelled
2026-09-07 01:01:18 +01:00
Chris Tsang 0aa9e6a1a4 App update 2026-09-07 00:49:44 +01:00
Chris Tsang 7bf7525a5a bump vtracer-bench 2026-09-07 00:22:51 +01:00
Chris Tsang e9fece97b8 vtracer-bench: codec-free library, codecs behind a cli feature
Rust / test (push) Has been cancelled
Rust / wasm-safety (core) (push) Has been cancelled
Rust / Node package (push) Has been cancelled
- The library now speaks the image crate's buffer types only:
  fidelity(&RgbImage, &RgbImage, thresh) -> (FidelityReport, GrayImage).
  A tracer embedding the metric pulls no image codec at all.
- New `cli` feature (default on) brings png/jpeg/webp decoding and
  gates the vtracer-bench bin (required-features).
- dssim-core with default features off: drops the rayon thread pool.
- Scores are unchanged: the CLI prints the same value before and after.
2026-09-06 18:49:34 +01:00
Chris Tsang 74c29b1a8c Release app 2026-08-29 22:27:11 +01:00
Chris Tsang 5a69ee33a8 Changelog 2026-08-29 22:14:25 +01:00
9 changed files with 129 additions and 91 deletions
+62
View File
@@ -2,6 +2,68 @@
All notable changes to the VTracer desktop app will be documented in this file. All notable changes to the VTracer desktop app will be documented in this file.
## 1.0.0-alpha.4 - Build 161 - 2026-09-12
### Added
* 简体中文 for UI display language.
### Added (Gen AI)
* Generation on your own sd-server
* Inked, an art style for SDXL-Turbo
## 1.0.0-alpha.4 - Build 151 - 2026-09-09
### Added
* An model manager to install/uninstall VTracer 2 engine versions
* Factory Reset under Settings > Application. It removes everything VTracer 2 and Gen AI keep on the device
### Fixed
* Trial activation issues
## 1.0.0-alpha.4 - Build 143 - 2026-09-08
### Added (Gen AI)
* The FLUX model family, alongside the Stable Diffusion models
* Art styles for the FLUX models
* Starring a round in the left panel, each round also shows the model
* Gradient Step is now determined dynamically, picking the optimal value per artwork
## 1.0.0-alpha.4 - Build 133 - 2026-09-07
### Added
* Gen AI preview. Supports 2 Stable Diffusion model lines, SD Turbo and SDXL Turbo. Requires Vulkan support on Windows. Requires an active VT2 license.
* The app now offer VTracer 2 engine update.
### Changed
* VTracer 2 composites with Stacked rather than Cutout by default.
* An expired VTracer 2 trial no longer locks the images you have already traced. They can still be reopened and exported, and only new tracing needs an active licence.
### Fixed
* Retrying VTracer 2 setup after an interrupted install no longer asks for a second device seat for the same machine.
* A trace with its background removed now sits on a light checkerboard in dark mode.
## 1.0.0-alpha.4 - Build 116 - 2026-08-29
### Added
* VTracer 2 preview. Trial license can be activated in app.
* SVG saving without a dialog. A preferred save folder can be set in Preferences, or the app can keep asking each time.
* A source image display preference, choosing smooth or pixelated scaling for the source image.
* Selected SVG shapes now show their bounds, and can be dragged around the canvas to inspect what sits beneath them.
* A configurable disk cache limit for VTracer 2, defaulting to 2 GB. Lowering it reclaims space immediately.
### Fixed
* Pasting an image now works consistently across the app rather than only when the canvas held focus.
## 1.0.0-alpha.3 - Build 59 - 2026-08-14 ## 1.0.0-alpha.3 - Build 59 - 2026-08-14
### Added ### Added
+15
View File
@@ -0,0 +1,15 @@
# VTracer 2 Changelog
All notable changes to the VTracer 2 engine will be documented in this file.
## 2.0.4 - 2026-09-07
* Engine improvement. Traces small circles better
* Gradient step now allow app to pick optimal value
## 2.0.3 - 2026-08-29
Initial release. Supported systems:
* macOS on Apple Silicon
* Windows 10 x64 with TPM, Windows 11
Generated
+1 -47
View File
@@ -194,31 +194,6 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "crossbeam-deque"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
[[package]] [[package]]
name = "crunchy" name = "crunchy"
version = "0.2.4" version = "0.2.4"
@@ -239,7 +214,6 @@ checksum = "e3c601412450ff29a9258b2f85b18b38f658caf70fad1692f40ca863d86cb753"
dependencies = [ dependencies = [
"imgref", "imgref",
"itertools 0.14.0", "itertools 0.14.0",
"rayon",
"rgb", "rgb",
] ]
@@ -645,26 +619,6 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "rayon"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]] [[package]]
name = "resvg" name = "resvg"
version = "0.45.1" version = "0.45.1"
@@ -994,7 +948,7 @@ dependencies = [
[[package]] [[package]]
name = "vtracer-bench" name = "vtracer-bench"
version = "1.0.0-alpha.4" version = "1.0.0-alpha.6"
dependencies = [ dependencies = [
"dssim-core", "dssim-core",
"image", "image",
+3 -3
View File
@@ -8,11 +8,11 @@
</p> </p>
<h3> <h3>
<a href="https://github.com/visioncortex/vtracer/releases/download/1.0.0-alpha.3/VTracer_1.0.0-alpha.3_x64-setup.exe">Windows App</a> <a href="https://github.com/visioncortex/vtracer/releases/download/1.0.0-alpha.4/VTracer_1.0.0-alpha.4_x64-setup.exe">Windows App</a>
<span> | </span> <span> | </span>
<a href="https://github.com/visioncortex/vtracer/releases/download/1.0.0-alpha.3/VTracer_1.0.0-alpha.3_universal.dmg">macOS App</a> <a href="https://github.com/visioncortex/vtracer/releases/download/1.0.0-alpha.4/VTracer_1.0.0-alpha.4_universal.dmg">macOS App</a>
<span> | </span> <span> | </span>
<a href="https://github.com/visioncortex/vtracer/releases/download/1.0.0-alpha.3/VTracer_1.0.0-alpha.3_x64.AppImage">Linux App</a> <a href="https://github.com/visioncortex/vtracer/releases/download/1.0.0-alpha.4/VTracer_1.0.0-alpha.4_x64.AppImage">Linux App</a>
</h3> </h3>
<p> <p>
+15 -4
View File
@@ -1,7 +1,7 @@
[package] [package]
name = "vtracer-bench" name = "vtracer-bench"
description = "Blind fidelity benchmark for raster-to-vector tracers: compare the original raster with a rendered reconstruction and get one 0..1 fidelity score built from PSNR, SSIM and a clustered-diff patch metric." description = "Blind fidelity benchmark for raster-to-vector tracers: compare the original raster with a rendered reconstruction and get one 0..1 fidelity score built from PSNR, SSIM and a clustered-diff patch metric."
version.workspace = true version = "1.0.0-alpha.6"
authors.workspace = true authors.workspace = true
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
@@ -10,9 +10,20 @@ repository.workspace = true
categories = ["graphics", "development-tools::testing"] categories = ["graphics", "development-tools::testing"]
keywords = ["vectorization", "benchmark", "fidelity", "ssim", "psnr"] keywords = ["vectorization", "benchmark", "fidelity", "ssim", "psnr"]
[features]
default = ["cli"]
# The CLI decodes originals and rendered candidates from files; the
# library itself only speaks the image crate's buffer types, so a
# tracer embedding the metric pulls no codec at all.
cli = ["image/png", "image/jpeg", "image/webp"]
[[bin]]
name = "vtracer-bench"
required-features = ["cli"]
[dependencies] [dependencies]
visioncortex.workspace = true visioncortex.workspace = true
dssim-core = "3" # ~3.4: the only line whose no-`threads` fallback compiles (3.3 and 3.5 both fail)
dssim-core = { version = "~3.4", default-features = false }
rgb = "0.8" rgb = "0.8"
# Decode-only: trimmed to real input formats (drops the AV1 encoder + OpenEXR). image = { version = "0.25", default-features = false }
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp"] }
+21 -20
View File
@@ -26,6 +26,7 @@
//! weight: it tracks visual accuracy best and is the axis most robust to a //! weight: it tracks visual accuracy best and is the axis most robust to a
//! mildly compressed or blurred source. //! mildly compressed or blurred source.
use image::{GrayImage, RgbImage};
use visioncortex::BinaryImage; use visioncortex::BinaryImage;
/// Patch mass fraction that halves the patch subscore. /// Patch mass fraction that halves the patch subscore.
@@ -77,18 +78,14 @@ fn dssim_score(a_rgb: &[u8], b_rgb: &[u8], w: usize, h: usize) -> f64 {
val.into() val.into()
} }
/// Compare an original against a candidate reconstruction, both RGB8, w×h. /// Compare an original against a candidate reconstruction (same
/// `thresh` is the RGB Euclidean bad-pixel gate (use [`DEFAULT_THRESH`]). /// dimensions). `thresh` is the RGB Euclidean bad-pixel gate (use
/// Returns the report plus the bad-pixel mask (255/0, one byte per pixel). /// [`DEFAULT_THRESH`]). Returns the report plus the raw bad-pixel mask
pub fn fidelity( /// (255 = bad).
orig_rgb: &[u8], pub fn fidelity(orig: &RgbImage, cand: &RgbImage, thresh: f64) -> (FidelityReport, GrayImage) {
cand_rgb: &[u8], assert_eq!(orig.dimensions(), cand.dimensions(), "rasters must match in size");
w: usize, let (w, h) = (orig.width() as usize, orig.height() as usize);
h: usize, let (orig_rgb, cand_rgb) = (orig.as_raw().as_slice(), cand.as_raw().as_slice());
thresh: f64,
) -> (FidelityReport, Vec<u8>) {
assert_eq!(orig_rgb.len(), w * h * 3);
assert_eq!(cand_rgb.len(), w * h * 3);
// PSNR + RMSE + bad-pixel binarization in one pass // PSNR + RMSE + bad-pixel binarization in one pass
let mut sse = 0f64; let mut sse = 0f64;
@@ -185,7 +182,7 @@ pub fn fidelity(
s_patch, s_patch,
fidelity, fidelity,
}, },
mask, GrayImage::from_raw(w as u32, h as u32, mask).expect("mask dims"),
) )
} }
@@ -197,12 +194,16 @@ mod tests {
(0..w * h).flat_map(|_| c).collect() (0..w * h).flat_map(|_| c).collect()
} }
fn img(w: usize, h: usize, px: &[u8]) -> RgbImage {
RgbImage::from_raw(w as u32, h as u32, px.to_vec()).unwrap()
}
#[test] #[test]
fn identical_is_one() { fn identical_is_one() {
let a = flat(64, 64, [120, 90, 200]); let a = flat(64, 64, [120, 90, 200]);
let (r, mask) = fidelity(&a, &a, 64, 64, DEFAULT_THRESH); let (r, mask) = fidelity(&img(64, 64, &a), &img(64, 64, &a), DEFAULT_THRESH);
assert_eq!(r.bad_px, 0); assert_eq!(r.bad_px, 0);
assert!(mask.iter().all(|&m| m == 0)); assert!(mask.as_raw().iter().all(|&m| m == 0));
assert!((r.fidelity - 1.0).abs() < 1e-9, "fidelity {}", r.fidelity); assert!((r.fidelity - 1.0).abs() < 1e-9, "fidelity {}", r.fidelity);
} }
@@ -221,8 +222,8 @@ mod tests {
let (x, y) = ((k % 16) * 4, (k / 16) * 4); // 4px spacing: 256 singleton clusters let (x, y) = ((k % 16) * 4, (k / 16) * 4); // 4px spacing: 256 singleton clusters
dust[(y * 64 + x) * 3..(y * 64 + x) * 3 + 3].fill(0); dust[(y * 64 + x) * 3..(y * 64 + x) * 3 + 3].fill(0);
} }
let (rb, _) = fidelity(&clean, &blob, 64, 64, DEFAULT_THRESH); let (rb, _) = fidelity(&img(64, 64, &clean), &img(64, 64, &blob), DEFAULT_THRESH);
let (rd, _) = fidelity(&clean, &dust, 64, 64, DEFAULT_THRESH); let (rd, _) = fidelity(&img(64, 64, &clean), &img(64, 64, &dust), DEFAULT_THRESH);
assert_eq!(rb.bad_px, 256); assert_eq!(rb.bad_px, 256);
assert_eq!(rd.bad_px, 256); assert_eq!(rd.bad_px, 256);
// dust vanishes under the opening entirely; the blob survives // dust vanishes under the opening entirely; the blob survives
@@ -252,7 +253,7 @@ mod tests {
fil[(y * 64 + x) * 3..(y * 64 + x) * 3 + 3].fill(0); fil[(y * 64 + x) * 3..(y * 64 + x) * 3 + 3].fill(0);
} }
} }
let (rf, _) = fidelity(&clean, &fil, 64, 64, DEFAULT_THRESH); let (rf, _) = fidelity(&img(64, 64, &clean), &img(64, 64, &fil), DEFAULT_THRESH);
assert_eq!(rf.bad_px, 128); assert_eq!(rf.bad_px, 128);
assert_eq!(rf.clusters, 0); assert_eq!(rf.clusters, 0);
assert!( assert!(
@@ -266,8 +267,8 @@ mod tests {
let a = flat(32, 32, [100, 100, 100]); let a = flat(32, 32, [100, 100, 100]);
let mild: Vec<u8> = a.iter().map(|&v| v + 4).collect(); let mild: Vec<u8> = a.iter().map(|&v| v + 4).collect();
let harsh: Vec<u8> = a.iter().map(|&v| v + 60).collect(); let harsh: Vec<u8> = a.iter().map(|&v| v + 60).collect();
let (rm, _) = fidelity(&a, &mild, 32, 32, DEFAULT_THRESH); let (rm, _) = fidelity(&img(32, 32, &a), &img(32, 32, &mild), DEFAULT_THRESH);
let (rh, _) = fidelity(&a, &harsh, 32, 32, DEFAULT_THRESH); let (rh, _) = fidelity(&img(32, 32, &a), &img(32, 32, &harsh), DEFAULT_THRESH);
assert!(rm.fidelity > rh.fidelity); assert!(rm.fidelity > rh.fidelity);
assert!(rh.fidelity < 0.4, "harsh {}", rh.fidelity); assert!(rh.fidelity < 0.4, "harsh {}", rh.fidelity);
} }
+4 -13
View File
@@ -37,22 +37,13 @@ fn main() {
} }
let orig = image::open(&args[1]).expect("open original").to_rgb8(); let orig = image::open(&args[1]).expect("open original").to_rgb8();
let (w, h) = (orig.width() as usize, orig.height() as usize); let cand = image::open(&args[2]).expect("open candidate").to_rgb8();
let img = image::open(&args[2]).expect("open candidate").to_rgb8(); assert_eq!(cand.dimensions(), orig.dimensions(), "candidate raster must match original dimensions");
assert_eq!(
(img.width() as usize, img.height() as usize),
(w, h),
"candidate raster must match original dimensions"
);
let cand: Vec<u8> = img.into_raw();
let (r, mask) = fidelity(orig.as_raw(), &cand, w, h, thresh); let (r, mask) = fidelity(&orig, &cand, thresh);
if let Some(out) = mask_out { if let Some(out) = mask_out {
image::GrayImage::from_raw(w as u32, h as u32, mask) mask.save(&out).expect("save mask");
.unwrap()
.save(&out)
.expect("save mask");
} }
println!( println!(
+4 -2
View File
@@ -8,9 +8,11 @@
</p> </p>
<h3> <h3>
<a href="https://www.visioncortex.org/vtracer/">Web App</a> <a href="https://github.com/visioncortex/vtracer/releases/download/1.0.0-alpha.4/VTracer_1.0.0-alpha.4_x64-setup.exe">Windows App</a>
<span> | </span> <span> | </span>
<a href="https://github.com/visioncortex/vtracer/releases/download/1.0.0-alpha.3/VTracer_1.0.0-alpha.3_x64-setup.exe">Windows App</a> <a href="https://github.com/visioncortex/vtracer/releases/download/1.0.0-alpha.4/VTracer_1.0.0-alpha.4_universal.dmg">macOS App</a>
<span> | </span>
<a href="https://github.com/visioncortex/vtracer/releases/download/1.0.0-alpha.4/VTracer_1.0.0-alpha.4_x64.AppImage">Linux App</a>
</h3> </h3>
</div> </div>
+4 -2
View File
@@ -8,9 +8,11 @@
</p> </p>
<h3> <h3>
<a href="https://www.visioncortex.org/vtracer/">Web App</a> <a href="https://github.com/visioncortex/vtracer/releases/download/1.0.0-alpha.4/VTracer_1.0.0-alpha.4_x64-setup.exe">Windows App</a>
<span> | </span> <span> | </span>
<a href="https://github.com/visioncortex/vtracer/releases/download/1.0.0-alpha.3/VTracer_1.0.0-alpha.3_x64-setup.exe">Windows App</a> <a href="https://github.com/visioncortex/vtracer/releases/download/1.0.0-alpha.4/VTracer_1.0.0-alpha.4_universal.dmg">macOS App</a>
<span> | </span>
<a href="https://github.com/visioncortex/vtracer/releases/download/1.0.0-alpha.4/VTracer_1.0.0-alpha.4_x64.AppImage">Linux App</a>
</h3> </h3>
</div> </div>