mirror of
https://github.com/visioncortex/vtracer.git
synced 2026-09-09 05:38:49 -07:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 66f6bd98f4 | |||
| 59448884cb | |||
| edf1311425 | |||
| 02c844dc7a | |||
| c3090ac50b | |||
| d5dfa9fd73 | |||
| 685009bd21 | |||
| 971bffa948 | |||
| 7be6882d6b | |||
| 386a0bcaab | |||
| 82284ab470 | |||
| ead104f0ce | |||
| a7219fd370 | |||
| 2448dbb3ba | |||
| c9d9073b17 | |||
| 47091b0bf5 | |||
| 8439cc995d | |||
| 0b292ad35f | |||
| ebb17ac22b | |||
| 227850dd83 | |||
| e0dac19c31 | |||
| d18d4f8b81 | |||
| 60a4ce579f | |||
| c1c09d964c |
@@ -0,0 +1,117 @@
|
|||||||
|
# This file is autogenerated by maturin v1.2.3
|
||||||
|
# To update, run
|
||||||
|
#
|
||||||
|
# maturin generate-ci github
|
||||||
|
#
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
target: [x86_64, x86, aarch64, armv7, s390x, ppc64le]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
- name: Build wheels
|
||||||
|
uses: PyO3/maturin-action@v1
|
||||||
|
with:
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
args: --release --out dist --find-interpreter
|
||||||
|
sccache: 'true'
|
||||||
|
manylinux: auto
|
||||||
|
- name: Upload wheels
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: wheels
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
target: [x64, x86]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
architecture: ${{ matrix.target }}
|
||||||
|
- name: Build wheels
|
||||||
|
uses: PyO3/maturin-action@v1
|
||||||
|
with:
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
args: --release --out dist --find-interpreter
|
||||||
|
sccache: 'true'
|
||||||
|
- name: Upload wheels
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: wheels
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
macos:
|
||||||
|
runs-on: macos-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
target: [x86_64, aarch64]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
- name: Build wheels
|
||||||
|
uses: PyO3/maturin-action@v1
|
||||||
|
with:
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
args: --release --out dist --find-interpreter
|
||||||
|
sccache: 'true'
|
||||||
|
- name: Upload wheels
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: wheels
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
sdist:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Build sdist
|
||||||
|
uses: PyO3/maturin-action@v1
|
||||||
|
with:
|
||||||
|
command: sdist
|
||||||
|
args: --out dist
|
||||||
|
- name: Upload sdist
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: wheels
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: "startsWith(github.ref, 'refs/tags/')"
|
||||||
|
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 *
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
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.0 - 2023-09-08
|
||||||
|
|
||||||
|
* Python Binding
|
||||||
|
|
||||||
|
## 0.5.0 - 2022-10-09
|
||||||
|
|
||||||
|
* Handle transparent png images (cli) (#23)
|
||||||
|
|
||||||
|
## 0.4.0 - 2021-07-23
|
||||||
|
|
||||||
|
* SVG path string numeric precision
|
||||||
|
|
||||||
|
## 0.3.0 - 2021-01-24
|
||||||
|
|
||||||
|
* Added cutout mode
|
||||||
|
|
||||||
|
## 0.2.0 - 2020-11-15
|
||||||
|
|
||||||
|
* Use relative & closed paths
|
||||||
|
|
||||||
|
## 0.1.0 - 2020-10-31
|
||||||
|
|
||||||
|
* Initial release
|
||||||
@@ -4,3 +4,4 @@ members = [
|
|||||||
"cmdapp",
|
"cmdapp",
|
||||||
"webapp",
|
"webapp",
|
||||||
]
|
]
|
||||||
|
resolver = "2"
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
Copyright (c) 2022 Tsang Hao Fung
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
<a href="//www.visioncortex.org/vtracer-docs">Document</a>
|
<a href="//www.visioncortex.org/vtracer-docs">Article</a>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
<a href="//www.visioncortex.org/vtracer/">Demo</a>
|
<a href="//www.visioncortex.org/vtracer/">Demo</a>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
@@ -22,9 +22,9 @@
|
|||||||
|
|
||||||
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.
|
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]() which only accept binarized inputs (Black & White pixmap), VTracer has an image processing pipeline which can handle colored high resolution scans.
|
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 Live Trace, VTracer's output is much more compact (less shapes) as we adopt a stacking strategy and avoid producing shapes with holes.
|
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.
|
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.
|
||||||
|
|
||||||
@@ -32,16 +32,16 @@ A technical description of the algorithm is on [visioncortex.org/vtracer-docs](/
|
|||||||
|
|
||||||
## Web App
|
## Web App
|
||||||
|
|
||||||
VTracer and its [core library](//github.com/visioncortex/visioncortex) is implemented in [Rust](//www.rust-lang.org/). It provides us a solid foundation to develop robust and efficient algorithms and easily bring it to interactive applications. The webapp is a perfect showcase of the capability of the Rust + HTML5 platform.
|
VTracer and its [core library](//github.com/visioncortex/visioncortex) is implemented in [Rust](//www.rust-lang.org/). It provides us a solid foundation to develop robust and efficient algorithms and easily bring it to interactive applications. The webapp is a perfect showcase of the capability of the Rust + wasm platform.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Command Line
|
## Cmd App
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
visioncortex VTracer 0.3.0
|
visioncortex VTracer 0.4.0
|
||||||
A cmd app to convert images into vector graphics.
|
A cmd app to convert images into vector graphics.
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
@@ -63,6 +63,7 @@ OPTIONS:
|
|||||||
-i, --input <input> Path to input raster image
|
-i, --input <input> Path to input raster image
|
||||||
-m, --mode <mode> Curver fitting mode `pixel`, `polygon`, `spline`
|
-m, --mode <mode> Curver fitting mode `pixel`, `polygon`, `spline`
|
||||||
-o, --output <output> Path to output vector graphics
|
-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`
|
--preset <preset> Use one of the preset configs `bw`, `poster`, `photo`
|
||||||
-l, --segment_length <segment_length>
|
-l, --segment_length <segment_length>
|
||||||
Perform iterative subdivide smooth until all segments are shorter than this length
|
Perform iterative subdivide smooth until all segments are shorter than this length
|
||||||
@@ -77,9 +78,31 @@ OPTIONS:
|
|||||||
|
|
||||||
## Library
|
## Library
|
||||||
|
|
||||||
The library can be found on [crates.io/vtracer](//crates.io/crates/vtracer).
|
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):
|
||||||
|
|
||||||
### Install
|
|
||||||
```
|
```
|
||||||
vtracer = "*"
|
cargo install vtracer
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## In the wild
|
||||||
|
|
||||||
|
VTracer is used by the following products (feel free to add yours to the list):
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://logo.aliyun.com/logo#/name"><img src="docs/images/aliyun-logo.png" width="250"/></a>
|
||||||
|
<br>Smart logo design
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|||||||
+17
-12
@@ -1,22 +1,27 @@
|
|||||||
Version 0.3.0 (2021-01-24)
|
# Version 0.6.0 (2023-09-08)
|
||||||
==========================
|
|
||||||
|
|
||||||
- Added cutout mode
|
- Python bindings
|
||||||
|
|
||||||
|
# Version 0.5.0 (2022-10-09)
|
||||||
|
|
||||||
Version 0.2.0 (2020-11-15)
|
- Handle transparent png images
|
||||||
==========================
|
|
||||||
|
|
||||||
- Use relative & closed paths
|
# Version 0.4.0 (2021-07-23)
|
||||||
|
|
||||||
|
- SVG path string numeric precision
|
||||||
|
|
||||||
Version 0.1.1 (2020-11-01)
|
# Version 0.3.0 (2021-01-24)
|
||||||
==========================
|
|
||||||
|
|
||||||
- SVG namespace
|
- Added cutout mode
|
||||||
|
|
||||||
|
# Version 0.2.0 (2020-11-15)
|
||||||
|
|
||||||
Version 0.1.0 (2020-10-31)
|
- Use relative & closed paths
|
||||||
==========================
|
|
||||||
|
|
||||||
- Initial release
|
# Version 0.1.1 (2020-11-01)
|
||||||
|
|
||||||
|
- SVG namespace
|
||||||
|
|
||||||
|
# Version 0.1.0 (2020-10-31)
|
||||||
|
|
||||||
|
- Initial release
|
||||||
|
|||||||
+9
-4
@@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "vtracer"
|
name = "vtracer"
|
||||||
version = "0.4.0"
|
version = "0.6.3"
|
||||||
authors = ["Chris Tsang <tyt2y7@gmail.com>"]
|
authors = ["Chris Tsang <chris.2y3@outlook.com>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
description = "A cmd app to convert images into vector graphics."
|
description = "A cmd app to convert images into vector graphics."
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
homepage = "http://www.visioncortex.org/vtracer"
|
homepage = "http://www.visioncortex.org/vtracer"
|
||||||
@@ -13,4 +13,9 @@ keywords = ["svg", "computer-graphics"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "2.33.3"
|
clap = "2.33.3"
|
||||||
image = "0.23.10"
|
image = "0.23.10"
|
||||||
visioncortex = "0.6.0"
|
visioncortex = { version = "0.8.0" }
|
||||||
|
fastrand = "1.8"
|
||||||
|
pyo3 = { version = "0.19.0", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
python-binding = ["pyo3"]
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2020 Tsang Hao Fung
|
Copyright (c) 2022 Tsang Hao Fung
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any
|
Permission is hereby granted, free of charge, to any
|
||||||
person obtaining a copy of this software and associated
|
person obtaining a copy of this software and associated
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
[project]
|
||||||
|
name = "vtracer"
|
||||||
|
version = "0.6.3"
|
||||||
|
description = "Python bindings for the Rust Vtracer raster-to-vector library"
|
||||||
|
authors = [ { name = "Chris Tsang", email = "chris.2y3@outlook.com" } ]
|
||||||
|
readme = "vtracer/README.md"
|
||||||
|
requires-python = ">=3.7"
|
||||||
|
license = "MIT"
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Rust",
|
||||||
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
python = "^3.7"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
maturin = "^1.2"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["maturin>=1.2,<2.0"]
|
||||||
|
build-backend = "maturin"
|
||||||
|
|
||||||
|
[tool.maturin]
|
||||||
|
features = ["pyo3/extension-module"]
|
||||||
|
compatibility = "linux"
|
||||||
|
sdist-include = ["../LICENSE", "../README.md"]
|
||||||
@@ -200,7 +200,7 @@ impl Config {
|
|||||||
let app = app.arg(Arg::with_name("path_precision")
|
let app = app.arg(Arg::with_name("path_precision")
|
||||||
.long("path_precision")
|
.long("path_precision")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("Number of deciaml places to use in path string"));
|
.help("Number of decimal places to use in path string"));
|
||||||
|
|
||||||
// Extract matches
|
// Extract matches
|
||||||
let matches = app.get_matches();
|
let matches = app.get_matches();
|
||||||
@@ -240,8 +240,8 @@ impl Config {
|
|||||||
if let Some(value) = matches.value_of("filter_speckle") {
|
if let Some(value) = matches.value_of("filter_speckle") {
|
||||||
if value.trim().parse::<usize>().is_ok() { // is numeric
|
if value.trim().parse::<usize>().is_ok() { // is numeric
|
||||||
let value = value.trim().parse::<usize>().unwrap();
|
let value = value.trim().parse::<usize>().unwrap();
|
||||||
if value < 1 || value > 16 {
|
if value > 16 {
|
||||||
panic!("Out of Range Error: Filter speckle is invalid at {}. It must be within [1,16].", value);
|
panic!("Out of Range Error: Filter speckle is invalid at {}. It must be within [0,16].", value);
|
||||||
}
|
}
|
||||||
config.filter_speckle = value;
|
config.filter_speckle = value;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+96
-7
@@ -1,11 +1,17 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{fs::File, io::Write};
|
use std::{fs::File, io::Write};
|
||||||
|
|
||||||
|
use fastrand::Rng;
|
||||||
use visioncortex::{Color, ColorImage, ColorName};
|
use visioncortex::{Color, ColorImage, ColorName};
|
||||||
use visioncortex::color_clusters::{Runner, RunnerConfig, HIERARCHICAL_MAX};
|
use visioncortex::color_clusters::{Runner, RunnerConfig, KeyingAction, HIERARCHICAL_MAX};
|
||||||
use super::config::{Config, ColorMode, Hierarchical, ConverterConfig};
|
use super::config::{Config, ColorMode, Hierarchical, ConverterConfig};
|
||||||
use super::svg::SvgFile;
|
use super::svg::SvgFile;
|
||||||
|
|
||||||
|
const NUM_UNUSED_COLOR_ITERATIONS: usize = 6;
|
||||||
|
/// The fraction of pixels in the top/bottom rows of the image that need to be transparent before
|
||||||
|
/// the entire image will be keyed.
|
||||||
|
const KEYING_THRESHOLD: f32 = 0.2;
|
||||||
|
|
||||||
/// Convert an image file into svg file
|
/// Convert an image file into svg file
|
||||||
pub fn convert_image_to_svg(config: Config) -> Result<(), String> {
|
pub fn convert_image_to_svg(config: Config) -> Result<(), String> {
|
||||||
let config = config.into_converter_config();
|
let config = config.into_converter_config();
|
||||||
@@ -15,8 +21,68 @@ pub fn convert_image_to_svg(config: Config) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn color_exists_in_image(img: &ColorImage, color: Color) -> bool {
|
||||||
|
for y in 0..img.height {
|
||||||
|
for x in 0..img.width {
|
||||||
|
let pixel_color = img.get_pixel(x, y);
|
||||||
|
if pixel_color.r == color.r && pixel_color.g == color.g && pixel_color.b == color.b {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_unused_color_in_image(img: &ColorImage) -> Result<Color, String> {
|
||||||
|
let special_colors = IntoIterator::into_iter([
|
||||||
|
Color::new(255, 0, 0),
|
||||||
|
Color::new(0, 255, 0),
|
||||||
|
Color::new(0, 0, 255),
|
||||||
|
Color::new(255, 255, 0),
|
||||||
|
Color::new(0, 255, 255),
|
||||||
|
Color::new(255, 0, 255),
|
||||||
|
]);
|
||||||
|
let rng = Rng::new();
|
||||||
|
let random_colors = (0..NUM_UNUSED_COLOR_ITERATIONS).map(|_| {
|
||||||
|
Color::new(
|
||||||
|
rng.u8(..),
|
||||||
|
rng.u8(..),
|
||||||
|
rng.u8(..),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
for color in special_colors.chain(random_colors) {
|
||||||
|
if !color_exists_in_image(img, color) {
|
||||||
|
return Ok(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(String::from("unable to find unused color in image to use as key"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_key_image(img: &ColorImage) -> bool {
|
||||||
|
if img.width == 0 || img.height == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for transparency at several scanlines
|
||||||
|
let threshold = ((img.width * 2) as f32 * KEYING_THRESHOLD) as usize;
|
||||||
|
let mut num_transparent_boundary_pixels = 0;
|
||||||
|
let y_positions = [0, img.height / 4, img.height / 2, 3 * img.height / 4, img.height - 1];
|
||||||
|
for y in y_positions {
|
||||||
|
for x in 0..img.width {
|
||||||
|
if img.get_pixel(x, y).a == 0 {
|
||||||
|
num_transparent_boundary_pixels += 1;
|
||||||
|
}
|
||||||
|
if num_transparent_boundary_pixels >= threshold {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn color_image_to_svg(config: ConverterConfig) -> Result<(), String> {
|
fn color_image_to_svg(config: ConverterConfig) -> Result<(), String> {
|
||||||
let (img, width, height);
|
let (mut img, width, height);
|
||||||
match read_image(config.input_path) {
|
match read_image(config.input_path) {
|
||||||
Ok(values) => {
|
Ok(values) => {
|
||||||
img = values.0;
|
img = values.0;
|
||||||
@@ -26,6 +92,21 @@ fn color_image_to_svg(config: ConverterConfig) -> Result<(), String> {
|
|||||||
Err(msg) => return Err(msg),
|
Err(msg) => return Err(msg),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let key_color = if should_key_image(&img) {
|
||||||
|
let key_color = find_unused_color_in_image(&img)?;
|
||||||
|
for y in 0..height {
|
||||||
|
for x in 0..width {
|
||||||
|
if img.get_pixel(x, y).a == 0 {
|
||||||
|
img.set_pixel(x, y, &key_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key_color
|
||||||
|
} else {
|
||||||
|
// The default color is all zeroes, which is treated by visioncortex as a special value meaning no keying will be applied.
|
||||||
|
Color::default()
|
||||||
|
};
|
||||||
|
|
||||||
let runner = Runner::new(RunnerConfig {
|
let runner = Runner::new(RunnerConfig {
|
||||||
diagonal: config.layer_difference == 0,
|
diagonal: config.layer_difference == 0,
|
||||||
hierarchical: HIERARCHICAL_MAX,
|
hierarchical: HIERARCHICAL_MAX,
|
||||||
@@ -36,6 +117,12 @@ fn color_image_to_svg(config: ConverterConfig) -> Result<(), String> {
|
|||||||
is_same_color_b: 1,
|
is_same_color_b: 1,
|
||||||
deepen_diff: config.layer_difference,
|
deepen_diff: config.layer_difference,
|
||||||
hollow_neighbours: 1,
|
hollow_neighbours: 1,
|
||||||
|
key_color,
|
||||||
|
keying_action: if matches!(config.hierarchical, Hierarchical::Cutout) {
|
||||||
|
KeyingAction::Keep
|
||||||
|
} else {
|
||||||
|
KeyingAction::Discard
|
||||||
|
},
|
||||||
}, img);
|
}, img);
|
||||||
|
|
||||||
let mut clusters = runner.run();
|
let mut clusters = runner.run();
|
||||||
@@ -55,6 +142,8 @@ fn color_image_to_svg(config: ConverterConfig) -> Result<(), String> {
|
|||||||
is_same_color_b: 1,
|
is_same_color_b: 1,
|
||||||
deepen_diff: 0,
|
deepen_diff: 0,
|
||||||
hollow_neighbours: 0,
|
hollow_neighbours: 0,
|
||||||
|
key_color,
|
||||||
|
keying_action: KeyingAction::Discard,
|
||||||
}, image);
|
}, image);
|
||||||
clusters = runner.run();
|
clusters = runner.run();
|
||||||
},
|
},
|
||||||
@@ -62,7 +151,7 @@ fn color_image_to_svg(config: ConverterConfig) -> Result<(), String> {
|
|||||||
|
|
||||||
let view = clusters.view();
|
let view = clusters.view();
|
||||||
|
|
||||||
let mut svg = SvgFile::new(width, height);
|
let mut svg = SvgFile::new(width, height, config.path_precision);
|
||||||
for &cluster_index in view.clusters_output.iter().rev() {
|
for &cluster_index in view.clusters_output.iter().rev() {
|
||||||
let cluster = view.get_cluster(cluster_index);
|
let cluster = view.get_cluster(cluster_index);
|
||||||
let paths = cluster.to_compound_path(
|
let paths = cluster.to_compound_path(
|
||||||
@@ -74,7 +163,7 @@ fn color_image_to_svg(config: ConverterConfig) -> Result<(), String> {
|
|||||||
config.max_iterations,
|
config.max_iterations,
|
||||||
config.splice_threshold
|
config.splice_threshold
|
||||||
);
|
);
|
||||||
svg.add_path(paths, cluster.residue_color(), config.path_precision);
|
svg.add_path(paths, cluster.residue_color());
|
||||||
}
|
}
|
||||||
|
|
||||||
write_svg(svg, config.output_path)
|
write_svg(svg, config.output_path)
|
||||||
@@ -95,7 +184,7 @@ fn binary_image_to_svg(config: ConverterConfig) -> Result<(), String> {
|
|||||||
|
|
||||||
let clusters = img.to_clusters(false);
|
let clusters = img.to_clusters(false);
|
||||||
|
|
||||||
let mut svg = SvgFile::new(width, height);
|
let mut svg = SvgFile::new(width, height, config.path_precision);
|
||||||
for i in 0..clusters.len() {
|
for i in 0..clusters.len() {
|
||||||
let cluster = clusters.get_cluster(i);
|
let cluster = clusters.get_cluster(i);
|
||||||
if cluster.size() >= config.filter_speckle_area {
|
if cluster.size() >= config.filter_speckle_area {
|
||||||
@@ -106,7 +195,7 @@ fn binary_image_to_svg(config: ConverterConfig) -> Result<(), String> {
|
|||||||
config.max_iterations,
|
config.max_iterations,
|
||||||
config.splice_threshold,
|
config.splice_threshold,
|
||||||
);
|
);
|
||||||
svg.add_path(paths, Color::color(&ColorName::Black), config.path_precision);
|
svg.add_path(paths, Color::color(&ColorName::Black));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +205,7 @@ fn binary_image_to_svg(config: ConverterConfig) -> Result<(), String> {
|
|||||||
fn read_image(input_path: PathBuf) -> Result<(ColorImage, usize, usize), String> {
|
fn read_image(input_path: PathBuf) -> Result<(ColorImage, usize, usize), String> {
|
||||||
let img = image::open(input_path);
|
let img = image::open(input_path);
|
||||||
let img = match img {
|
let img = match img {
|
||||||
Ok(file) => file.to_rgba(),
|
Ok(file) => file.to_rgba8(),
|
||||||
Err(_) => return Err(String::from("No image file found at specified input path")),
|
Err(_) => return Err(String::from("No image file found at specified input path")),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,11 @@
|
|||||||
mod config;
|
mod config;
|
||||||
mod converter;
|
mod converter;
|
||||||
mod svg;
|
mod svg;
|
||||||
|
#[cfg(feature = "python-binding")]
|
||||||
|
mod python;
|
||||||
|
|
||||||
pub use config::*;
|
pub use config::*;
|
||||||
pub use converter::*;
|
pub use converter::*;
|
||||||
pub use svg::*;
|
pub use svg::*;
|
||||||
|
#[cfg(feature = "python-binding")]
|
||||||
|
pub use python::*;
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
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<()> {
|
||||||
|
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
|
||||||
|
// accept the strings 'color' or 'binary'
|
||||||
|
let color_mode = match colormode.unwrap_or("color") {
|
||||||
|
"color" => ColorMode::Color,
|
||||||
|
"binary" => ColorMode::Binary,
|
||||||
|
_ => ColorMode::Color,
|
||||||
|
};
|
||||||
|
|
||||||
|
let hierarchical = match hierarchical.unwrap_or("stacked") {
|
||||||
|
"stacked" => Hierarchical::Stacked,
|
||||||
|
"cutout" => Hierarchical::Cutout,
|
||||||
|
_ => Hierarchical::Stacked,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mode = match mode.unwrap_or("spline") {
|
||||||
|
"spline" => PathSimplifyMode::Spline,
|
||||||
|
"polygon" => PathSimplifyMode::Polygon,
|
||||||
|
"none" => PathSimplifyMode::None,
|
||||||
|
_ => 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 config = Config {
|
||||||
|
input_path,
|
||||||
|
output_path,
|
||||||
|
color_mode,
|
||||||
|
hierarchical,
|
||||||
|
filter_speckle,
|
||||||
|
color_precision,
|
||||||
|
layer_difference,
|
||||||
|
mode,
|
||||||
|
corner_threshold,
|
||||||
|
length_threshold,
|
||||||
|
max_iterations,
|
||||||
|
splice_threshold,
|
||||||
|
path_precision,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
convert_image_to_svg(config).unwrap();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Python module implemented in Rust.
|
||||||
|
#[pymodule]
|
||||||
|
fn vtracer(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||||
|
m.add_function(wrap_pyfunction!(convert_image_to_svg_py, m)?)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
+12
-6
@@ -5,28 +5,28 @@ pub struct SvgFile {
|
|||||||
pub paths: Vec<SvgPath>,
|
pub paths: Vec<SvgPath>,
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
|
pub path_precision: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SvgPath {
|
pub struct SvgPath {
|
||||||
pub path: CompoundPath,
|
pub path: CompoundPath,
|
||||||
pub color: Color,
|
pub color: Color,
|
||||||
pub path_precision: Option<u32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SvgFile {
|
impl SvgFile {
|
||||||
pub fn new(width: usize, height: usize) -> Self {
|
pub fn new(width: usize, height: usize, path_precision: Option<u32>) -> Self {
|
||||||
SvgFile {
|
SvgFile {
|
||||||
paths: vec![],
|
paths: vec![],
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
path_precision,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_path(&mut self, path: CompoundPath, color: Color, path_precision: Option<u32>) {
|
pub fn add_path(&mut self, path: CompoundPath, color: Color) {
|
||||||
self.paths.push(SvgPath {
|
self.paths.push(SvgPath {
|
||||||
path,
|
path,
|
||||||
color,
|
color,
|
||||||
path_precision,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ impl fmt::Display for SvgFile {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
for path in &self.paths {
|
for path in &self.paths {
|
||||||
path.fmt(f)?;
|
path.fmt_with_precision(f, self.path_precision)?;
|
||||||
};
|
};
|
||||||
|
|
||||||
writeln!(f, "</svg>")
|
writeln!(f, "</svg>")
|
||||||
@@ -49,7 +49,13 @@ impl fmt::Display for SvgFile {
|
|||||||
|
|
||||||
impl fmt::Display for SvgPath {
|
impl fmt::Display for SvgPath {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let (string, offset) = self.path.to_svg_string(true, PointF64::default(), self.path_precision);
|
self.fmt_with_precision(f, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SvgPath {
|
||||||
|
fn fmt_with_precision(&self, f: &mut fmt::Formatter, precision: Option<u32>) -> fmt::Result {
|
||||||
|
let (string, offset) = self.path.to_svg_string(true, PointF64::default(), precision);
|
||||||
writeln!(
|
writeln!(
|
||||||
f, "<path d=\"{}\" fill=\"{}\" transform=\"translate({},{})\"/>",
|
f, "<path d=\"{}\" fill=\"{}\" transform=\"translate({},{})\"/>",
|
||||||
string, self.color.to_hex_string(),
|
string, self.color.to_hex_string(),
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<div align="center">
|
||||||
|
|
||||||
|
<img src="https://github.com/visioncortex/vtracer/raw/master/docs/images/visioncortex-banner.png">
|
||||||
|
|
||||||
|
<h1>VTracer: Python Binding</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>Raster to Vector Graphics Converter built on top of visioncortex</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
<a href="//www.visioncortex.org/vtracer-docs">Article</a>
|
||||||
|
<span> | </span>
|
||||||
|
<a href="//www.visioncortex.org/vtracer/">Demo</a>
|
||||||
|
<span> | </span>
|
||||||
|
<a href="//github.com/visioncortex/vtracer/releases/latest">Download</a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<sub>Built with 🦀 by <a href="//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](//www.visioncortex.org/vtracer-docs).
|
||||||
|
|
||||||
|
## Install (Python)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pip install vtracer
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage (Python)
|
||||||
|
|
||||||
|
```python
|
||||||
|
import vtracer
|
||||||
|
|
||||||
|
input_path = "/path/to/some_file.jpg"
|
||||||
|
output_path = "/path/to/some_file.vtracer.jpg"
|
||||||
|
|
||||||
|
# Minimal example: use all default values, generate a multicolor SVG
|
||||||
|
vtracer.convert_image_to_svg_py(inp, out)
|
||||||
|
|
||||||
|
# Single-color example. Good for line art, and much faster than full color:
|
||||||
|
vtracer.convert_image_to_svg_py(inp, out, colormode='binary')
|
||||||
|
|
||||||
|
# All the bells & whistles
|
||||||
|
vtracer.convert_image_to_svg_py(inp,
|
||||||
|
out,
|
||||||
|
colormode = 'color', # ["color"] or "binary"
|
||||||
|
hierarchical = 'stacked', # ["stacked"] or "cutout"
|
||||||
|
mode = 'spline', # ["spline"] "polygon", or "none"
|
||||||
|
filter_speckle = 4, # default: 4
|
||||||
|
color_precision = 6, # default: 6
|
||||||
|
layer_difference = 16, # default: 16
|
||||||
|
corner_threshold = 60, # default: 60
|
||||||
|
length_threshold = 4.0, # in [3.5, 10] default: 4.0
|
||||||
|
max_iterations = 10, # default: 10
|
||||||
|
splice_threshold = 45, # default: 45
|
||||||
|
path_precision = 3 # default: 8
|
||||||
|
)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rust Library
|
||||||
|
|
||||||
|
The (Rust) library can be found on [crates.io/vtracer](//crates.io/crates/vtracer) and [crates.io/vtracer-webapp](//crates.io/crates/vtracer-webapp).
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
from .vtracer import convert_image_to_svg_py
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
def convert_image_to_svg_py(image_path: str,
|
||||||
|
out_path: str,
|
||||||
|
colormode: Optional[str] = None, # ["color"] or "binary"
|
||||||
|
hierarchical: Optional[str] = None, # ["stacked"] or "cutout"
|
||||||
|
mode: Optional[str] = None, # ["spline"], "polygon", "none"
|
||||||
|
filter_speckle: Optional[int] = None, # default: 4
|
||||||
|
color_precision: Optional[int] = None, # default: 6
|
||||||
|
layer_difference: Optional[int] = None, # default: 16
|
||||||
|
corner_threshold: Optional[int] = None, # default: 60
|
||||||
|
length_threshold: Optional[float] = None, # in [3.5, 10] default: 4.0
|
||||||
|
max_iterations: Optional[int] = None, # default: 10
|
||||||
|
splice_threshold: Optional[int] = None, # default: 45
|
||||||
|
path_precision: Optional[int] = None, # default: 8
|
||||||
|
) -> None:
|
||||||
|
...
|
||||||
Generated
+13
-2
File diff suppressed because one or more lines are too long
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
+1
-1
@@ -258,7 +258,7 @@
|
|||||||
/******/ promises.push(installedWasmModuleData);
|
/******/ promises.push(installedWasmModuleData);
|
||||||
/******/ else {
|
/******/ else {
|
||||||
/******/ var importObject = wasmImportObjects[wasmModuleId]();
|
/******/ var importObject = wasmImportObjects[wasmModuleId]();
|
||||||
/******/ var req = fetch(__webpack_require__.p + "" + {"../pkg/vtracer_webapp_bg.wasm":"bad37dda49ff45dee696"}[wasmModuleId] + ".module.wasm");
|
/******/ var req = fetch(__webpack_require__.p + "" + {"../pkg/vtracer_webapp_bg.wasm":"589cb57662b50620abca"}[wasmModuleId] + ".module.wasm");
|
||||||
/******/ var promise;
|
/******/ var promise;
|
||||||
/******/ if(importObject instanceof Promise && typeof WebAssembly.compileStreaming === 'function') {
|
/******/ if(importObject instanceof Promise && typeof WebAssembly.compileStreaming === 'function') {
|
||||||
/******/ promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {
|
/******/ promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {
|
||||||
|
|||||||
Generated
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Generated
+44
@@ -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 |
Generated
BIN
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
Generated
+15
@@ -142,6 +142,8 @@
|
|||||||
/ VTracer
|
/ VTracer
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-navbar-right uk-padding-small">
|
<div class="uk-navbar-right uk-padding-small">
|
||||||
|
<a class="uk-button uk-button-default" href="//www.visioncortex.org/vtracer-docs">Article</a>
|
||||||
|
|
||||||
<a class="uk-button uk-button-default" href="//github.com/visioncortex/vtracer">GitHub</a>
|
<a class="uk-button uk-button-default" href="//github.com/visioncortex/vtracer">GitHub</a>
|
||||||
|
|
||||||
<a id="export" class="uk-button uk-button-primary">Download as SVG</a>
|
<a id="export" class="uk-button uk-button-primary">Download as SVG</a>
|
||||||
@@ -285,6 +287,19 @@
|
|||||||
<input id="splice" class="uk-range" type="range" min="0" max="180" step="1" value="45">
|
<input id="splice" class="uk-range" type="range" min="0" max="180" step="1" value="45">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div style="display: none">
|
||||||
|
<div class="spline-options uk-width-1-1 uk-flex uk-flex-right">
|
||||||
|
<div uk-tooltip="pos: left; title: Number of decimal places used in svg path string">
|
||||||
|
Path Precision <span class="uk-text-meta">(More digits)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="pathprecisionvalue" class="spline-options uk-width-1-6">
|
||||||
|
8
|
||||||
|
</div>
|
||||||
|
<div class="spline-options uk-width-5-6">
|
||||||
|
<input id="pathprecision" class="uk-range" type="range" min="0" max="16" step="1" value="8">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "vtracer-webapp"
|
name = "vtracer-webapp"
|
||||||
version = "0.1.0"
|
version = "0.4.0"
|
||||||
authors = ["Chris Tsang <tyt2y7@gmail.com>"]
|
authors = ["Chris Tsang <chris.2y3@outlook.com>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
description = "A web app to convert images into vector graphics."
|
description = "A web app to convert images into vector graphics."
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
homepage = "http://www.visioncortex.org/vtracer"
|
homepage = "http://www.visioncortex.org/vtracer"
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
<div align="center">
|
||||||
|
<img src="https://github.com/visioncortex/vtracer/raw/master/docs/images/visioncortex-banner.png">
|
||||||
|
</div>
|
||||||
|
|
||||||
# visioncortex VTracer
|
# visioncortex VTracer
|
||||||
|
|
||||||
A web app to convert raster images into vector graphics.
|
A web app to convert raster images into vector graphics.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "vtracer-app",
|
"name": "vtracer-app",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "VTracer Webapp",
|
"description": "VTracer Webapp",
|
||||||
"author": "Chris Tsang <tyt2y7@gmail.com>",
|
"author": "Chris Tsang <chris.2y3@outlook.com>",
|
||||||
"license": "proprietary",
|
"license": "proprietary",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|||||||
+3
-1
@@ -7,7 +7,9 @@
|
|||||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
#![doc(
|
||||||
|
html_logo_url = "https://github.com/visioncortex/vtracer/raw/master/docs/images/visioncortex icon.png"
|
||||||
|
)]
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
mod conversion;
|
mod conversion;
|
||||||
|
|||||||
Reference in New Issue
Block a user