mirror of
https://github.com/visioncortex/vtracer.git
synced 2026-09-11 22:56:51 -07:00
Cutout mode
This commit is contained in:
+36
-3
@@ -9,18 +9,22 @@ pub enum Preset {
|
|||||||
Photo
|
Photo
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ColorMode {
|
pub enum ColorMode {
|
||||||
Color,
|
Color,
|
||||||
Binary,
|
Binary,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum Hierarchical {
|
||||||
|
Stacked,
|
||||||
|
Cutout,
|
||||||
|
}
|
||||||
|
|
||||||
/// Converter config
|
/// Converter config
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub input_path: PathBuf,
|
pub input_path: PathBuf,
|
||||||
pub output_path: PathBuf,
|
pub output_path: PathBuf,
|
||||||
pub color_mode: ColorMode,
|
pub color_mode: ColorMode,
|
||||||
|
pub hierarchical: Hierarchical,
|
||||||
pub filter_speckle: usize,
|
pub filter_speckle: usize,
|
||||||
pub color_precision: i32,
|
pub color_precision: i32,
|
||||||
pub layer_difference: i32,
|
pub layer_difference: i32,
|
||||||
@@ -31,11 +35,11 @@ pub struct Config {
|
|||||||
pub splice_threshold: i32,
|
pub splice_threshold: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct ConverterConfig {
|
pub(crate) struct ConverterConfig {
|
||||||
pub input_path: PathBuf,
|
pub input_path: PathBuf,
|
||||||
pub output_path: PathBuf,
|
pub output_path: PathBuf,
|
||||||
pub color_mode: ColorMode,
|
pub color_mode: ColorMode,
|
||||||
|
pub hierarchical: Hierarchical,
|
||||||
pub filter_speckle_area: usize,
|
pub filter_speckle_area: usize,
|
||||||
pub color_precision_loss: i32,
|
pub color_precision_loss: i32,
|
||||||
pub layer_difference: i32,
|
pub layer_difference: i32,
|
||||||
@@ -52,6 +56,7 @@ impl Default for Config {
|
|||||||
input_path: PathBuf::default(),
|
input_path: PathBuf::default(),
|
||||||
output_path: PathBuf::default(),
|
output_path: PathBuf::default(),
|
||||||
color_mode: ColorMode::Color,
|
color_mode: ColorMode::Color,
|
||||||
|
hierarchical: Hierarchical::Stacked,
|
||||||
mode: PathSimplifyMode::Spline,
|
mode: PathSimplifyMode::Spline,
|
||||||
filter_speckle: 4,
|
filter_speckle: 4,
|
||||||
color_precision: 6,
|
color_precision: 6,
|
||||||
@@ -76,6 +81,18 @@ impl FromStr for ColorMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromStr for Hierarchical {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"stacked" => Ok(Self::Stacked),
|
||||||
|
"cutout" => Ok(Self::Cutout),
|
||||||
|
_ => Err(format!("unknown Hierarchical {}", s)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FromStr for Preset {
|
impl FromStr for Preset {
|
||||||
type Err = String;
|
type Err = String;
|
||||||
|
|
||||||
@@ -122,6 +139,14 @@ impl Config {
|
|||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("True color image `color` (default) or Binary image `bw`"));
|
.help("True color image `color` (default) or Binary image `bw`"));
|
||||||
|
|
||||||
|
let app = app.arg(Arg::with_name("hierarchical")
|
||||||
|
.long("hierarchical")
|
||||||
|
.takes_value(true)
|
||||||
|
.help(
|
||||||
|
"Hierarchical clustering `stacked` (default) or non-stacked `cutout`. \
|
||||||
|
Only applies to color mode. "
|
||||||
|
));
|
||||||
|
|
||||||
let app = app.arg(Arg::with_name("preset")
|
let app = app.arg(Arg::with_name("preset")
|
||||||
.long("preset")
|
.long("preset")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
@@ -187,6 +212,10 @@ impl Config {
|
|||||||
config.color_mode = ColorMode::from_str(if value.trim() == "bw" || value.trim() == "BW" {"binary"} else {"color"}).unwrap()
|
config.color_mode = ColorMode::from_str(if value.trim() == "bw" || value.trim() == "BW" {"binary"} else {"color"}).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(value) = matches.value_of("hierarchical") {
|
||||||
|
config.hierarchical = Hierarchical::from_str(value).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(value) = matches.value_of("mode") {
|
if let Some(value) = matches.value_of("mode") {
|
||||||
let value = value.trim();
|
let value = value.trim();
|
||||||
config.mode = path_simplify_mode_from_str(if value == "pixel" {
|
config.mode = path_simplify_mode_from_str(if value == "pixel" {
|
||||||
@@ -283,6 +312,7 @@ impl Config {
|
|||||||
input_path,
|
input_path,
|
||||||
output_path,
|
output_path,
|
||||||
color_mode: ColorMode::Binary,
|
color_mode: ColorMode::Binary,
|
||||||
|
hierarchical: Hierarchical::Stacked,
|
||||||
filter_speckle: 4,
|
filter_speckle: 4,
|
||||||
color_precision: 6,
|
color_precision: 6,
|
||||||
layer_difference: 16,
|
layer_difference: 16,
|
||||||
@@ -296,6 +326,7 @@ impl Config {
|
|||||||
input_path,
|
input_path,
|
||||||
output_path,
|
output_path,
|
||||||
color_mode: ColorMode::Color,
|
color_mode: ColorMode::Color,
|
||||||
|
hierarchical: Hierarchical::Stacked,
|
||||||
filter_speckle: 4,
|
filter_speckle: 4,
|
||||||
color_precision: 8,
|
color_precision: 8,
|
||||||
layer_difference: 16,
|
layer_difference: 16,
|
||||||
@@ -309,6 +340,7 @@ impl Config {
|
|||||||
input_path,
|
input_path,
|
||||||
output_path,
|
output_path,
|
||||||
color_mode: ColorMode::Color,
|
color_mode: ColorMode::Color,
|
||||||
|
hierarchical: Hierarchical::Stacked,
|
||||||
filter_speckle: 10,
|
filter_speckle: 10,
|
||||||
color_precision: 8,
|
color_precision: 8,
|
||||||
layer_difference: 48,
|
layer_difference: 48,
|
||||||
@@ -326,6 +358,7 @@ impl Config {
|
|||||||
input_path: self.input_path,
|
input_path: self.input_path,
|
||||||
output_path: self.output_path,
|
output_path: self.output_path,
|
||||||
color_mode: self.color_mode,
|
color_mode: self.color_mode,
|
||||||
|
hierarchical: self.hierarchical,
|
||||||
filter_speckle_area: self.filter_speckle * self.filter_speckle,
|
filter_speckle_area: self.filter_speckle * self.filter_speckle,
|
||||||
color_precision_loss: 8 - self.color_precision,
|
color_precision_loss: 8 - self.color_precision,
|
||||||
layer_difference: self.layer_difference,
|
layer_difference: self.layer_difference,
|
||||||
|
|||||||
+22
-2
@@ -3,7 +3,7 @@ use std::{fs::File, io::Write};
|
|||||||
|
|
||||||
use visioncortex::{Color, ColorImage, ColorName};
|
use visioncortex::{Color, ColorImage, ColorName};
|
||||||
use visioncortex::color_clusters::{Runner, RunnerConfig, HIERARCHICAL_MAX};
|
use visioncortex::color_clusters::{Runner, RunnerConfig, HIERARCHICAL_MAX};
|
||||||
use super::config::{Config, ColorMode, ConverterConfig};
|
use super::config::{Config, ColorMode, Hierarchical, ConverterConfig};
|
||||||
use super::svg::SvgFile;
|
use super::svg::SvgFile;
|
||||||
|
|
||||||
/// Convert an image file into svg file
|
/// Convert an image file into svg file
|
||||||
@@ -38,7 +38,27 @@ fn color_image_to_svg(config: ConverterConfig) -> Result<(), String> {
|
|||||||
hollow_neighbours: 1,
|
hollow_neighbours: 1,
|
||||||
}, img);
|
}, img);
|
||||||
|
|
||||||
let clusters = runner.run();
|
let mut clusters = runner.run();
|
||||||
|
|
||||||
|
match config.hierarchical {
|
||||||
|
Hierarchical::Stacked => {}
|
||||||
|
Hierarchical::Cutout => {
|
||||||
|
let view = clusters.view();
|
||||||
|
let image = view.to_color_image();
|
||||||
|
let runner = Runner::new(RunnerConfig {
|
||||||
|
diagonal: false,
|
||||||
|
hierarchical: 64,
|
||||||
|
batch_size: 25600,
|
||||||
|
good_min_area: 0,
|
||||||
|
good_max_area: (image.width * image.height) as usize,
|
||||||
|
is_same_color_a: 0,
|
||||||
|
is_same_color_b: 1,
|
||||||
|
deepen_diff: 0,
|
||||||
|
hollow_neighbours: 0,
|
||||||
|
}, image);
|
||||||
|
clusters = runner.run();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
let view = clusters.view();
|
let view = clusters.view();
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<button id="clustering-cutout" title="Shapes disjoint with others">Cutout</button>
|
||||||
|
<button id="clustering-stacked" title="Stack shapes on top of another">Stacked</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div title="Discard patches small than X px in size">
|
<div title="Discard patches small than X px in size">
|
||||||
Filter Speckle <span>(Cleaner)</span>
|
Filter Speckle <span>(Cleaner)</span>
|
||||||
|
|||||||
+26
-2
@@ -7,7 +7,7 @@ const svg = document.getElementById('svg');
|
|||||||
const img = new Image();
|
const img = new Image();
|
||||||
const progress = document.getElementById('progressbar');
|
const progress = document.getElementById('progressbar');
|
||||||
const progressregion = document.getElementById('progressregion');
|
const progressregion = document.getElementById('progressregion');
|
||||||
let mode = 'spline', clustering_mode = 'color';
|
let mode = 'spline', clustering_mode = 'color', clustering_hierarchical = 'stacked';
|
||||||
|
|
||||||
// Hide canas and svg on load
|
// Hide canas and svg on load
|
||||||
canvas.style.display = 'none';
|
canvas.style.display = 'none';
|
||||||
@@ -49,6 +49,7 @@ var presetConfigs = [
|
|||||||
{
|
{
|
||||||
src: 'assets/samples/K1_drawing.jpg',
|
src: 'assets/samples/K1_drawing.jpg',
|
||||||
clustering_mode: 'binary',
|
clustering_mode: 'binary',
|
||||||
|
clustering_hierarchical: 'stacked',
|
||||||
filter_speckle: 4,
|
filter_speckle: 4,
|
||||||
color_precision: 6,
|
color_precision: 6,
|
||||||
layer_difference: 16,
|
layer_difference: 16,
|
||||||
@@ -62,6 +63,7 @@ var presetConfigs = [
|
|||||||
{
|
{
|
||||||
src: 'assets/samples/Cityscape Sunset_DFM3-01.jpg',
|
src: 'assets/samples/Cityscape Sunset_DFM3-01.jpg',
|
||||||
clustering_mode: 'color',
|
clustering_mode: 'color',
|
||||||
|
clustering_hierarchical: 'stacked',
|
||||||
filter_speckle: 4,
|
filter_speckle: 4,
|
||||||
color_precision: 8,
|
color_precision: 8,
|
||||||
layer_difference: 25,
|
layer_difference: 25,
|
||||||
@@ -75,6 +77,7 @@ var presetConfigs = [
|
|||||||
{
|
{
|
||||||
src: 'assets/samples/Gum Tree Vector.jpg',
|
src: 'assets/samples/Gum Tree Vector.jpg',
|
||||||
clustering_mode: 'color',
|
clustering_mode: 'color',
|
||||||
|
clustering_hierarchical: 'cutout',
|
||||||
filter_speckle: 4,
|
filter_speckle: 4,
|
||||||
color_precision: 8,
|
color_precision: 8,
|
||||||
layer_difference: 28,
|
layer_difference: 28,
|
||||||
@@ -88,6 +91,7 @@ var presetConfigs = [
|
|||||||
{
|
{
|
||||||
src: 'assets/samples/vectorstock_31191940.png',
|
src: 'assets/samples/vectorstock_31191940.png',
|
||||||
clustering_mode: 'color',
|
clustering_mode: 'color',
|
||||||
|
clustering_hierarchical: 'stacked',
|
||||||
filter_speckle: 8,
|
filter_speckle: 8,
|
||||||
color_precision: 7,
|
color_precision: 7,
|
||||||
layer_difference: 64,
|
layer_difference: 64,
|
||||||
@@ -101,6 +105,7 @@ var presetConfigs = [
|
|||||||
{
|
{
|
||||||
src: 'assets/samples/angel-luciano-LATYeZyw88c-unsplash-s.jpg',
|
src: 'assets/samples/angel-luciano-LATYeZyw88c-unsplash-s.jpg',
|
||||||
clustering_mode: 'color',
|
clustering_mode: 'color',
|
||||||
|
clustering_hierarchical: 'stacked',
|
||||||
filter_speckle: 10,
|
filter_speckle: 10,
|
||||||
color_precision: 8,
|
color_precision: 8,
|
||||||
layer_difference: 48,
|
layer_difference: 48,
|
||||||
@@ -114,6 +119,7 @@ var presetConfigs = [
|
|||||||
{
|
{
|
||||||
src: 'assets/samples/tank-unit-preview.png',
|
src: 'assets/samples/tank-unit-preview.png',
|
||||||
clustering_mode: 'color',
|
clustering_mode: 'color',
|
||||||
|
clustering_hierarchical: 'stacked',
|
||||||
filter_speckle: 0,
|
filter_speckle: 0,
|
||||||
color_precision: 8,
|
color_precision: 8,
|
||||||
layer_difference: 0,
|
layer_difference: 0,
|
||||||
@@ -127,7 +133,8 @@ var presetConfigs = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Insert gallery items dynamically
|
// Insert gallery items dynamically
|
||||||
for (let i = 0; i < presetConfigs.length; i++) {
|
if (document.getElementById('galleryslider')) {
|
||||||
|
for (let i = 0; i < presetConfigs.length; i++) {
|
||||||
document.getElementById('galleryslider').innerHTML +=
|
document.getElementById('galleryslider').innerHTML +=
|
||||||
`<li>
|
`<li>
|
||||||
<div class="galleryitem uk-panel uk-flex uk-flex-center">
|
<div class="galleryitem uk-panel uk-flex uk-flex-center">
|
||||||
@@ -138,12 +145,14 @@ for (let i = 0; i < presetConfigs.length; i++) {
|
|||||||
</li>`;
|
</li>`;
|
||||||
document.getElementById('credits-modal-content').innerHTML +=
|
document.getElementById('credits-modal-content').innerHTML +=
|
||||||
`<p>${presetConfigs[i].credit}</p>`;
|
`<p>${presetConfigs[i].credit}</p>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to load a given config WITHOUT restarting
|
// Function to load a given config WITHOUT restarting
|
||||||
function loadConfig(config) {
|
function loadConfig(config) {
|
||||||
mode = config.mode;
|
mode = config.mode;
|
||||||
clustering_mode = config.clustering_mode;
|
clustering_mode = config.clustering_mode;
|
||||||
|
clustering_hierarchical = config.clustering_hierarchical;
|
||||||
|
|
||||||
globalcorner = config.corner_threshold;
|
globalcorner = config.corner_threshold;
|
||||||
document.getElementById('cornervalue').innerHTML = globalcorner;
|
document.getElementById('cornervalue').innerHTML = globalcorner;
|
||||||
@@ -272,6 +281,16 @@ document.getElementById('clustering-color').addEventListener('click', function (
|
|||||||
restart();
|
restart();
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
document.getElementById('clustering-cutout').addEventListener('click', function (e) {
|
||||||
|
clustering_hierarchical = 'cutout';
|
||||||
|
restart();
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
document.getElementById('clustering-stacked').addEventListener('click', function (e) {
|
||||||
|
clustering_hierarchical = 'stacked';
|
||||||
|
restart();
|
||||||
|
}, false);
|
||||||
|
|
||||||
document.getElementById('filterspeckle').addEventListener('change', function (e) {
|
document.getElementById('filterspeckle').addEventListener('change', function (e) {
|
||||||
globalfilterspeckle = parseInt(this.value);
|
globalfilterspeckle = parseInt(this.value);
|
||||||
document.getElementById('filterspecklevalue').innerHTML = this.value;
|
document.getElementById('filterspecklevalue').innerHTML = this.value;
|
||||||
@@ -352,6 +371,10 @@ function restart() {
|
|||||||
el.style.display = clustering_mode == 'color' ? '' : 'none';
|
el.style.display = clustering_mode == 'color' ? '' : 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('clustering-cutout').classList.remove('selected');
|
||||||
|
document.getElementById('clustering-stacked').classList.remove('selected');
|
||||||
|
document.getElementById('clustering-' + clustering_hierarchical).classList.add('selected');
|
||||||
|
|
||||||
document.getElementById('none').classList.remove('selected');
|
document.getElementById('none').classList.remove('selected');
|
||||||
document.getElementById('polygon').classList.remove('selected');
|
document.getElementById('polygon').classList.remove('selected');
|
||||||
document.getElementById('spline').classList.remove('selected');
|
document.getElementById('spline').classList.remove('selected');
|
||||||
@@ -373,6 +396,7 @@ function restart() {
|
|||||||
'svg_id': svg.id,
|
'svg_id': svg.id,
|
||||||
'mode': mode,
|
'mode': mode,
|
||||||
'clustering_mode': clustering_mode,
|
'clustering_mode': clustering_mode,
|
||||||
|
'hierarchical': clustering_hierarchical,
|
||||||
'corner_threshold': deg2rad(globalcorner),
|
'corner_threshold': deg2rad(globalcorner),
|
||||||
'length_threshold': globallength,
|
'length_threshold': globallength,
|
||||||
'max_iterations': 10,
|
'max_iterations': 10,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pub struct ColorImageConverterParams {
|
|||||||
pub canvas_id: String,
|
pub canvas_id: String,
|
||||||
pub svg_id: String,
|
pub svg_id: String,
|
||||||
pub mode: String,
|
pub mode: String,
|
||||||
|
pub hierarchical: String,
|
||||||
pub corner_threshold: f64,
|
pub corner_threshold: f64,
|
||||||
pub length_threshold: f64,
|
pub length_threshold: f64,
|
||||||
pub max_iterations: usize,
|
pub max_iterations: usize,
|
||||||
@@ -35,6 +36,7 @@ pub struct ColorImageConverter {
|
|||||||
pub enum Stage {
|
pub enum Stage {
|
||||||
New,
|
New,
|
||||||
Clustering(IncrementalBuilder),
|
Clustering(IncrementalBuilder),
|
||||||
|
Reclustering(IncrementalBuilder),
|
||||||
Vectorize(Clusters),
|
Vectorize(Clusters),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +88,35 @@ impl ColorImageConverter {
|
|||||||
},
|
},
|
||||||
Stage::Clustering(builder) => {
|
Stage::Clustering(builder) => {
|
||||||
self.canvas.log("Clustering tick");
|
self.canvas.log("Clustering tick");
|
||||||
|
if builder.tick() {
|
||||||
|
match self.params.hierarchical.as_str() {
|
||||||
|
"stacked" => {
|
||||||
|
self.stage = Stage::Vectorize(builder.result());
|
||||||
|
},
|
||||||
|
"cutout" => {
|
||||||
|
let clusters = builder.result();
|
||||||
|
let view = clusters.view();
|
||||||
|
let image = view.to_color_image();
|
||||||
|
let runner = Runner::new(RunnerConfig {
|
||||||
|
diagonal: false,
|
||||||
|
hierarchical: 64,
|
||||||
|
batch_size: 25600,
|
||||||
|
good_min_area: 0,
|
||||||
|
good_max_area: (image.width * image.height) as usize,
|
||||||
|
is_same_color_a: 0,
|
||||||
|
is_same_color_b: 1,
|
||||||
|
deepen_diff: 0,
|
||||||
|
hollow_neighbours: 0,
|
||||||
|
}, image);
|
||||||
|
self.stage = Stage::Reclustering(runner.start());
|
||||||
|
},
|
||||||
|
_ => panic!("unknown hierarchical `{}`", self.params.hierarchical)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
},
|
||||||
|
Stage::Reclustering(builder) => {
|
||||||
|
self.canvas.log("Reclustering tick");
|
||||||
if builder.tick() {
|
if builder.tick() {
|
||||||
self.stage = Stage::Vectorize(builder.result())
|
self.stage = Stage::Vectorize(builder.result())
|
||||||
}
|
}
|
||||||
@@ -125,6 +156,9 @@ impl ColorImageConverter {
|
|||||||
Stage::Clustering(builder) => {
|
Stage::Clustering(builder) => {
|
||||||
builder.progress() / 2
|
builder.progress() / 2
|
||||||
},
|
},
|
||||||
|
Stage::Reclustering(_builder) => {
|
||||||
|
50
|
||||||
|
},
|
||||||
Stage::Vectorize(clusters) => {
|
Stage::Vectorize(clusters) => {
|
||||||
50 + 50 * self.counter as u32 / clusters.view().clusters_output.len() as u32
|
50 + 50 * self.counter as u32 / clusters.view().clusters_output.len() as u32
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user