mirror of
https://github.com/visioncortex/vtracer.git
synced 2026-09-12 07:06:03 -07:00
Compare commits
8 Commits
1.0.0-alpha.4
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| f67142a895 | |||
| 13435cfe1e | |||
| cee33b5a58 | |||
| 0aa9e6a1a4 | |||
| 7bf7525a5a | |||
| e9fece97b8 | |||
| 74c29b1a8c | |||
| 5a69ee33a8 |
@@ -2,6 +2,57 @@
|
|||||||
|
|
||||||
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 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
|
||||||
|
|||||||
@@ -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
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"] }
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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!(
|
||||||
|
|||||||
@@ -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
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user