mirror of
https://github.com/visioncortex/vtracer.git
synced 2025-12-06 17:15:41 -08:00
cargo fmt
This commit is contained in:
@@ -4,7 +4,7 @@ use visioncortex::PathSimplifyMode;
|
|||||||
pub enum Preset {
|
pub enum Preset {
|
||||||
Bw,
|
Bw,
|
||||||
Poster,
|
Poster,
|
||||||
Photo
|
Photo,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ColorMode {
|
pub enum ColorMode {
|
||||||
@@ -142,7 +142,7 @@ impl Config {
|
|||||||
max_iterations: 10,
|
max_iterations: 10,
|
||||||
splice_threshold: 45,
|
splice_threshold: 45,
|
||||||
path_precision: Some(8),
|
path_precision: Some(8),
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::{fs::File, io::Write};
|
use std::{fs::File, io::Write};
|
||||||
|
|
||||||
use fastrand::Rng;
|
use super::config::{ColorMode, Config, ConverterConfig, Hierarchical};
|
||||||
use visioncortex::{Color, ColorImage, ColorName};
|
|
||||||
use visioncortex::color_clusters::{Runner, RunnerConfig, KeyingAction, HIERARCHICAL_MAX};
|
|
||||||
use super::config::{Config, ColorMode, Hierarchical, ConverterConfig};
|
|
||||||
use super::svg::SvgFile;
|
use super::svg::SvgFile;
|
||||||
|
use fastrand::Rng;
|
||||||
|
use visioncortex::color_clusters::{KeyingAction, Runner, RunnerConfig, HIERARCHICAL_MAX};
|
||||||
|
use visioncortex::{Color, ColorImage, ColorName};
|
||||||
|
|
||||||
const NUM_UNUSED_COLOR_ITERATIONS: usize = 6;
|
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 fraction of pixels in the top/bottom rows of the image that need to be transparent before
|
||||||
@@ -22,7 +22,11 @@ pub fn convert(img: ColorImage, config: Config) -> Result<SvgFile, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert an image file into svg file
|
/// Convert an image file into svg file
|
||||||
pub fn convert_image_to_svg(input_path: &Path, output_path: &Path, config: Config) -> Result<(), String> {
|
pub fn convert_image_to_svg(
|
||||||
|
input_path: &Path,
|
||||||
|
output_path: &Path,
|
||||||
|
config: Config,
|
||||||
|
) -> Result<(), String> {
|
||||||
let img = read_image(input_path)?;
|
let img = read_image(input_path)?;
|
||||||
let svg = convert(img, config)?;
|
let svg = convert(img, config)?;
|
||||||
write_svg(svg, output_path)
|
write_svg(svg, output_path)
|
||||||
@@ -33,7 +37,7 @@ fn color_exists_in_image(img: &ColorImage, color: Color) -> bool {
|
|||||||
for x in 0..img.width {
|
for x in 0..img.width {
|
||||||
let pixel_color = img.get_pixel(x, y);
|
let pixel_color = img.get_pixel(x, y);
|
||||||
if pixel_color.r == color.r && pixel_color.g == color.g && pixel_color.b == color.b {
|
if pixel_color.r == color.r && pixel_color.g == color.g && pixel_color.b == color.b {
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,27 +46,24 @@ fn color_exists_in_image(img: &ColorImage, color: Color) -> bool {
|
|||||||
|
|
||||||
fn find_unused_color_in_image(img: &ColorImage) -> Result<Color, String> {
|
fn find_unused_color_in_image(img: &ColorImage) -> Result<Color, String> {
|
||||||
let special_colors = IntoIterator::into_iter([
|
let special_colors = IntoIterator::into_iter([
|
||||||
Color::new(255, 0, 0),
|
Color::new(255, 0, 0),
|
||||||
Color::new(0, 255, 0),
|
Color::new(0, 255, 0),
|
||||||
Color::new(0, 0, 255),
|
Color::new(0, 0, 255),
|
||||||
Color::new(255, 255, 0),
|
Color::new(255, 255, 0),
|
||||||
Color::new(0, 255, 255),
|
Color::new(0, 255, 255),
|
||||||
Color::new(255, 0, 255),
|
Color::new(255, 0, 255),
|
||||||
]);
|
]);
|
||||||
let rng = Rng::new();
|
let rng = Rng::new();
|
||||||
let random_colors = (0..NUM_UNUSED_COLOR_ITERATIONS).map(|_| {
|
let random_colors =
|
||||||
Color::new(
|
(0..NUM_UNUSED_COLOR_ITERATIONS).map(|_| Color::new(rng.u8(..), rng.u8(..), rng.u8(..)));
|
||||||
rng.u8(..),
|
|
||||||
rng.u8(..),
|
|
||||||
rng.u8(..),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
for color in special_colors.chain(random_colors) {
|
for color in special_colors.chain(random_colors) {
|
||||||
if !color_exists_in_image(img, color) {
|
if !color_exists_in_image(img, color) {
|
||||||
return Ok(color);
|
return Ok(color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(String::from("unable to find unused color in image to use as key"))
|
Err(String::from(
|
||||||
|
"unable to find unused color in image to use as key",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_key_image(img: &ColorImage) -> bool {
|
fn should_key_image(img: &ColorImage) -> bool {
|
||||||
@@ -73,7 +74,13 @@ fn should_key_image(img: &ColorImage) -> bool {
|
|||||||
// Check for transparency at several scanlines
|
// Check for transparency at several scanlines
|
||||||
let threshold = ((img.width * 2) as f32 * KEYING_THRESHOLD) as usize;
|
let threshold = ((img.width * 2) as f32 * KEYING_THRESHOLD) as usize;
|
||||||
let mut num_transparent_boundary_pixels = 0;
|
let mut num_transparent_boundary_pixels = 0;
|
||||||
let y_positions = [0, img.height / 4, img.height / 2, 3 * img.height / 4, img.height - 1];
|
let y_positions = [
|
||||||
|
0,
|
||||||
|
img.height / 4,
|
||||||
|
img.height / 2,
|
||||||
|
3 * img.height / 4,
|
||||||
|
img.height - 1,
|
||||||
|
];
|
||||||
for y in y_positions {
|
for y in y_positions {
|
||||||
for x in 0..img.width {
|
for x in 0..img.width {
|
||||||
if img.get_pixel(x, y).a == 0 {
|
if img.get_pixel(x, y).a == 0 {
|
||||||
@@ -107,23 +114,26 @@ fn color_image_to_svg(mut img: ColorImage, config: ConverterConfig) -> Result<Sv
|
|||||||
Color::default()
|
Color::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let runner = Runner::new(RunnerConfig {
|
let runner = Runner::new(
|
||||||
diagonal: config.layer_difference == 0,
|
RunnerConfig {
|
||||||
hierarchical: HIERARCHICAL_MAX,
|
diagonal: config.layer_difference == 0,
|
||||||
batch_size: 25600,
|
hierarchical: HIERARCHICAL_MAX,
|
||||||
good_min_area: config.filter_speckle_area,
|
batch_size: 25600,
|
||||||
good_max_area: (width * height),
|
good_min_area: config.filter_speckle_area,
|
||||||
is_same_color_a: config.color_precision_loss,
|
good_max_area: (width * height),
|
||||||
is_same_color_b: 1,
|
is_same_color_a: config.color_precision_loss,
|
||||||
deepen_diff: config.layer_difference,
|
is_same_color_b: 1,
|
||||||
hollow_neighbours: 1,
|
deepen_diff: config.layer_difference,
|
||||||
key_color,
|
hollow_neighbours: 1,
|
||||||
keying_action: if matches!(config.hierarchical, Hierarchical::Cutout) {
|
key_color,
|
||||||
KeyingAction::Keep
|
keying_action: if matches!(config.hierarchical, Hierarchical::Cutout) {
|
||||||
} else {
|
KeyingAction::Keep
|
||||||
KeyingAction::Discard
|
} else {
|
||||||
|
KeyingAction::Discard
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, img);
|
img,
|
||||||
|
);
|
||||||
|
|
||||||
let mut clusters = runner.run();
|
let mut clusters = runner.run();
|
||||||
|
|
||||||
@@ -132,21 +142,24 @@ fn color_image_to_svg(mut img: ColorImage, config: ConverterConfig) -> Result<Sv
|
|||||||
Hierarchical::Cutout => {
|
Hierarchical::Cutout => {
|
||||||
let view = clusters.view();
|
let view = clusters.view();
|
||||||
let image = view.to_color_image();
|
let image = view.to_color_image();
|
||||||
let runner = Runner::new(RunnerConfig {
|
let runner = Runner::new(
|
||||||
diagonal: false,
|
RunnerConfig {
|
||||||
hierarchical: 64,
|
diagonal: false,
|
||||||
batch_size: 25600,
|
hierarchical: 64,
|
||||||
good_min_area: 0,
|
batch_size: 25600,
|
||||||
good_max_area: (image.width * image.height) as usize,
|
good_min_area: 0,
|
||||||
is_same_color_a: 0,
|
good_max_area: (image.width * image.height) as usize,
|
||||||
is_same_color_b: 1,
|
is_same_color_a: 0,
|
||||||
deepen_diff: 0,
|
is_same_color_b: 1,
|
||||||
hollow_neighbours: 0,
|
deepen_diff: 0,
|
||||||
key_color,
|
hollow_neighbours: 0,
|
||||||
keying_action: KeyingAction::Discard,
|
key_color,
|
||||||
}, image);
|
keying_action: KeyingAction::Discard,
|
||||||
|
},
|
||||||
|
image,
|
||||||
|
);
|
||||||
clusters = runner.run();
|
clusters = runner.run();
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let view = clusters.view();
|
let view = clusters.view();
|
||||||
@@ -161,7 +174,7 @@ fn color_image_to_svg(mut img: ColorImage, config: ConverterConfig) -> Result<Sv
|
|||||||
config.corner_threshold,
|
config.corner_threshold,
|
||||||
config.length_threshold,
|
config.length_threshold,
|
||||||
config.max_iterations,
|
config.max_iterations,
|
||||||
config.splice_threshold
|
config.splice_threshold,
|
||||||
);
|
);
|
||||||
svg.add_path(paths, cluster.residue_color());
|
svg.add_path(paths, cluster.residue_color());
|
||||||
}
|
}
|
||||||
@@ -202,7 +215,11 @@ fn read_image(input_path: &Path) -> Result<ColorImage, String> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let (width, height) = (img.width() as usize, img.height() as usize);
|
let (width, height) = (img.width() as usize, img.height() as usize);
|
||||||
let img = ColorImage {pixels: img.as_raw().to_vec(), width, height};
|
let img = ColorImage {
|
||||||
|
pixels: img.as_raw().to_vec(),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(img)
|
Ok(img)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,13 @@
|
|||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod converter;
|
mod converter;
|
||||||
mod svg;
|
|
||||||
#[cfg(feature = "python-binding")]
|
#[cfg(feature = "python-binding")]
|
||||||
mod python;
|
mod python;
|
||||||
|
mod svg;
|
||||||
|
|
||||||
pub use config::*;
|
pub use config::*;
|
||||||
pub use converter::*;
|
pub use converter::*;
|
||||||
pub use svg::*;
|
|
||||||
#[cfg(feature = "python-binding")]
|
#[cfg(feature = "python-binding")]
|
||||||
pub use python::*;
|
pub use python::*;
|
||||||
|
pub use svg::*;
|
||||||
pub use visioncortex::ColorImage;
|
pub use visioncortex::ColorImage;
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ mod config;
|
|||||||
mod converter;
|
mod converter;
|
||||||
mod svg;
|
mod svg;
|
||||||
|
|
||||||
use std::str::FromStr;
|
use clap::{App, Arg};
|
||||||
|
use config::{ColorMode, Config, Hierarchical, Preset};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use clap::{Arg, App};
|
use std::str::FromStr;
|
||||||
use visioncortex::PathSimplifyMode;
|
use visioncortex::PathSimplifyMode;
|
||||||
use config::{Config, Preset, ColorMode, Hierarchical};
|
|
||||||
|
|
||||||
fn path_simplify_mode_from_str(s: &str) -> PathSimplifyMode {
|
fn path_simplify_mode_from_str(s: &str) -> PathSimplifyMode {
|
||||||
match s {
|
match s {
|
||||||
@@ -21,61 +21,79 @@ pub fn config_from_args() -> (PathBuf, PathBuf, Config) {
|
|||||||
let app = App::new("visioncortex VTracer ".to_owned() + env!("CARGO_PKG_VERSION"))
|
let app = App::new("visioncortex VTracer ".to_owned() + env!("CARGO_PKG_VERSION"))
|
||||||
.about("A cmd app to convert images into vector graphics.");
|
.about("A cmd app to convert images into vector graphics.");
|
||||||
|
|
||||||
let app = app.arg(Arg::with_name("input")
|
let app = app.arg(
|
||||||
.long("input")
|
Arg::with_name("input")
|
||||||
.short("i")
|
.long("input")
|
||||||
.takes_value(true)
|
.short("i")
|
||||||
.help("Path to input raster image")
|
.takes_value(true)
|
||||||
.required(true));
|
.help("Path to input raster image")
|
||||||
|
.required(true),
|
||||||
|
);
|
||||||
|
|
||||||
let app = app.arg(Arg::with_name("output")
|
let app = app.arg(
|
||||||
.long("output")
|
Arg::with_name("output")
|
||||||
.short("o")
|
.long("output")
|
||||||
.takes_value(true)
|
.short("o")
|
||||||
.help("Path to output vector graphics")
|
.takes_value(true)
|
||||||
.required(true));
|
.help("Path to output vector graphics")
|
||||||
|
.required(true),
|
||||||
|
);
|
||||||
|
|
||||||
let app = app.arg(Arg::with_name("color_mode")
|
let app = app.arg(
|
||||||
.long("colormode")
|
Arg::with_name("color_mode")
|
||||||
.takes_value(true)
|
.long("colormode")
|
||||||
.help("True color image `color` (default) or Binary image `bw`"));
|
.takes_value(true)
|
||||||
|
.help("True color image `color` (default) or Binary image `bw`"),
|
||||||
|
);
|
||||||
|
|
||||||
let app = app.arg(Arg::with_name("hierarchical")
|
let app = app.arg(
|
||||||
.long("hierarchical")
|
Arg::with_name("hierarchical")
|
||||||
.takes_value(true)
|
.long("hierarchical")
|
||||||
.help(
|
.takes_value(true)
|
||||||
"Hierarchical clustering `stacked` (default) or non-stacked `cutout`. \
|
.help(
|
||||||
Only applies to color mode. "
|
"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(
|
||||||
.long("preset")
|
Arg::with_name("preset")
|
||||||
.takes_value(true)
|
.long("preset")
|
||||||
.help("Use one of the preset configs `bw`, `poster`, `photo`"));
|
.takes_value(true)
|
||||||
|
.help("Use one of the preset configs `bw`, `poster`, `photo`"),
|
||||||
|
);
|
||||||
|
|
||||||
let app = app.arg(Arg::with_name("filter_speckle")
|
let app = app.arg(
|
||||||
.long("filter_speckle")
|
Arg::with_name("filter_speckle")
|
||||||
.short("f")
|
.long("filter_speckle")
|
||||||
.takes_value(true)
|
.short("f")
|
||||||
.help("Discard patches smaller than X px in size"));
|
.takes_value(true)
|
||||||
|
.help("Discard patches smaller than X px in size"),
|
||||||
|
);
|
||||||
|
|
||||||
let app = app.arg(Arg::with_name("color_precision")
|
let app = app.arg(
|
||||||
.long("color_precision")
|
Arg::with_name("color_precision")
|
||||||
.short("p")
|
.long("color_precision")
|
||||||
.takes_value(true)
|
.short("p")
|
||||||
.help("Number of significant bits to use in an RGB channel"));
|
.takes_value(true)
|
||||||
|
.help("Number of significant bits to use in an RGB channel"),
|
||||||
|
);
|
||||||
|
|
||||||
let app = app.arg(Arg::with_name("gradient_step")
|
let app = app.arg(
|
||||||
.long("gradient_step")
|
Arg::with_name("gradient_step")
|
||||||
.short("g")
|
.long("gradient_step")
|
||||||
.takes_value(true)
|
.short("g")
|
||||||
.help("Color difference between gradient layers"));
|
.takes_value(true)
|
||||||
|
.help("Color difference between gradient layers"),
|
||||||
|
);
|
||||||
|
|
||||||
let app = app.arg(Arg::with_name("corner_threshold")
|
let app = app.arg(
|
||||||
.long("corner_threshold")
|
Arg::with_name("corner_threshold")
|
||||||
.short("c")
|
.long("corner_threshold")
|
||||||
.takes_value(true)
|
.short("c")
|
||||||
.help("Minimum momentary angle (degree) to be considered a corner"));
|
.takes_value(true)
|
||||||
|
.help("Minimum momentary angle (degree) to be considered a corner"),
|
||||||
|
);
|
||||||
|
|
||||||
let app = app.arg(Arg::with_name("segment_length")
|
let app = app.arg(Arg::with_name("segment_length")
|
||||||
.long("segment_length")
|
.long("segment_length")
|
||||||
@@ -83,29 +101,39 @@ pub fn config_from_args() -> (PathBuf, PathBuf, Config) {
|
|||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("Perform iterative subdivide smooth until all segments are shorter than this length"));
|
.help("Perform iterative subdivide smooth until all segments are shorter than this length"));
|
||||||
|
|
||||||
let app = app.arg(Arg::with_name("splice_threshold")
|
let app = app.arg(
|
||||||
.long("splice_threshold")
|
Arg::with_name("splice_threshold")
|
||||||
.short("s")
|
.long("splice_threshold")
|
||||||
.takes_value(true)
|
.short("s")
|
||||||
.help("Minimum angle displacement (degree) to splice a spline"));
|
.takes_value(true)
|
||||||
|
.help("Minimum angle displacement (degree) to splice a spline"),
|
||||||
|
);
|
||||||
|
|
||||||
let app = app.arg(Arg::with_name("mode")
|
let app = app.arg(
|
||||||
.long("mode")
|
Arg::with_name("mode")
|
||||||
.short("m")
|
.long("mode")
|
||||||
.takes_value(true)
|
.short("m")
|
||||||
.help("Curver fitting mode `pixel`, `polygon`, `spline`"));
|
.takes_value(true)
|
||||||
|
.help("Curver fitting mode `pixel`, `polygon`, `spline`"),
|
||||||
|
);
|
||||||
|
|
||||||
let app = app.arg(Arg::with_name("path_precision")
|
let app = app.arg(
|
||||||
.long("path_precision")
|
Arg::with_name("path_precision")
|
||||||
.takes_value(true)
|
.long("path_precision")
|
||||||
.help("Number of decimal places to use in path string"));
|
.takes_value(true)
|
||||||
|
.help("Number of decimal places to use in path string"),
|
||||||
|
);
|
||||||
|
|
||||||
// Extract matches
|
// Extract matches
|
||||||
let matches = app.get_matches();
|
let matches = app.get_matches();
|
||||||
|
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
let input_path = matches.value_of("input").expect("Input path is required, please specify it by --input or -i.");
|
let input_path = matches
|
||||||
let output_path = matches.value_of("output").expect("Output path is required, please specify it by --output or -o.");
|
.value_of("input")
|
||||||
|
.expect("Input path is required, please specify it by --input or -i.");
|
||||||
|
let output_path = matches
|
||||||
|
.value_of("output")
|
||||||
|
.expect("Output path is required, please specify it by --output or -o.");
|
||||||
|
|
||||||
let input_path = PathBuf::from(input_path);
|
let input_path = PathBuf::from(input_path);
|
||||||
let output_path = PathBuf::from(output_path);
|
let output_path = PathBuf::from(output_path);
|
||||||
@@ -115,7 +143,12 @@ pub fn config_from_args() -> (PathBuf, PathBuf, Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = matches.value_of("color_mode") {
|
if let Some(value) = matches.value_of("color_mode") {
|
||||||
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") {
|
if let Some(value) = matches.value_of("hierarchical") {
|
||||||
@@ -136,31 +169,40 @@ pub fn config_from_args() -> (PathBuf, PathBuf, 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 > 16 {
|
if value > 16 {
|
||||||
panic!("Out of Range Error: Filter speckle is invalid at {}. It must be within [0,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 {
|
||||||
panic!("Parser Error: Filter speckle is not a positive integer: {}.", value);
|
panic!(
|
||||||
|
"Parser Error: Filter speckle is not a positive integer: {}.",
|
||||||
|
value
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = matches.value_of("color_precision") {
|
if let Some(value) = matches.value_of("color_precision") {
|
||||||
if value.trim().parse::<i32>().is_ok() { // is numeric
|
if value.trim().parse::<i32>().is_ok() {
|
||||||
|
// is numeric
|
||||||
let value = value.trim().parse::<i32>().unwrap();
|
let value = value.trim().parse::<i32>().unwrap();
|
||||||
if value < 1 || value > 8 {
|
if value < 1 || value > 8 {
|
||||||
panic!("Out of Range Error: Color precision is invalid at {}. It must be within [1,8].", value);
|
panic!("Out of Range Error: Color precision is invalid at {}. It must be within [1,8].", value);
|
||||||
}
|
}
|
||||||
config.color_precision = value;
|
config.color_precision = value;
|
||||||
} else {
|
} else {
|
||||||
panic!("Parser Error: Color precision is not an integer: {}.", value);
|
panic!(
|
||||||
|
"Parser Error: Color precision is not an integer: {}.",
|
||||||
|
value
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = matches.value_of("gradient_step") {
|
if let Some(value) = matches.value_of("gradient_step") {
|
||||||
if value.trim().parse::<i32>().is_ok() { // is numeric
|
if value.trim().parse::<i32>().is_ok() {
|
||||||
|
// is numeric
|
||||||
let value = value.trim().parse::<i32>().unwrap();
|
let value = value.trim().parse::<i32>().unwrap();
|
||||||
if value < 0 || value > 255 {
|
if value < 0 || value > 255 {
|
||||||
panic!("Out of Range Error: Gradient step is invalid at {}. It must be within [0,255].", value);
|
panic!("Out of Range Error: Gradient step is invalid at {}. It must be within [0,255].", value);
|
||||||
@@ -172,7 +214,8 @@ pub fn config_from_args() -> (PathBuf, PathBuf, Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = matches.value_of("corner_threshold") {
|
if let Some(value) = matches.value_of("corner_threshold") {
|
||||||
if value.trim().parse::<i32>().is_ok() { // is numeric
|
if value.trim().parse::<i32>().is_ok() {
|
||||||
|
// is numeric
|
||||||
let value = value.trim().parse::<i32>().unwrap();
|
let value = value.trim().parse::<i32>().unwrap();
|
||||||
if value < 0 || value > 180 {
|
if value < 0 || value > 180 {
|
||||||
panic!("Out of Range Error: Corner threshold is invalid at {}. It must be within [0,180].", value);
|
panic!("Out of Range Error: Corner threshold is invalid at {}. It must be within [0,180].", value);
|
||||||
@@ -184,7 +227,8 @@ pub fn config_from_args() -> (PathBuf, PathBuf, Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = matches.value_of("segment_length") {
|
if let Some(value) = matches.value_of("segment_length") {
|
||||||
if value.trim().parse::<f64>().is_ok() { // is numeric
|
if value.trim().parse::<f64>().is_ok() {
|
||||||
|
// is numeric
|
||||||
let value = value.trim().parse::<f64>().unwrap();
|
let value = value.trim().parse::<f64>().unwrap();
|
||||||
if value < 3.5 || value > 10.0 {
|
if value < 3.5 || value > 10.0 {
|
||||||
panic!("Out of Range Error: Segment length is invalid at {}. It must be within [3.5,10].", value);
|
panic!("Out of Range Error: Segment length is invalid at {}. It must be within [3.5,10].", value);
|
||||||
@@ -196,7 +240,8 @@ pub fn config_from_args() -> (PathBuf, PathBuf, Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = matches.value_of("splice_threshold") {
|
if let Some(value) = matches.value_of("splice_threshold") {
|
||||||
if value.trim().parse::<i32>().is_ok() { // is numeric
|
if value.trim().parse::<i32>().is_ok() {
|
||||||
|
// is numeric
|
||||||
let value = value.trim().parse::<i32>().unwrap();
|
let value = value.trim().parse::<i32>().unwrap();
|
||||||
if value < 0 || value > 180 {
|
if value < 0 || value > 180 {
|
||||||
panic!("Out of Range Error: Segment length is invalid at {}. It must be within [0,180].", value);
|
panic!("Out of Range Error: Segment length is invalid at {}. It must be within [0,180].", value);
|
||||||
@@ -208,11 +253,15 @@ pub fn config_from_args() -> (PathBuf, PathBuf, Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = matches.value_of("path_precision") {
|
if let Some(value) = matches.value_of("path_precision") {
|
||||||
if value.trim().parse::<u32>().is_ok() { // is numeric
|
if value.trim().parse::<u32>().is_ok() {
|
||||||
|
// is numeric
|
||||||
let value = value.trim().parse::<u32>().ok();
|
let value = value.trim().parse::<u32>().ok();
|
||||||
config.path_precision = value;
|
config.path_precision = value;
|
||||||
} else {
|
} else {
|
||||||
panic!("Parser Error: Path precision is not an unsigned integer: {}.", value);
|
panic!(
|
||||||
|
"Parser Error: Path precision is not an unsigned integer: {}.",
|
||||||
|
value
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +274,7 @@ fn main() {
|
|||||||
match result {
|
match result {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
println!("Conversion successful.");
|
println!("Conversion successful.");
|
||||||
},
|
}
|
||||||
Err(msg) => {
|
Err(msg) => {
|
||||||
panic!("Conversion failed with error message: {}", msg);
|
panic!("Conversion failed with error message: {}", msg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,25 +24,27 @@ impl SvgFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_path(&mut self, path: CompoundPath, color: Color) {
|
pub fn add_path(&mut self, path: CompoundPath, color: Color) {
|
||||||
self.paths.push(SvgPath {
|
self.paths.push(SvgPath { path, color })
|
||||||
path,
|
|
||||||
color,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for SvgFile {
|
impl fmt::Display for SvgFile {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
writeln!(f, r#"<?xml version="1.0" encoding="UTF-8"?>"#)?;
|
writeln!(f, r#"<?xml version="1.0" encoding="UTF-8"?>"#)?;
|
||||||
writeln!(f, r#"<!-- Generator: visioncortex VTracer {} -->"#, env!("CARGO_PKG_VERSION"))?;
|
writeln!(
|
||||||
writeln!(f,
|
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="{}">"#,
|
r#"<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="{}" height="{}">"#,
|
||||||
self.width, self.height
|
self.width, self.height
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
for path in &self.paths {
|
for path in &self.paths {
|
||||||
path.fmt_with_precision(f, self.path_precision)?;
|
path.fmt_with_precision(f, self.path_precision)?;
|
||||||
};
|
}
|
||||||
|
|
||||||
writeln!(f, "</svg>")
|
writeln!(f, "</svg>")
|
||||||
}
|
}
|
||||||
@@ -56,11 +58,16 @@ impl fmt::Display for SvgPath {
|
|||||||
|
|
||||||
impl SvgPath {
|
impl SvgPath {
|
||||||
fn fmt_with_precision(&self, f: &mut fmt::Formatter, precision: Option<u32>) -> fmt::Result {
|
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);
|
let (string, offset) = self
|
||||||
|
.path
|
||||||
|
.to_svg_string(true, PointF64::default(), precision);
|
||||||
writeln!(
|
writeln!(
|
||||||
f, "<path d=\"{}\" fill=\"{}\" transform=\"translate({},{})\"/>",
|
f,
|
||||||
string, self.color.to_hex_string(),
|
"<path d=\"{}\" fill=\"{}\" transform=\"translate({},{})\"/>",
|
||||||
offset.x, offset.y
|
string,
|
||||||
|
self.color.to_hex_string(),
|
||||||
|
offset.x,
|
||||||
|
offset.y
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user