4 Commits

Author SHA1 Message Date
Chris Tsang 1adde15f42 Fix the macOS download link 404ing
publicDownloadUrl built the disk image URL from manifest.version, but the
version carries a build suffix the release tag does not: version
"1.0.0-alpha.3.app.59" lives under tag "1.0.0-alpha.3". Every macOS
visitor was handed a 404.

Derive it from the updater URL instead, rewriting only the filename so
the tag comes from the release directory the manifest points at.
2026-08-27 00:00:13 +01:00
Chris Tsang 2a87d8cf82 Fix macOS desktop download link 2026-08-12 14:37:35 +01:00
Chris Tsang 3b76f32cd2 update web app 2026-08-08 22:15:33 +01:00
Chris Tsang 148328da45 Update webapp 2024-05-29 15:01:49 +01:00
21 changed files with 353 additions and 282 deletions
@@ -3,7 +3,7 @@
#
# maturin generate-ci github
#
name: python
name: CI
on:
push:
@@ -102,20 +102,17 @@ jobs:
release:
name: Release
runs-on: ubuntu-latest
# Specifying a GitHub environment is optional, but strongly encouraged
environment: python
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write
if: "startsWith(github.ref, 'refs/tags/')"
needs: [windows, macos, sdist, linux]
needs: [linux, windows, macos, sdist]
steps:
- uses: actions/download-artifact@v3
with:
name: wheels
- name: Publish to PyPI
uses: PyO3/maturin-action@v1
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
with:
command: upload
args: --non-interactive --skip-existing *
# manylinux: auto
manylinux: auto
+3 -13
View File
@@ -5,19 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## 0.6.1 - 2023-09-23
## 0.6.0 - 2023-09-16
* Fixed "The two lines are parallel!"
### Python Binding
Thanks to the contribution of [@etjones](https://github.com/etjones), we now have an official Python binding! https://github.com/visioncortex/vtracer/pull/55
https://pypi.org/project/vtracer/0.6.10/
* Python Binding
## 0.5.0 - 2022-10-09
* Handle transparent png images (cli) https://github.com/visioncortex/vtracer/pull/23
* Handle transparent png images (cli) (#23)
## 0.4.0 - 2021-07-23
@@ -31,10 +25,6 @@ https://pypi.org/project/vtracer/0.6.10/
* Use relative & closed paths
## 0.1.1 - 2020-11-01
* SVG namespace
## 0.1.0 - 2020-10-31
* Initial release
+19 -42
View File
@@ -8,14 +8,14 @@
</p>
<h3>
<a href="https://www.visioncortex.org/vtracer-docs">Article</a>
<a href="//www.visioncortex.org/vtracer-docs">Article</a>
<span> | </span>
<a href="https://www.visioncortex.org/vtracer/">Demo</a>
<a href="//www.visioncortex.org/vtracer/">Demo</a>
<span> | </span>
<a href="https://github.com/visioncortex/vtracer/releases/latest">Download</a>
<a href="//github.com/visioncortex/vtracer/releases/latest">Download</a>
</h3>
<sub>Built with 🦀 by <a href="https://www.visioncortex.org/">The Vision Cortex Research Group</a></sub>
<sub>Built with 🦀 by <a href="//www.visioncortex.org/">The Vision Cortex Research Group</a></sub>
</div>
## Introduction
@@ -28,7 +28,7 @@ Comparing to Adobe Illustrator's [Image Trace](https://helpx.adobe.com/illustrat
VTracer is originally designed for processing high resolution scans of historic blueprints up to gigapixels. At the same time, VTracer can also handle low resolution pixel art, simulating `image-rendering: pixelated` for retro game artworks.
A technical description of the algorithm is on [visioncortex.org/vtracer-docs](https://www.visioncortex.org/vtracer-docs).
A technical description of the algorithm is on [visioncortex.org/vtracer-docs](//www.visioncortex.org/vtracer-docs).
## Web App
@@ -41,7 +41,7 @@ VTracer and its [core library](//github.com/visioncortex/visioncortex) is implem
## Cmd App
```sh
visioncortex VTracer 0.6.0
visioncortex VTracer 0.4.0
A cmd app to convert images into vector graphics.
USAGE:
@@ -71,41 +71,30 @@ OPTIONS:
-s, --splice_threshold <splice_threshold> Minimum angle displacement (degree) to splice a spline
```
### Install
You can download pre-built binaries from [Releases](https://github.com/visioncortex/vtracer/releases).
You can also install the program from source from [crates.io/vtracer](https://crates.io/crates/vtracer):
```sh
cargo install vtracer
```
### Usage
```sh
```
./vtracer --input input.jpg --output output.svg
```
## Rust Library
## Library
You can install [`vtracer`](https://crates.io/crates/vtracer) as a Rust library.
The library can be found on [crates.io/vtracer](//crates.io/crates/vtracer) and [crates.io/vtracer-webapp](//crates.io/crates/vtracer-webapp).
## Install
Download pre-built binaries from [Releases](https://github.com/visioncortex/vtracer/releases).
or
Install from source (Rust toolchain needed):
```sh
cargo add vtracer
```
## Python Library
Since `0.6`, [`vtracer`](https://pypi.org/project/vtracer/) is also packaged as Python native extensions, thanks to the awesome [pyo3](https://github.com/PyO3/pyo3) project.
```sh
pip install vtracer
cargo install vtracer
```
## In the wild
VTracer is used by the following projects (feel free to add yours!):
VTracer is used by the following products (feel free to add yours to the list):
<table>
<tbody>
@@ -117,15 +106,3 @@ VTracer is used by the following projects (feel free to add yours!):
</tr>
</tbody>
</table>
## What's next?
There are several things in my mind:
1. Pencil tracing. Instead of tracing shapes as closed paths, may be we can attempt to skeletonize the shapes as open paths. The output would be clean, fixed width strokes.
2. Perfect cut-out mode. Right now in cut-out mode, the shapes do not share perfect boundaries, but have seams.
3. Image cleaning. Right now the tracer works best on losslessly compressed pngs. If an image suffered from jpeg noises, it could impact the tracing quality. We might be able to develop a pre-filtering pass that denoises the input.
If you are interested in working on them or willing to sponsor its development, feel free to get in touch.
+3 -7
View File
@@ -1,6 +1,6 @@
[package]
name = "vtracer"
version = "0.6.1"
version = "0.6.0"
authors = ["Chris Tsang <chris.2y3@outlook.com>"]
edition = "2021"
description = "A cmd app to convert images into vector graphics."
@@ -13,13 +13,9 @@ keywords = ["svg", "computer-graphics"]
[dependencies]
clap = "2.33.3"
image = "0.23.10"
visioncortex = { version = "0.8.1" }
visioncortex = { version = "0.8.0" }
fastrand = "1.8"
pyo3 = { version = "0.19.0", optional = true }
[features]
python-binding = ["pyo3"]
[lib]
name = "vtracer"
crate-type = ["cdylib"]
python-binding = ["pyo3"]
-96
View File
@@ -1,96 +0,0 @@
<div align="center">
<img src="https://raw.githubusercontent.com/visioncortex/vtracer/master/docs/images/visioncortex-banner.png">
<h1>VTracer</h1>
<p>
<strong>Raster to Vector Graphics Converter built on top of visioncortex</strong>
</p>
<h3>
<a href="https://www.visioncortex.org/vtracer-docs">Article</a>
<span> | </span>
<a href="https://www.visioncortex.org/vtracer/">Demo</a>
<span> | </span>
<a href="https://github.com/visioncortex/vtracer/releases/latest">Download</a>
</h3>
<sub>Built with 🦀 by <a href="https://www.visioncortex.org/">The Vision Cortex Research Group</a></sub>
</div>
## Introduction
visioncortex VTracer is an open source software to convert raster images (like jpg & png) into vector graphics (svg). It can vectorize graphics and photographs and trace the curves to output compact vector files.
Comparing to [Potrace](http://potrace.sourceforge.net/) which only accept binarized inputs (Black & White pixmap), VTracer has an image processing pipeline which can handle colored high resolution scans.
Comparing to Adobe Illustrator's [Image Trace](https://helpx.adobe.com/illustrator/using/image-trace.html), VTracer's output is much more compact (less shapes) as we adopt a stacking strategy and avoid producing shapes with holes.
VTracer is originally designed for processing high resolution scans of historic blueprints up to gigapixels. At the same time, VTracer can also handle low resolution pixel art, simulating `image-rendering: pixelated` for retro game artworks.
A technical description of the algorithm is on [visioncortex.org/vtracer-docs](https://www.visioncortex.org/vtracer-docs).
## Cmd App
```sh
visioncortex VTracer 0.6.0
A cmd app to convert images into vector graphics.
USAGE:
vtracer [OPTIONS] --input <input> --output <output>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--colormode <color_mode> True color image `color` (default) or Binary image `bw`
-p, --color_precision <color_precision> Number of significant bits to use in an RGB channel
-c, --corner_threshold <corner_threshold> Minimum momentary angle (degree) to be considered a corner
-f, --filter_speckle <filter_speckle> Discard patches smaller than X px in size
-g, --gradient_step <gradient_step> Color difference between gradient layers
--hierarchical <hierarchical>
Hierarchical clustering `stacked` (default) or non-stacked `cutout`. Only applies to color mode.
-i, --input <input> Path to input raster image
-m, --mode <mode> Curver fitting mode `pixel`, `polygon`, `spline`
-o, --output <output> Path to output vector graphics
--path_precision <path_precision> Number of decimal places to use in path string
--preset <preset> Use one of the preset configs `bw`, `poster`, `photo`
-l, --segment_length <segment_length>
Perform iterative subdivide smooth until all segments are shorter than this length
-s, --splice_threshold <splice_threshold> Minimum angle displacement (degree) to splice a spline
```
### Install
You can download pre-built binaries from [Releases](https://github.com/visioncortex/vtracer/releases).
You can also install the program from source from [crates.io/vtracer](https://crates.io/crates/vtracer):
```sh
cargo install vtracer
```
### Usage
```sh
./vtracer --input input.jpg --output output.svg
```
## Rust Library
You can install [`vtracer`](https://crates.io/crates/vtracer) as a Rust library.
```sh
cargo add vtracer
```
## Python Library
Since `0.6`, [`vtracer`](https://pypi.org/project/vtracer/) is also packaged as Python native extensions, thanks to the awesome [pyo3](https://github.com/PyO3/pyo3) project.
```sh
pip install vtracer
```
+2 -2
View File
@@ -1,6 +1,6 @@
[project]
name = "vtracer"
version = "0.6.10"
version = "0.6.0"
description = "Python bindings for the Rust Vtracer raster-to-vector library"
authors = [ { name = "Chris Tsang", email = "chris.2y3@outlook.com" } ]
readme = "vtracer/README.md"
@@ -23,6 +23,6 @@ requires = ["maturin>=1.2,<2.0"]
build-backend = "maturin"
[tool.maturin]
features = ["pyo3/extension-module", "python-binding"]
features = ["pyo3/extension-module"]
compatibility = "manylinux2014"
sdist-include = ["LICENSE-MIT", "vtracer/README.md"]
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2023 Tsang Hao Fung. See the COPYRIGHT
// Copyright 2020 Tsang Hao Fung. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
+3 -5
View File
@@ -1,10 +1,8 @@
mod config;
mod converter;
mod svg;
use vtracer::{Config, convert_image_to_svg};
fn main() {
let config = config::Config::from_args();
let result = converter::convert_image_to_svg(config);
let config = Config::from_args();
let result = convert_image_to_svg(config);
match result {
Ok(()) => {
println!("Conversion successful.");
+29 -29
View File
@@ -1,29 +1,28 @@
use crate::*;
use pyo3::prelude::*;
use std::path::PathBuf;
use visioncortex::PathSimplifyMode;
use visioncortex::{PathSimplifyMode};
use super::converter::*;
/// Python binding
#[pyfunction]
fn convert_image_to_svg_py(
image_path: &str,
out_path: &str,
colormode: Option<&str>, // "color" or "binary"
hierarchical: Option<&str>, // "stacked" or "cutout"
mode: Option<&str>, // "polygon", "spline", "none"
filter_speckle: Option<usize>, // default: 4
color_precision: Option<i32>, // default: 6
layer_difference: Option<i32>, // default: 16
corner_threshold: Option<i32>, // default: 60
length_threshold: Option<f64>, // in [3.5, 10] default: 4.0
max_iterations: Option<usize>, // default: 10
splice_threshold: Option<i32>, // default: 45
path_precision: Option<u32>, // default: 8
) -> PyResult<()> {
fn convert_image_to_svg_py( image_path: &str,
out_path: &str,
colormode: Option<&str>, // "color" or "binary"
hierarchical: Option<&str>, // "stacked" or "cutout"
mode: Option<&str>, // "polygon", "spline", "none"
filter_speckle: Option<usize>, // default: 4
color_precision: Option<i32>, // default: 6
layer_difference: Option<i32>, // default: 16
corner_threshold: Option<i32>, // default: 60
length_threshold: Option<f64>, // in [3.5, 10] default: 4.0
max_iterations: Option<usize>, // default: 10
splice_threshold: Option<i32>, // default: 45
path_precision: Option<u32> // default: 8
) -> PyResult<()> {
let input_path = PathBuf::from(image_path);
let output_path = PathBuf::from(out_path);
// TODO: enforce color mode with an enum so that we only
// TODO: enforce color mode with an enum so that we only
// accept the strings 'color' or 'binary'
let color_mode = match colormode.unwrap_or("color") {
"color" => ColorMode::Color,
@@ -36,7 +35,7 @@ fn convert_image_to_svg_py(
"cutout" => Hierarchical::Cutout,
_ => Hierarchical::Stacked,
};
let mode = match mode.unwrap_or("spline") {
"spline" => PathSimplifyMode::Spline,
"polygon" => PathSimplifyMode::Polygon,
@@ -44,13 +43,13 @@ fn convert_image_to_svg_py(
_ => PathSimplifyMode::Spline,
};
let filter_speckle = filter_speckle.unwrap_or(4);
let color_precision = color_precision.unwrap_or(6);
let layer_difference = layer_difference.unwrap_or(16);
let corner_threshold = corner_threshold.unwrap_or(60);
let length_threshold = length_threshold.unwrap_or(4.0);
let splice_threshold = splice_threshold.unwrap_or(45);
let max_iterations = max_iterations.unwrap_or(10);
let filter_speckle = filter_speckle.unwrap_or(4);
let color_precision = color_precision.unwrap_or(6);
let layer_difference = layer_difference.unwrap_or(16);
let corner_threshold = corner_threshold.unwrap_or(60);
let length_threshold = length_threshold.unwrap_or(4.0);
let splice_threshold = splice_threshold.unwrap_or(45);
let max_iterations = max_iterations.unwrap_or(10);
let config = Config {
input_path,
@@ -66,9 +65,10 @@ fn convert_image_to_svg_py(
max_iterations,
splice_threshold,
path_precision,
..Default::default()
..Default::default()
};
convert_image_to_svg(config).unwrap();
Ok(())
}
@@ -78,4 +78,4 @@ fn convert_image_to_svg_py(
fn vtracer(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(convert_image_to_svg_py, m)?)?;
Ok(())
}
}
-1
View File
@@ -34,7 +34,6 @@ impl SvgFile {
impl fmt::Display for SvgFile {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, r#"<?xml version="1.0" encoding="UTF-8"?>"#)?;
writeln!(f, r#"<!-- Generator: visioncortex VTracer {} -->"#, env!("CARGO_PKG_VERSION"))?;
writeln!(f,
r#"<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="{}" height="{}">"#,
self.width, self.height
+6 -6
View File
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1,3 +1,3 @@
Copyright (c) 2020 Tsang Hao Fung
Copyright (c) 2024 TSANG, Hao Fung
The content in the /docs directory is for GitHub pages, and is not covered under open source licenses.
Generated Vendored
+58 -58
View File
@@ -61,92 +61,92 @@
/******/ "__wbindgen_string_new": function(p0i32,p1i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbindgen_string_new"](p0i32,p1i32);
/******/ },
/******/ "__wbg_new_59cb74e423758ede": function() {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_new_59cb74e423758ede"]();
/******/ "__wbg_new_abda76e883ba8a5f": function() {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_new_abda76e883ba8a5f"]();
/******/ },
/******/ "__wbg_stack_558ba5917b466edd": function(p0i32,p1i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_stack_558ba5917b466edd"](p0i32,p1i32);
/******/ "__wbg_stack_658279fe44541cf6": function(p0i32,p1i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_stack_658279fe44541cf6"](p0i32,p1i32);
/******/ },
/******/ "__wbg_error_4bb6c2a97407129a": function(p0i32,p1i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_error_4bb6c2a97407129a"](p0i32,p1i32);
/******/ "__wbg_error_f851667af71bcfc6": function(p0i32,p1i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_error_f851667af71bcfc6"](p0i32,p1i32);
/******/ },
/******/ "__wbg_instanceof_Window_adf3196bdc02b386": function(p0i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_instanceof_Window_adf3196bdc02b386"](p0i32);
/******/ "__wbg_instanceof_Window_f401953a2cf86220": function(p0i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_instanceof_Window_f401953a2cf86220"](p0i32);
/******/ },
/******/ "__wbg_document_6cc8d0b87c0a99b9": function(p0i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_document_6cc8d0b87c0a99b9"](p0i32);
/******/ "__wbg_document_5100775d18896c16": function(p0i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_document_5100775d18896c16"](p0i32);
/******/ },
/******/ "__wbg_createElementNS_ea14cb45a87a0719": function(p0i32,p1i32,p2i32,p3i32,p4i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_createElementNS_ea14cb45a87a0719"](p0i32,p1i32,p2i32,p3i32,p4i32);
/******/ "__wbg_createElementNS_556a62fb298be5a2": function(p0i32,p1i32,p2i32,p3i32,p4i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_createElementNS_556a62fb298be5a2"](p0i32,p1i32,p2i32,p3i32,p4i32);
/******/ },
/******/ "__wbg_getElementById_0cb6ad9511b1efc0": function(p0i32,p1i32,p2i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_getElementById_0cb6ad9511b1efc0"](p0i32,p1i32,p2i32);
/******/ "__wbg_getElementById_c369ff43f0db99cf": function(p0i32,p1i32,p2i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_getElementById_c369ff43f0db99cf"](p0i32,p1i32,p2i32);
/******/ },
/******/ "__wbg_setAttribute_727bdb9763037624": function(p0i32,p1i32,p2i32,p3i32,p4i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_setAttribute_727bdb9763037624"](p0i32,p1i32,p2i32,p3i32,p4i32);
/******/ "__wbg_setAttribute_3c9f6c303b696daa": function(p0i32,p1i32,p2i32,p3i32,p4i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_setAttribute_3c9f6c303b696daa"](p0i32,p1i32,p2i32,p3i32,p4i32);
/******/ },
/******/ "__wbg_prepend_fa995bb42f6e2983": function(p0i32,p1i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_prepend_fa995bb42f6e2983"](p0i32,p1i32);
/******/ "__wbg_prepend_6fcfb183e9fe632c": function(p0i32,p1i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_prepend_6fcfb183e9fe632c"](p0i32,p1i32);
/******/ },
/******/ "__wbg_debug_d101e002eb92f20b": function(p0i32,p1i32,p2i32,p3i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_debug_d101e002eb92f20b"](p0i32,p1i32,p2i32,p3i32);
/******/ "__wbg_instanceof_CanvasRenderingContext2d_20bf99ccc051643b": function(p0i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_instanceof_CanvasRenderingContext2d_20bf99ccc051643b"](p0i32);
/******/ },
/******/ "__wbg_error_cb872335132b1ef7": function(p0i32,p1i32,p2i32,p3i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_error_cb872335132b1ef7"](p0i32,p1i32,p2i32,p3i32);
/******/ "__wbg_getImageData_740186e596b34364": function(p0i32,p1f64,p2f64,p3f64,p4f64) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_getImageData_740186e596b34364"](p0i32,p1f64,p2f64,p3f64,p4f64);
/******/ },
/******/ "__wbg_info_a25afde0ff8cd04a": function(p0i32,p1i32,p2i32,p3i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_info_a25afde0ff8cd04a"](p0i32,p1i32,p2i32,p3i32);
/******/ "__wbg_data_c02d3aac6da15e9f": function(p0i32,p1i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_data_c02d3aac6da15e9f"](p0i32,p1i32);
/******/ },
/******/ "__wbg_log_3bafd82835c6de6d": function(p0i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_log_3bafd82835c6de6d"](p0i32);
/******/ "__wbg_debug_7d879afce6cf56cb": function(p0i32,p1i32,p2i32,p3i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_debug_7d879afce6cf56cb"](p0i32,p1i32,p2i32,p3i32);
/******/ },
/******/ "__wbg_log_64f566ae90a6c43c": function(p0i32,p1i32,p2i32,p3i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_log_64f566ae90a6c43c"](p0i32,p1i32,p2i32,p3i32);
/******/ "__wbg_error_696630710900ec44": function(p0i32,p1i32,p2i32,p3i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_error_696630710900ec44"](p0i32,p1i32,p2i32,p3i32);
/******/ },
/******/ "__wbg_warn_f632d7d3f55682b6": function(p0i32,p1i32,p2i32,p3i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_warn_f632d7d3f55682b6"](p0i32,p1i32,p2i32,p3i32);
/******/ "__wbg_info_80803d9a3f0aad16": function(p0i32,p1i32,p2i32,p3i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_info_80803d9a3f0aad16"](p0i32,p1i32,p2i32,p3i32);
/******/ },
/******/ "__wbg_instanceof_CanvasRenderingContext2d_5b86ec94bce38d5b": function(p0i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_instanceof_CanvasRenderingContext2d_5b86ec94bce38d5b"](p0i32);
/******/ "__wbg_log_5bb5f88f245d7762": function(p0i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_log_5bb5f88f245d7762"](p0i32);
/******/ },
/******/ "__wbg_getImageData_888c08c04395524a": function(p0i32,p1f64,p2f64,p3f64,p4f64) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_getImageData_888c08c04395524a"](p0i32,p1f64,p2f64,p3f64,p4f64);
/******/ "__wbg_log_151eb4333ef0fe39": function(p0i32,p1i32,p2i32,p3i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_log_151eb4333ef0fe39"](p0i32,p1i32,p2i32,p3i32);
/******/ },
/******/ "__wbg_instanceof_HtmlCanvasElement_4f5b5ec6cd53ccf3": function(p0i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_instanceof_HtmlCanvasElement_4f5b5ec6cd53ccf3"](p0i32);
/******/ "__wbg_warn_5d3f783b0bae8943": function(p0i32,p1i32,p2i32,p3i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_warn_5d3f783b0bae8943"](p0i32,p1i32,p2i32,p3i32);
/******/ },
/******/ "__wbg_width_a22f9855caa54b53": function(p0i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_width_a22f9855caa54b53"](p0i32);
/******/ "__wbg_instanceof_HtmlCanvasElement_46bdbf323b0b18d1": function(p0i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_instanceof_HtmlCanvasElement_46bdbf323b0b18d1"](p0i32);
/******/ },
/******/ "__wbg_height_9a404a6b3c61c7ef": function(p0i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_height_9a404a6b3c61c7ef"](p0i32);
/******/ "__wbg_width_aee8b8809b033b05": function(p0i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_width_aee8b8809b033b05"](p0i32);
/******/ },
/******/ "__wbg_getContext_37ca0870acb096d9": function(p0i32,p1i32,p2i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_getContext_37ca0870acb096d9"](p0i32,p1i32,p2i32);
/******/ "__wbg_height_80053d3c71b338e0": function(p0i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_height_80053d3c71b338e0"](p0i32);
/******/ },
/******/ "__wbg_data_c2cd7a48734589b2": function(p0i32,p1i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_data_c2cd7a48734589b2"](p0i32,p1i32);
/******/ "__wbg_getContext_df50fa48a8876636": function(p0i32,p1i32,p2i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_getContext_df50fa48a8876636"](p0i32,p1i32,p2i32);
/******/ },
/******/ "__wbg_call_8e95613cc6524977": function(p0i32,p1i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_call_8e95613cc6524977"](p0i32,p1i32);
/******/ "__wbg_newnoargs_e258087cd0daa0ea": function(p0i32,p1i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_newnoargs_e258087cd0daa0ea"](p0i32,p1i32);
/******/ },
/******/ "__wbg_call_27c0f87801dedf93": function(p0i32,p1i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_call_27c0f87801dedf93"](p0i32,p1i32);
/******/ },
/******/ "__wbindgen_object_clone_ref": function(p0i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbindgen_object_clone_ref"](p0i32);
/******/ },
/******/ "__wbg_newnoargs_f3b8a801d5d4b079": function(p0i32,p1i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_newnoargs_f3b8a801d5d4b079"](p0i32,p1i32);
/******/ "__wbg_self_ce0dbfc45cf2f5be": function() {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_self_ce0dbfc45cf2f5be"]();
/******/ },
/******/ "__wbg_self_07b2f89e82ceb76d": function() {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_self_07b2f89e82ceb76d"]();
/******/ "__wbg_window_c6fb939a7f436783": function() {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_window_c6fb939a7f436783"]();
/******/ },
/******/ "__wbg_window_ba85d88572adc0dc": function() {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_window_ba85d88572adc0dc"]();
/******/ "__wbg_globalThis_d1e6af4856ba331b": function() {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_globalThis_d1e6af4856ba331b"]();
/******/ },
/******/ "__wbg_globalThis_b9277fc37e201fe5": function() {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_globalThis_b9277fc37e201fe5"]();
/******/ },
/******/ "__wbg_global_e16303fe83e1d57f": function() {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_global_e16303fe83e1d57f"]();
/******/ "__wbg_global_207b558942527489": function() {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbg_global_207b558942527489"]();
/******/ },
/******/ "__wbindgen_is_undefined": function(p0i32) {
/******/ return installedModules["../pkg/vtracer_webapp_bg.js"].exports["__wbindgen_is_undefined"](p0i32);
@@ -258,7 +258,7 @@
/******/ promises.push(installedWasmModuleData);
/******/ else {
/******/ var importObject = wasmImportObjects[wasmModuleId]();
/******/ var req = fetch(__webpack_require__.p + "" + {"../pkg/vtracer_webapp_bg.wasm":"589cb57662b50620abca"}[wasmModuleId] + ".module.wasm");
/******/ var req = fetch(__webpack_require__.p + "" + {"../pkg/vtracer_webapp_bg.wasm":"1ea3f1b97c566154f2b2"}[wasmModuleId] + ".module.wasm");
/******/ var promise;
/******/ if(importObject instanceof Promise && typeof WebAssembly.compileStreaming === 'function') {
/******/ promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {
+44
View File
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1783.4 441.4" style="enable-background:new 0 0 1783.4 441.4;" xml:space="preserve">
<style type="text/css">
.st0{fill:#A1FCFE;}
.st1{fill:#010357;}
</style>
<rect class="st0" width="1783.4" height="441.4"/>
<g>
<path class="st1" d="M61.4,393.2V73.8H250V113H109v100.4h124.2v39.7H109v140.1L61.4,393.2L61.4,393.2z"/>
<path class="st1" d="M280,393.2V159.7h45.3v233.5L280,393.2L280,393.2z M330.9,92.4c0-15.5-12.5-28-28-28s-28,12.5-28,28
s12.5,28,28,28S330.9,107.9,330.9,92.4z"/>
<path class="st1" d="M383.5,338.6V57.9h45.3v278.8c0,21,3.7,26.1,18.7,26.1c6.1,0,11.7-0.5,16.8-1.4v30.4
c-9.8,2.8-20.1,4.2-29.9,4.2C400.3,396,383.5,376.9,383.5,338.6z"/>
<path class="st1" d="M480.6,275.5c0-78.5,41.6-121.4,110.2-121.4c31.3,0,55.6,9.8,72.9,29.9c17.3,19.6,26.2,45.3,26.2,77.5
c0,7-0.5,14.9-1.9,24.3H525.4c0.9,50.9,22.4,76.6,64.4,76.6c31.8,0,53.2-14.5,53.2-42H687c0,48.6-39.7,77.5-97.6,77.5
C521.7,397.9,480.6,357.3,480.6,275.5z M645,251.7c-0.9-41.6-19.6-62.1-55.6-62.1c-39.7,0-59.8,24.3-63.5,62.1H645z"/>
<path class="st1" d="M720.7,323.2h44.8c1.9,28.5,18.7,38.8,51.8,38.8c28.5,0,46.7-15,46.7-35c0-20.6-16.8-28.5-48.1-36l-13.1-2.8
l-14.5-3.7l-12.6-4.2c-6.1-1.9-10.7-3.3-13.1-4.7c-5.1-3.3-17.3-8.9-21.5-14.5c-7.5-8.9-15.4-21.5-14-38.8c0-21,8.4-37.8,25.2-50
c17.3-12.1,37.8-18.2,62.1-18.2c55.6,0,91.5,22.4,91.5,72.4h-43c-0.5-26.6-16.8-36.4-49-36.4c-24.3,0-40.6,11.2-40.6,31.3
c0,18.7,15.9,25.7,49,33.6l5.6,1.4c10.3,2.3,18.2,4.7,23.3,6.1c5.1,1.4,12.1,4.2,21,8.4c23.8,10.3,34.6,25.7,36.9,56
c0,43.4-37.8,71-92.5,71C754.8,397.9,720.7,371.7,720.7,323.2z"/>
<path class="st1" d="M964,336.7V192.4h-39.2v-32.7h39.7V94.3h44.8v65.4h64v32.7h-64v143.4c0,17.3,8.9,25.7,26.2,25.7
c14.5,0,28-1.9,39.7-5.1v34.6c-13.6,4.2-28,6.1-43.4,6.1C986.9,397,964,375.5,964,336.7z"/>
<path class="st1" d="M1095.3,325.5c0-44.8,35-69.6,93.4-69.6h58.4v-20.6c0-28.5-16.8-45.8-50-45.8c-35.5,0-51.8,16.8-51.8,41.6
h-43.4c0-44.8,33.2-77.1,95.3-77.1c61.2,0,95.7,30.4,95.7,83.1v92.5c0,26.1,3.7,32.2,17.3,33.6h6.5v29.4
c-4.7,1.4-11.7,2.3-21.5,2.3c-26.6,0-41.6-15.9-43.4-41.6c-8.9,23.8-36.4,44.4-74.3,44.4C1127.5,397.9,1095.3,368.5,1095.3,325.5z
M1247.1,297.5v-9.3h-58.4c-32.2,0-48.6,12.6-48.6,37.4c0,22.9,14,36.9,41.6,36.9C1222.3,362.4,1247.1,334.4,1247.1,297.5z"/>
<path class="st1" d="M1351.7,395.1V161.6h43.9l0.9,36.4h0.9c11.7-24.8,34.1-40.6,62.1-40.6c8.4,0,15.9,0.9,22.9,3.3v38.8
c-7.5-1.4-15.9-1.9-25.7-1.9c-33.2,0-59.3,28-59.3,72.9v124.7H1351.7z"/>
<g>
<g>
<rect x="1549" y="146.5" transform="matrix(0.866 -0.5 0.5 0.866 137.8024 841.2704)" class="st1" width="179.4" height="34"/>
</g>
<g>
<rect x="1621.7" y="73.8" transform="matrix(0.5 -0.866 0.866 0.5 677.7373 1500.9166)" class="st1" width="34" height="179.4"/>
</g>
<g>
<rect x="1621.7" y="73.8" class="st1" width="34" height="179.4"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

+166 -8
View File
@@ -11,12 +11,14 @@
<!-- UIkit JS -->
<script src="./uikit/js/uikit.min.js"></script>
<script src="./uikit/js/uikit-icons.min.js"></script>
<script type="text/javascript">
(function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "403biw7tra");
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XPL1JGMDSC"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XPL1JGMDSC');
</script>
<style>
html, body {
@@ -128,6 +130,35 @@
.uk-text-meta {
font-size: 12px;
}
.desktop-banner {
border: 1px solid rgba(204, 151, 46, 0.55);
background: rgba(204, 151, 46, 0.12);
gap: 8px 16px;
padding-top: 8px;
padding-bottom: 8px;
}
.desktop-banner-title {
margin: 0;
font-weight: 700;
}
.desktop-banner-copy {
margin: 4px 0 0 0;
}
.desktop-banner .uk-button {
white-space: nowrap;
}
.uk-light .desktop-banner {
border-color: rgba(204, 151, 46, 0.7);
background: rgba(204, 151, 46, 0.16);
}
@media only screen and (max-width: 640px) {
.desktop-banner {
text-align: center;
}
.desktop-banner .uk-button {
width: 100%;
}
}
</style>
</head>
@@ -142,13 +173,21 @@
/ VTracer
</div>
<div class="uk-navbar-right uk-padding-small">
<a class="uk-button uk-button-default" href="//www.visioncortex.org/vtracer-docs">Article</a>
&nbsp;&nbsp;
<a class="uk-button uk-button-default" href="//github.com/visioncortex/vtracer">GitHub</a>
&nbsp;&nbsp;
<a id="export" class="uk-button uk-button-primary">Download as SVG</a>
</div>
</div>
<div class="uk-width-1-1">
<div class="desktop-banner uk-padding-small uk-flex uk-flex-middle uk-flex-between uk-flex-wrap">
<div class="uk-margin-small-right">
<p id="desktop-banner-copy" class="desktop-banner-title">Try the latest VTracer Desktop App for native speed, curve simplification, and higher tracing quality.</p>
</div>
<a id="desktop-download" class="uk-button uk-button-primary" href="https://github.com/visioncortex/vtracer/releases/latest" rel="noopener">
Download Desktop App
</a>
</div>
</div>
<div id="progressregion" class="uk-width-1-1 uk-padding-small" style="display: none;">
<progress id="progressbar" class="uk-progress uk-align-right" value="0" max="100" style="width: 98%;"></progress>
</div>
@@ -314,6 +353,125 @@
document.body.classList.remove('uk-dark');
document.body.classList.add('uk-light');
}
(function() {
var releasesUrl = 'https://github.com/visioncortex/vtracer/releases/latest';
var manifestUrl = 'https://raw.githubusercontent.com/visioncortex/vtracer/desktop-updates/latest.json';
var downloadLink = document.getElementById('desktop-download');
var bannerCopy = document.getElementById('desktop-banner-copy');
function detectPlatform() {
var platform = navigator.userAgentData && navigator.userAgentData.platform
? navigator.userAgentData.platform
: navigator.platform || '';
var userAgent = navigator.userAgent || '';
var platformText = platform + ' ' + userAgent;
if (/Windows/i.test(platformText)) {
return {
label: 'Windows',
manifestKeys: ['windows-x86_64']
};
}
if (/Mac|Darwin/i.test(platformText) && !/iPhone|iPad|iPod/i.test(userAgent)) {
return {
label: 'macOS',
manifestKeys: ['macos-universal', 'darwin-universal', 'darwin-aarch64', 'darwin-x86_64']
};
}
if (/Linux|X11/i.test(platformText) && !/Android/i.test(userAgent)) {
return {
label: 'Linux',
manifestKeys: ['linux-x86_64']
};
}
return {
label: '',
manifestKeys: []
};
}
function setFallback(platform) {
downloadLink.href = releasesUrl;
downloadLink.textContent = platform.label ? 'Download for ' + platform.label : 'View latest downloads';
bannerCopy.textContent = platform.label
? 'Try the latest VTracer Desktop App for ' + platform.label + ' with native speed, curve simplification, and higher tracing quality.'
: 'Try the latest VTracer Desktop App for native speed, curve simplification, and higher tracing quality.';
}
function findPlatformDownload(platforms, platform) {
var keys = platform.manifestKeys || [];
for (var i = 0; i < keys.length; i++) {
if (platforms[keys[i]] && platforms[keys[i]].url) {
return platforms[keys[i]].url;
}
}
return '';
}
function publicDownloadUrl(platform, updaterUrl) {
if (platform.label !== 'macOS' || !updaterUrl) {
return updaterUrl;
}
// The manifest points at Tauri's updater bundle; a person wants the
// disk image sitting beside it. Rewrite only the filename, so the
// release tag comes from the URL we were handed: manifest.version
// carries a build suffix the tag does not, and building the URL from
// it 404s (version '1.0.0-alpha.3.app.59' lives under tag
// '1.0.0-alpha.3').
return updaterUrl.replace(/\.app\.tar\.gz$/i, '.dmg');
}
var platform = detectPlatform();
setFallback(platform);
downloadLink.addEventListener('click', function() {
if (typeof gtag !== 'function') {
return;
}
gtag('event', 'desktop_download_click', {
platform: platform.label || 'unknown',
source: 'docs_banner',
download_url: downloadLink.href,
transport_type: 'beacon'
});
});
if (!window.fetch) {
return;
}
fetch(manifestUrl)
.then(function(response) {
if (!response.ok) {
throw new Error('Desktop manifest lookup failed');
}
return response.json();
})
.then(function(manifest) {
var updaterUrl = findPlatformDownload(manifest.platforms || {}, platform);
var downloadUrl = publicDownloadUrl(platform, updaterUrl);
if (!downloadUrl) {
return;
}
downloadLink.href = downloadUrl;
downloadLink.textContent = 'Download for ' + platform.label;
bannerCopy.textContent = 'Try the latest VTracer Desktop App for ' + platform.label + ' with native speed, curve simplification, and higher tracing quality.';
})
.catch(function() {
setFallback(platform);
});
})();
</script>
<script src="./bootstrap.js"></script>
</body>
+1 -1
View File
@@ -22,7 +22,7 @@ console_log = { version = "0.2", features = ["color"] }
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
visioncortex = "0.6.0"
visioncortex = "0.8.1"
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
+6 -2
View File
@@ -35,7 +35,11 @@ document.addEventListener('paste', function (e) {
// Download as SVG
document.getElementById('export').addEventListener('click', function (e) {
const blob = new Blob([new XMLSerializer().serializeToString(svg)], {type: 'octet/stream'}),
const blob = new Blob([
`<?xml version="1.0" encoding="UTF-8"?>\n`,
`<!-- Generator: visioncortex VTracer -->\n`,
new XMLSerializer().serializeToString(svg)
], {type: 'octet/stream'}),
url = window.URL.createObjectURL(blob);
this.href = url;
@@ -444,7 +448,7 @@ class ConverterRunner {
this.converter.init();
this.stopped = false;
if (clustering_mode == 'binary') {
svg.style.background = '#000';
svg.style.background = '#fff';
canvas.style.display = 'none';
} else {
svg.style.background = '';
+1 -1
View File
@@ -77,7 +77,7 @@ impl BinaryImageConverter {
self.params.max_iterations,
self.params.splice_threshold
);
let color = Color::color(&ColorName::White);
let color = Color::color(&ColorName::Black);
self.svg.prepend_path(
&paths,
&color,
+5 -1
View File
@@ -1,6 +1,6 @@
use wasm_bindgen::prelude::*;
use visioncortex::PathSimplifyMode;
use visioncortex::color_clusters::{IncrementalBuilder, Clusters, Runner, RunnerConfig, HIERARCHICAL_MAX};
use visioncortex::color_clusters::{Clusters, Runner, RunnerConfig, HIERARCHICAL_MAX, IncrementalBuilder, KeyingAction};
use crate::canvas::*;
use crate::svg::*;
@@ -78,6 +78,8 @@ impl ColorImageConverter {
is_same_color_b: 1,
deepen_diff: self.params.layer_difference,
hollow_neighbours: 1,
key_color: Default::default(),
keying_action: KeyingAction::Discard,
}, image);
self.stage = Stage::Clustering(runner.start());
}
@@ -108,6 +110,8 @@ impl ColorImageConverter {
is_same_color_b: 1,
deepen_diff: 0,
hollow_neighbours: 0,
key_color: Default::default(),
keying_action: KeyingAction::Discard,
}, image);
self.stage = Stage::Reclustering(runner.start());
},