mirror of
https://github.com/visioncortex/vtracer.git
synced 2026-09-02 10:26:30 -07:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 20187e0993 | |||
| 04d575dec6 | |||
| c8f93acf04 | |||
| a33a659b22 | |||
| 31c3f109a8 | |||
| 5f18837b61 | |||
| 99cb79895b | |||
| 08483eb7a8 | |||
| b5f8753410 | |||
| 6b86baad75 | |||
| ba0ab63a92 | |||
| 3df69f6274 | |||
| 5efd479885 | |||
| 105bdfc7a3 | |||
| d8b6d1cf33 |
+1
-1
@@ -1,6 +1,6 @@
|
||||
[workspace]
|
||||
|
||||
members = [
|
||||
"cmdapp",
|
||||
"cmdapp",
|
||||
"webapp",
|
||||
]
|
||||
+29
-2
@@ -1,6 +1,24 @@
|
||||

|
||||
<div align="center">
|
||||
|
||||
# visioncortex VTracer
|
||||
<img src="docs/images/visioncortex-banner.png">
|
||||
<h1>visioncortex VTracer</h1>
|
||||
|
||||
<p>
|
||||
<strong>Raster to Vector Graphics Converter built on top of visioncortex</strong>
|
||||
</p>
|
||||
|
||||
<h3>
|
||||
<a href="//www.visioncortex.org/vtracer-docs">Document</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.
|
||||
|
||||
@@ -50,3 +68,12 @@ OPTIONS:
|
||||
```
|
||||
./vtracer --input input.jpg --output output.svg
|
||||
```
|
||||
|
||||
## Library
|
||||
|
||||
The library can be found on [crates.io/vtracer](//crates.io/crates/vtracer).
|
||||
|
||||
### Install
|
||||
```
|
||||
vtracer = "0.1.0"
|
||||
```
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
Version 0.2.0 (2020-10-31)
|
||||
==========================
|
||||
|
||||
- Use relative & closed paths
|
||||
|
||||
|
||||
Version 0.1.1 (2020-10-31)
|
||||
==========================
|
||||
|
||||
- SVG namespace
|
||||
|
||||
|
||||
Version 0.1.0 (2020-10-31)
|
||||
==========================
|
||||
|
||||
- Initial release
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "vtracer"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
authors = ["Chris Tsang <tyt2y7@gmail.com>"]
|
||||
edition = "2018"
|
||||
description = "A cmd app to convert images into vector graphics."
|
||||
@@ -13,4 +13,4 @@ keywords = ["svg", "computer-graphics"]
|
||||
[dependencies]
|
||||
clap = "2.33.3"
|
||||
image = "0.23.10"
|
||||
visioncortex = "0.2.0"
|
||||
visioncortex = "0.3.0"
|
||||
@@ -100,7 +100,8 @@ fn path_simplify_mode_from_str(s: &str) -> PathSimplifyMode {
|
||||
|
||||
impl Config {
|
||||
pub fn from_args() -> Self {
|
||||
let app = App::new("visioncortex VTracer").about("A cmd app to convert images into vector graphics.");
|
||||
let app = App::new("visioncortex VTracer ".to_owned() + env!("CARGO_PKG_VERSION"))
|
||||
.about("A cmd app to convert images into vector graphics.");
|
||||
|
||||
let app = app.arg(Arg::with_name("input")
|
||||
.long("input")
|
||||
|
||||
+9
-18
@@ -42,7 +42,7 @@ fn color_image_to_svg(config: ConverterConfig) -> Result<(), String> {
|
||||
let mut svg = SvgFile::new(width, height);
|
||||
for &cluster_index in view.clusters_output.iter().rev() {
|
||||
let cluster = view.get_cluster(cluster_index);
|
||||
let svg_path = cluster.to_svg_path(
|
||||
let paths = cluster.to_compound_path(
|
||||
&view,
|
||||
false,
|
||||
config.mode,
|
||||
@@ -51,22 +51,14 @@ fn color_image_to_svg(config: ConverterConfig) -> Result<(), String> {
|
||||
config.max_iterations,
|
||||
config.splice_threshold
|
||||
);
|
||||
svg.add_path(svg_path, cluster.residue_color());
|
||||
svg.add_path(paths, cluster.residue_color());
|
||||
}
|
||||
|
||||
let out_file = File::create(config.output_path);
|
||||
let mut out_file = match out_file {
|
||||
Ok(file) => file,
|
||||
Err(_) => return Err(String::from("Cannot create output file.")),
|
||||
};
|
||||
|
||||
out_file.write_all(&svg.to_svg_file().as_bytes()).unwrap();
|
||||
|
||||
Ok(())
|
||||
write_svg(svg, config.output_path)
|
||||
}
|
||||
|
||||
fn binary_image_to_svg(config: ConverterConfig) -> Result<(), String> {
|
||||
|
||||
|
||||
let (img, width, height);
|
||||
match read_image(config.input_path) {
|
||||
Ok(values) => {
|
||||
@@ -79,20 +71,19 @@ fn binary_image_to_svg(config: ConverterConfig) -> Result<(), String> {
|
||||
let img = img.to_binary_image(|x| x.r < 128);
|
||||
|
||||
let clusters = img.to_clusters(false);
|
||||
|
||||
|
||||
let mut svg = SvgFile::new(width, height);
|
||||
for i in 0..clusters.len() {
|
||||
let cluster = clusters.get_cluster(i);
|
||||
if cluster.size() >= config.filter_speckle_area {
|
||||
let svg_path = cluster.to_svg_path(
|
||||
let paths = cluster.to_compound_path(
|
||||
config.mode,
|
||||
config.corner_threshold,
|
||||
config.length_threshold,
|
||||
config.max_iterations,
|
||||
config.splice_threshold,
|
||||
);
|
||||
let color = Color::color(&ColorName::Black);
|
||||
svg.add_path(svg_path, color);
|
||||
svg.add_path(paths, Color::color(&ColorName::Black));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,8 +109,8 @@ fn write_svg(svg: SvgFile, output_path: PathBuf) -> Result<(), String> {
|
||||
Ok(file) => file,
|
||||
Err(_) => return Err(String::from("Cannot create output file.")),
|
||||
};
|
||||
|
||||
out_file.write_all(&svg.to_svg_file().as_bytes()).unwrap();
|
||||
|
||||
write!(&mut out_file, "{}", svg).expect("failed to write file.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+36
-22
@@ -1,43 +1,57 @@
|
||||
use visioncortex::Color;
|
||||
|
||||
pub struct SvgPath {
|
||||
path: String,
|
||||
color: Color,
|
||||
}
|
||||
use std::fmt;
|
||||
use visioncortex::{Color, CompoundPath, PointF64};
|
||||
|
||||
pub struct SvgFile {
|
||||
patches: Vec<SvgPath>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
pub paths: Vec<SvgPath>,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
}
|
||||
|
||||
pub struct SvgPath {
|
||||
pub path: CompoundPath,
|
||||
pub color: Color,
|
||||
}
|
||||
|
||||
impl SvgFile {
|
||||
pub fn new(width: usize, height: usize) -> Self {
|
||||
SvgFile {
|
||||
patches: vec![],
|
||||
paths: vec![],
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_path(&mut self, path: String, color: Color) {
|
||||
self.patches.push(SvgPath {
|
||||
pub fn add_path(&mut self, path: CompoundPath, color: Color) {
|
||||
self.paths.push(SvgPath {
|
||||
path,
|
||||
color
|
||||
color,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_svg_file(&self) -> String {
|
||||
let mut result: Vec<String> = vec![format!(r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="{}" height="{}">
|
||||
"#, self.width, self.height)];
|
||||
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#"<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="{}" height="{}">"#,
|
||||
self.width, self.height
|
||||
)?;
|
||||
|
||||
for patch in &self.patches {
|
||||
let color = patch.color;
|
||||
result.push(format!("<path d=\"{}\" fill=\"#{:02x}{:02x}{:02x}\"/>\n", patch.path, color.r, color.g, color.b));
|
||||
for path in &self.paths {
|
||||
path.fmt(f)?;
|
||||
};
|
||||
|
||||
result.push(String::from("</svg>"));
|
||||
result.concat()
|
||||
writeln!(f, "</svg>")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SvgPath {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let (string, offset) = self.path.to_svg_string(true, PointF64::default());
|
||||
writeln!(
|
||||
f, "<path d=\"{}\" fill=\"{}\" transform=\"translate({},{})\"/>",
|
||||
string, self.color.to_hex_string(),
|
||||
offset.x, offset.y
|
||||
)
|
||||
}
|
||||
}
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
Copyright (c) 2020 Tsang Hao Fung
|
||||
|
||||
All Rights Reserved
|
||||
Binary file not shown.
Vendored
+1
-1
@@ -258,7 +258,7 @@
|
||||
/******/ promises.push(installedWasmModuleData);
|
||||
/******/ else {
|
||||
/******/ var importObject = wasmImportObjects[wasmModuleId]();
|
||||
/******/ var req = fetch(__webpack_require__.p + "" + {"../pkg/vtracer_webapp_bg.wasm":"a62b3494f02004811a57"}[wasmModuleId] + ".module.wasm");
|
||||
/******/ var req = fetch(__webpack_require__.p + "" + {"../pkg/vtracer_webapp_bg.wasm":"7ef94c5089d40af744f2"}[wasmModuleId] + ".module.wasm");
|
||||
/******/ var promise;
|
||||
/******/ if(importObject instanceof Promise && typeof WebAssembly.compileStreaming === 'function') {
|
||||
/******/ promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {
|
||||
|
||||
+11
-4
@@ -11,6 +11,13 @@
|
||||
<!-- 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");
|
||||
</script>
|
||||
<style>
|
||||
html, body {
|
||||
width: 100%;
|
||||
@@ -158,7 +165,7 @@
|
||||
<div id="drop" class="uk-padding uk-flex uk-flex-center">
|
||||
<div id="canvas-container" class="uk-width-1-1" style="height: 480px;">
|
||||
<div id="droptext" class="uk-flex uk-flex-middle uk-flex-center">
|
||||
<p>Drag an image here or <a href="#" id="imageSelect">Select file</a></p>
|
||||
<p>Drag an image here, Cmd-V to paste or <a href="#" id="imageSelect">Select file</a></p>
|
||||
</div>
|
||||
<canvas id="frame"></canvas>
|
||||
<svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg"></svg>
|
||||
@@ -195,7 +202,7 @@
|
||||
4
|
||||
</div>
|
||||
<div class="uk-width-5-6">
|
||||
<input id="filterspeckle" class="uk-range" type="range" min="1" max="16" step="1" value="4">
|
||||
<input id="filterspeckle" class="uk-range" type="range" min="1" max="128" step="1" value="4">
|
||||
</div>
|
||||
|
||||
<div class="clustering-color-options uk-width-1-1 uk-flex uk-flex-right">
|
||||
@@ -219,7 +226,7 @@
|
||||
16
|
||||
</div>
|
||||
<div class="clustering-color-options uk-width-5-6">
|
||||
<input id="layerdifference" class="uk-range" type="range" min="0" max="255" step="1" value="16">
|
||||
<input id="layerdifference" class="uk-range" type="range" min="0" max="128" step="1" value="16">
|
||||
</div>
|
||||
|
||||
<div class="uk-width-1-1 uk-flex uk-flex-right">
|
||||
@@ -262,7 +269,7 @@
|
||||
|
||||
<div class="spline-options uk-width-1-1 uk-flex uk-flex-right">
|
||||
<div uk-tooltip="pos: left; title: Minimum Angle Displacement (in degrees) to be considered a cutting point between curves">
|
||||
Splice Threshold <span class="uk-text-meta">(More accurate)</span>
|
||||
Splice Threshold <span class="uk-text-meta">(Less accurate)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="splicevalue" class="spline-options uk-width-1-6">
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Chris Tsang <tyt2y7@gmail.com>"]
|
||||
edition = "2018"
|
||||
description = "A web app to convert images into vector graphics."
|
||||
license = "MIT OR Apache-2.0"
|
||||
homepage = "http://www.visioncortex.org/vtracer"
|
||||
repository = "https://github.com/visioncortex/vtracer/"
|
||||
categories = ["graphics"]
|
||||
@@ -21,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.2.0"
|
||||
visioncortex = "0.3.0"
|
||||
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
|
||||
@@ -430,5 +430,6 @@ class ConverterRunner {
|
||||
|
||||
stop () {
|
||||
this.stopped = true;
|
||||
this.converter.free();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use wasm_bindgen::prelude::*;
|
||||
use visioncortex::{clusters::Clusters, Color, ColorName, PointI32, PathSimplifyMode};
|
||||
use visioncortex::{clusters::Clusters, Color, ColorName, PathSimplifyMode};
|
||||
|
||||
use crate::{canvas::*};
|
||||
use crate::svg::*;
|
||||
@@ -69,7 +69,7 @@ impl BinaryImageConverter {
|
||||
self.canvas.log(&format!("tick {}", self.counter));
|
||||
let cluster = self.clusters.get_cluster(self.counter);
|
||||
if cluster.size() >= self.params.filter_speckle {
|
||||
let svg_path = cluster.to_svg_path(
|
||||
let paths = cluster.to_compound_path(
|
||||
self.mode,
|
||||
self.params.corner_threshold,
|
||||
self.params.length_threshold,
|
||||
@@ -77,9 +77,8 @@ impl BinaryImageConverter {
|
||||
self.params.splice_threshold
|
||||
);
|
||||
let color = Color::color(&ColorName::White);
|
||||
self.svg.prepend_path_with_fill(
|
||||
&svg_path,
|
||||
&PointI32::default(),
|
||||
self.svg.prepend_path(
|
||||
&paths,
|
||||
&color,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use wasm_bindgen::prelude::*;
|
||||
use visioncortex::{PathSimplifyMode, PointI32};
|
||||
use visioncortex::PathSimplifyMode;
|
||||
use visioncortex::color_clusters::{IncrementalBuilder, Clusters, Runner, RunnerConfig};
|
||||
|
||||
use crate::canvas::*;
|
||||
@@ -94,16 +94,15 @@ impl ColorImageConverter {
|
||||
if self.counter < view.clusters_output.len() {
|
||||
self.canvas.log("Vectorize tick");
|
||||
let cluster = view.get_cluster(view.clusters_output[self.counter]);
|
||||
let svg_path = cluster.to_svg_path(
|
||||
let paths = cluster.to_compound_path(
|
||||
&view, false, self.mode,
|
||||
self.params.corner_threshold,
|
||||
self.params.length_threshold,
|
||||
self.params.max_iterations,
|
||||
self.params.splice_threshold
|
||||
);
|
||||
self.svg.prepend_path_with_fill(
|
||||
&svg_path,
|
||||
&PointI32::new(0, 0),
|
||||
self.svg.prepend_path(
|
||||
&paths,
|
||||
&cluster.residue_color(),
|
||||
);
|
||||
self.counter += 1;
|
||||
|
||||
+4
-3
@@ -1,5 +1,5 @@
|
||||
use web_sys::Element;
|
||||
use visioncortex::{Color, PointI32};
|
||||
use visioncortex::{Color, CompoundPath, PointF64};
|
||||
use super::common::document;
|
||||
|
||||
pub struct Svg {
|
||||
@@ -13,11 +13,12 @@ impl Svg {
|
||||
Self { element }
|
||||
}
|
||||
|
||||
pub fn prepend_path_with_fill(&mut self, path_string: &str, offset: &PointI32, color: &Color) {
|
||||
pub fn prepend_path(&mut self, paths: &CompoundPath, color: &Color) {
|
||||
let path = document()
|
||||
.create_element_ns(Some("http://www.w3.org/2000/svg"), "path")
|
||||
.unwrap();
|
||||
path.set_attribute("d", path_string).unwrap();
|
||||
let (string, offset) = paths.to_svg_string(true, PointF64::default());
|
||||
path.set_attribute("d", &string).unwrap();
|
||||
path.set_attribute(
|
||||
"transform",
|
||||
format!("translate({},{})", offset.x, offset.y).as_str(),
|
||||
|
||||
Reference in New Issue
Block a user