From 5cefa694d319edb630aa8eec1c0dfa4e875c5854 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Fri, 20 Oct 2023 20:49:42 +0200 Subject: [PATCH 1/4] Move path handling out of conversion functions This lets us decouple file reading/writing from the actual conversion. --- cmdapp/src/config.rs | 6 +---- cmdapp/src/converter.rs | 52 ++++++++++++++++------------------------- 2 files changed, 21 insertions(+), 37 deletions(-) diff --git a/cmdapp/src/config.rs b/cmdapp/src/config.rs index c031b68..0874c2d 100644 --- a/cmdapp/src/config.rs +++ b/cmdapp/src/config.rs @@ -37,8 +37,6 @@ pub struct Config { } pub(crate) struct ConverterConfig { - pub input_path: PathBuf, - pub output_path: PathBuf, pub color_mode: ColorMode, pub hierarchical: Hierarchical, pub filter_speckle_area: usize, @@ -375,8 +373,6 @@ impl Config { pub(crate) fn into_converter_config(self) -> ConverterConfig { ConverterConfig { - input_path: self.input_path, - output_path: self.output_path, color_mode: self.color_mode, hierarchical: self.hierarchical, filter_speckle_area: self.filter_speckle * self.filter_speckle, @@ -394,4 +390,4 @@ impl Config { fn deg2rad(deg: i32) -> f64 { deg as f64 / 180.0 * std::f64::consts::PI -} \ No newline at end of file +} diff --git a/cmdapp/src/converter.rs b/cmdapp/src/converter.rs index 55d4cb9..600bcbc 100644 --- a/cmdapp/src/converter.rs +++ b/cmdapp/src/converter.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::path::Path; use std::{fs::File, io::Write}; use fastrand::Rng; @@ -14,11 +14,14 @@ const KEYING_THRESHOLD: f32 = 0.2; /// Convert an image file into svg file pub fn convert_image_to_svg(config: Config) -> Result<(), String> { + let img = read_image(&config.input_path)?; + let output_path = config.output_path.clone(); let config = config.into_converter_config(); - match config.color_mode { - ColorMode::Color => color_image_to_svg(config), - ColorMode::Binary => binary_image_to_svg(config), - } + let svg = match config.color_mode { + ColorMode::Color => color_image_to_svg(img, config)?, + ColorMode::Binary => binary_image_to_svg(img, config)?, + }; + write_svg(svg, &output_path) } fn color_exists_in_image(img: &ColorImage, color: Color) -> bool { @@ -81,16 +84,9 @@ fn should_key_image(img: &ColorImage) -> bool { false } -fn color_image_to_svg(config: ConverterConfig) -> Result<(), String> { - let (mut img, width, height); - match read_image(config.input_path) { - Ok(values) => { - img = values.0; - width = values.1; - height = values.2; - }, - Err(msg) => return Err(msg), - } +fn color_image_to_svg(mut img: ColorImage, config: ConverterConfig) -> Result { + let width = img.width; + let height = img.height; let key_color = if should_key_image(&img) { let key_color = find_unused_color_in_image(&img)?; @@ -166,21 +162,13 @@ fn color_image_to_svg(config: ConverterConfig) -> Result<(), String> { svg.add_path(paths, cluster.residue_color()); } - write_svg(svg, config.output_path) + Ok(svg) } -fn binary_image_to_svg(config: ConverterConfig) -> Result<(), String> { - - let (img, width, height); - match read_image(config.input_path) { - Ok(values) => { - img = values.0; - width = values.1; - height = values.2; - }, - Err(msg) => return Err(msg), - } +fn binary_image_to_svg(img: ColorImage, config: ConverterConfig) -> Result { let img = img.to_binary_image(|x| x.r < 128); + let width = img.width; + let height = img.height; let clusters = img.to_clusters(false); @@ -199,10 +187,10 @@ fn binary_image_to_svg(config: ConverterConfig) -> Result<(), String> { } } - write_svg(svg, config.output_path) + Ok(svg) } -fn read_image(input_path: PathBuf) -> Result<(ColorImage, usize, usize), String> { +fn read_image(input_path: &Path) -> Result { let img = image::open(input_path); let img = match img { Ok(file) => file.to_rgba8(), @@ -212,10 +200,10 @@ fn read_image(input_path: PathBuf) -> Result<(ColorImage, usize, usize), String> let (width, height) = (img.width() as usize, img.height() as usize); let img = ColorImage {pixels: img.as_raw().to_vec(), width, height}; - Ok((img, width, height)) + Ok(img) } -fn write_svg(svg: SvgFile, output_path: PathBuf) -> Result<(), String> { +fn write_svg(svg: SvgFile, output_path: &Path) -> Result<(), String> { let out_file = File::create(output_path); let mut out_file = match out_file { Ok(file) => file, @@ -225,4 +213,4 @@ fn write_svg(svg: SvgFile, output_path: PathBuf) -> Result<(), String> { write!(&mut out_file, "{}", svg).expect("failed to write file."); Ok(()) -} \ No newline at end of file +} From 6cdef491973011103e2fc30a5ad3cb8fd0690e54 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Fri, 20 Oct 2023 20:54:19 +0200 Subject: [PATCH 2/4] Move Config::from_args() to main.rs --- cmdapp/src/config.rs | 212 ----------------------------------------- cmdapp/src/main.rs | 221 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 219 insertions(+), 214 deletions(-) diff --git a/cmdapp/src/config.rs b/cmdapp/src/config.rs index 0874c2d..4f0a67a 100644 --- a/cmdapp/src/config.rs +++ b/cmdapp/src/config.rs @@ -1,6 +1,5 @@ use std::str::FromStr; use std::path::PathBuf; -use clap::{Arg, App}; use visioncortex::PathSimplifyMode; pub enum Preset { @@ -107,218 +106,7 @@ impl FromStr for Preset { } } -fn path_simplify_mode_from_str(s: &str) -> PathSimplifyMode { - match s { - "polygon" => PathSimplifyMode::Polygon, - "spline" => PathSimplifyMode::Spline, - "none" => PathSimplifyMode::None, - _ => panic!("unknown PathSimplifyMode {}", s), - } -} - impl Config { - pub fn from_args() -> Self { - 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") - .short("i") - .takes_value(true) - .help("Path to input raster image") - .required(true)); - - let app = app.arg(Arg::with_name("output") - .long("output") - .short("o") - .takes_value(true) - .help("Path to output vector graphics") - .required(true)); - - let app = app.arg(Arg::with_name("color_mode") - .long("colormode") - .takes_value(true) - .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") - .long("preset") - .takes_value(true) - .help("Use one of the preset configs `bw`, `poster`, `photo`")); - - let app = app.arg(Arg::with_name("filter_speckle") - .long("filter_speckle") - .short("f") - .takes_value(true) - .help("Discard patches smaller than X px in size")); - - let app = app.arg(Arg::with_name("color_precision") - .long("color_precision") - .short("p") - .takes_value(true) - .help("Number of significant bits to use in an RGB channel")); - - let app = app.arg(Arg::with_name("gradient_step") - .long("gradient_step") - .short("g") - .takes_value(true) - .help("Color difference between gradient layers")); - - let app = app.arg(Arg::with_name("corner_threshold") - .long("corner_threshold") - .short("c") - .takes_value(true) - .help("Minimum momentary angle (degree) to be considered a corner")); - - let app = app.arg(Arg::with_name("segment_length") - .long("segment_length") - .short("l") - .takes_value(true) - .help("Perform iterative subdivide smooth until all segments are shorter than this length")); - - let app = app.arg(Arg::with_name("splice_threshold") - .long("splice_threshold") - .short("s") - .takes_value(true) - .help("Minimum angle displacement (degree) to splice a spline")); - - let app = app.arg(Arg::with_name("mode") - .long("mode") - .short("m") - .takes_value(true) - .help("Curver fitting mode `pixel`, `polygon`, `spline`")); - - let app = app.arg(Arg::with_name("path_precision") - .long("path_precision") - .takes_value(true) - .help("Number of decimal places to use in path string")); - - // Extract matches - let matches = app.get_matches(); - - 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 output_path = matches.value_of("output").expect("Output path is required, please specify it by --output or -o."); - - if let Some(value) = matches.value_of("preset") { - config = Self::from_preset(Preset::from_str(value).unwrap(), input_path, output_path); - } - - config.input_path = PathBuf::from(input_path); - config.output_path = PathBuf::from(output_path); - - 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() - } - - if let Some(value) = matches.value_of("hierarchical") { - config.hierarchical = Hierarchical::from_str(value).unwrap() - } - - if let Some(value) = matches.value_of("mode") { - let value = value.trim(); - config.mode = path_simplify_mode_from_str(if value == "pixel" { - "none" - } else if value == "polygon" { - "polygon" - } else if value == "spline" { - "spline" - } else { - panic!("Parser Error: Curve fitting mode is invalid: {}", value); - }); - } - - if let Some(value) = matches.value_of("filter_speckle") { - if value.trim().parse::().is_ok() { // is numeric - let value = value.trim().parse::().unwrap(); - if value > 16 { - panic!("Out of Range Error: Filter speckle is invalid at {}. It must be within [0,16].", value); - } - config.filter_speckle = value; - } else { - panic!("Parser Error: Filter speckle is not a positive integer: {}.", value); - } - } - - if let Some(value) = matches.value_of("color_precision") { - if value.trim().parse::().is_ok() { // is numeric - let value = value.trim().parse::().unwrap(); - if value < 1 || value > 8 { - panic!("Out of Range Error: Color precision is invalid at {}. It must be within [1,8].", value); - } - config.color_precision = value; - } else { - panic!("Parser Error: Color precision is not an integer: {}.", value); - } - } - - if let Some(value) = matches.value_of("gradient_step") { - if value.trim().parse::().is_ok() { // is numeric - let value = value.trim().parse::().unwrap(); - if value < 0 || value > 255 { - panic!("Out of Range Error: Gradient step is invalid at {}. It must be within [0,255].", value); - } - config.layer_difference = value; - } else { - panic!("Parser Error: Gradient step is not an integer: {}.", value); - } - } - - if let Some(value) = matches.value_of("corner_threshold") { - if value.trim().parse::().is_ok() { // is numeric - let value = value.trim().parse::().unwrap(); - if value < 0 || value > 180 { - panic!("Out of Range Error: Corner threshold is invalid at {}. It must be within [0,180].", value); - } - config.corner_threshold = value - } else { - panic!("Parser Error: Corner threshold is not numeric: {}.", value); - } - } - - if let Some(value) = matches.value_of("segment_length") { - if value.trim().parse::().is_ok() { // is numeric - let value = value.trim().parse::().unwrap(); - 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); - } - config.length_threshold = value; - } else { - panic!("Parser Error: Segment length is not numeric: {}.", value); - } - } - - if let Some(value) = matches.value_of("splice_threshold") { - if value.trim().parse::().is_ok() { // is numeric - let value = value.trim().parse::().unwrap(); - if value < 0 || value > 180 { - panic!("Out of Range Error: Segment length is invalid at {}. It must be within [0,180].", value); - } - config.splice_threshold = value; - } else { - panic!("Parser Error: Segment length is not numeric: {}.", value); - } - } - - if let Some(value) = matches.value_of("path_precision") { - if value.trim().parse::().is_ok() { // is numeric - let value = value.trim().parse::().ok(); - config.path_precision = value; - } else { - panic!("Parser Error: Path precision is not an unsigned integer: {}.", value); - } - } - - config - } - pub fn from_preset(preset: Preset, input_path: &str, output_path: &str) -> Self { let input_path = PathBuf::from(input_path); let output_path = PathBuf::from(output_path); diff --git a/cmdapp/src/main.rs b/cmdapp/src/main.rs index 07a3c80..9793346 100644 --- a/cmdapp/src/main.rs +++ b/cmdapp/src/main.rs @@ -2,8 +2,225 @@ mod config; mod converter; mod svg; +use std::str::FromStr; +use std::path::PathBuf; +use clap::{Arg, App}; +use visioncortex::PathSimplifyMode; +use config::{Config, Preset, ColorMode, Hierarchical}; + +fn path_simplify_mode_from_str(s: &str) -> PathSimplifyMode { + match s { + "polygon" => PathSimplifyMode::Polygon, + "spline" => PathSimplifyMode::Spline, + "none" => PathSimplifyMode::None, + _ => panic!("unknown PathSimplifyMode {}", s), + } +} + +pub fn config_from_args() -> Config { + 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") + .short("i") + .takes_value(true) + .help("Path to input raster image") + .required(true)); + + let app = app.arg(Arg::with_name("output") + .long("output") + .short("o") + .takes_value(true) + .help("Path to output vector graphics") + .required(true)); + + let app = app.arg(Arg::with_name("color_mode") + .long("colormode") + .takes_value(true) + .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") + .long("preset") + .takes_value(true) + .help("Use one of the preset configs `bw`, `poster`, `photo`")); + + let app = app.arg(Arg::with_name("filter_speckle") + .long("filter_speckle") + .short("f") + .takes_value(true) + .help("Discard patches smaller than X px in size")); + + let app = app.arg(Arg::with_name("color_precision") + .long("color_precision") + .short("p") + .takes_value(true) + .help("Number of significant bits to use in an RGB channel")); + + let app = app.arg(Arg::with_name("gradient_step") + .long("gradient_step") + .short("g") + .takes_value(true) + .help("Color difference between gradient layers")); + + let app = app.arg(Arg::with_name("corner_threshold") + .long("corner_threshold") + .short("c") + .takes_value(true) + .help("Minimum momentary angle (degree) to be considered a corner")); + + let app = app.arg(Arg::with_name("segment_length") + .long("segment_length") + .short("l") + .takes_value(true) + .help("Perform iterative subdivide smooth until all segments are shorter than this length")); + + let app = app.arg(Arg::with_name("splice_threshold") + .long("splice_threshold") + .short("s") + .takes_value(true) + .help("Minimum angle displacement (degree) to splice a spline")); + + let app = app.arg(Arg::with_name("mode") + .long("mode") + .short("m") + .takes_value(true) + .help("Curver fitting mode `pixel`, `polygon`, `spline`")); + + let app = app.arg(Arg::with_name("path_precision") + .long("path_precision") + .takes_value(true) + .help("Number of decimal places to use in path string")); + + // Extract matches + let matches = app.get_matches(); + + 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 output_path = matches.value_of("output").expect("Output path is required, please specify it by --output or -o."); + + if let Some(value) = matches.value_of("preset") { + config = Config::from_preset(Preset::from_str(value).unwrap(), input_path, output_path); + } + + config.input_path = PathBuf::from(input_path); + config.output_path = PathBuf::from(output_path); + + 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() + } + + if let Some(value) = matches.value_of("hierarchical") { + config.hierarchical = Hierarchical::from_str(value).unwrap() + } + + if let Some(value) = matches.value_of("mode") { + let value = value.trim(); + config.mode = path_simplify_mode_from_str(if value == "pixel" { + "none" + } else if value == "polygon" { + "polygon" + } else if value == "spline" { + "spline" + } else { + panic!("Parser Error: Curve fitting mode is invalid: {}", value); + }); + } + + if let Some(value) = matches.value_of("filter_speckle") { + if value.trim().parse::().is_ok() { // is numeric + let value = value.trim().parse::().unwrap(); + if value > 16 { + panic!("Out of Range Error: Filter speckle is invalid at {}. It must be within [0,16].", value); + } + config.filter_speckle = value; + } else { + panic!("Parser Error: Filter speckle is not a positive integer: {}.", value); + } + } + + if let Some(value) = matches.value_of("color_precision") { + if value.trim().parse::().is_ok() { // is numeric + let value = value.trim().parse::().unwrap(); + if value < 1 || value > 8 { + panic!("Out of Range Error: Color precision is invalid at {}. It must be within [1,8].", value); + } + config.color_precision = value; + } else { + panic!("Parser Error: Color precision is not an integer: {}.", value); + } + } + + if let Some(value) = matches.value_of("gradient_step") { + if value.trim().parse::().is_ok() { // is numeric + let value = value.trim().parse::().unwrap(); + if value < 0 || value > 255 { + panic!("Out of Range Error: Gradient step is invalid at {}. It must be within [0,255].", value); + } + config.layer_difference = value; + } else { + panic!("Parser Error: Gradient step is not an integer: {}.", value); + } + } + + if let Some(value) = matches.value_of("corner_threshold") { + if value.trim().parse::().is_ok() { // is numeric + let value = value.trim().parse::().unwrap(); + if value < 0 || value > 180 { + panic!("Out of Range Error: Corner threshold is invalid at {}. It must be within [0,180].", value); + } + config.corner_threshold = value + } else { + panic!("Parser Error: Corner threshold is not numeric: {}.", value); + } + } + + if let Some(value) = matches.value_of("segment_length") { + if value.trim().parse::().is_ok() { // is numeric + let value = value.trim().parse::().unwrap(); + 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); + } + config.length_threshold = value; + } else { + panic!("Parser Error: Segment length is not numeric: {}.", value); + } + } + + if let Some(value) = matches.value_of("splice_threshold") { + if value.trim().parse::().is_ok() { // is numeric + let value = value.trim().parse::().unwrap(); + if value < 0 || value > 180 { + panic!("Out of Range Error: Segment length is invalid at {}. It must be within [0,180].", value); + } + config.splice_threshold = value; + } else { + panic!("Parser Error: Segment length is not numeric: {}.", value); + } + } + + if let Some(value) = matches.value_of("path_precision") { + if value.trim().parse::().is_ok() { // is numeric + let value = value.trim().parse::().ok(); + config.path_precision = value; + } else { + panic!("Parser Error: Path precision is not an unsigned integer: {}.", value); + } + } + + config +} + fn main() { - let config = config::Config::from_args(); + let config = config_from_args(); let result = converter::convert_image_to_svg(config); match result { Ok(()) => { @@ -13,4 +230,4 @@ fn main() { panic!("Conversion failed with error message: {}", msg); } } -} \ No newline at end of file +} From 2b5b574064159711ae8f272e3ffc7136835c06e1 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Fri, 20 Oct 2023 20:57:21 +0200 Subject: [PATCH 3/4] Remove path support from Config Instead the input_path and output_path have to be passed to convert_image_to_svg() manually. --- cmdapp/src/config.rs | 15 +-------------- cmdapp/src/converter.rs | 7 +++---- cmdapp/src/main.rs | 16 ++++++++-------- cmdapp/src/python.rs | 4 +--- 4 files changed, 13 insertions(+), 29 deletions(-) diff --git a/cmdapp/src/config.rs b/cmdapp/src/config.rs index 4f0a67a..9a7c908 100644 --- a/cmdapp/src/config.rs +++ b/cmdapp/src/config.rs @@ -1,5 +1,4 @@ use std::str::FromStr; -use std::path::PathBuf; use visioncortex::PathSimplifyMode; pub enum Preset { @@ -20,8 +19,6 @@ pub enum Hierarchical { /// Converter config pub struct Config { - pub input_path: PathBuf, - pub output_path: PathBuf, pub color_mode: ColorMode, pub hierarchical: Hierarchical, pub filter_speckle: usize, @@ -52,8 +49,6 @@ pub(crate) struct ConverterConfig { impl Default for Config { fn default() -> Self { Self { - input_path: PathBuf::default(), - output_path: PathBuf::default(), color_mode: ColorMode::Color, hierarchical: Hierarchical::Stacked, mode: PathSimplifyMode::Spline, @@ -107,13 +102,9 @@ impl FromStr for Preset { } impl Config { - pub fn from_preset(preset: Preset, input_path: &str, output_path: &str) -> Self { - let input_path = PathBuf::from(input_path); - let output_path = PathBuf::from(output_path); + pub fn from_preset(preset: Preset) -> Self { match preset { Preset::Bw => Self { - input_path, - output_path, color_mode: ColorMode::Binary, hierarchical: Hierarchical::Stacked, filter_speckle: 4, @@ -127,8 +118,6 @@ impl Config { path_precision: Some(8), }, Preset::Poster => Self { - input_path, - output_path, color_mode: ColorMode::Color, hierarchical: Hierarchical::Stacked, filter_speckle: 4, @@ -142,8 +131,6 @@ impl Config { path_precision: Some(8), }, Preset::Photo => Self { - input_path, - output_path, color_mode: ColorMode::Color, hierarchical: Hierarchical::Stacked, filter_speckle: 10, diff --git a/cmdapp/src/converter.rs b/cmdapp/src/converter.rs index 600bcbc..40304f4 100644 --- a/cmdapp/src/converter.rs +++ b/cmdapp/src/converter.rs @@ -13,15 +13,14 @@ const NUM_UNUSED_COLOR_ITERATIONS: usize = 6; const KEYING_THRESHOLD: f32 = 0.2; /// Convert an image file into svg file -pub fn convert_image_to_svg(config: Config) -> Result<(), String> { - let img = read_image(&config.input_path)?; - let output_path = config.output_path.clone(); +pub fn convert_image_to_svg(input_path: &Path, output_path: &Path, config: Config) -> Result<(), String> { + let img = read_image(input_path)?; let config = config.into_converter_config(); let svg = match config.color_mode { ColorMode::Color => color_image_to_svg(img, config)?, ColorMode::Binary => binary_image_to_svg(img, config)?, }; - write_svg(svg, &output_path) + write_svg(svg, output_path) } fn color_exists_in_image(img: &ColorImage, color: Color) -> bool { diff --git a/cmdapp/src/main.rs b/cmdapp/src/main.rs index 9793346..69d0eac 100644 --- a/cmdapp/src/main.rs +++ b/cmdapp/src/main.rs @@ -17,7 +17,7 @@ fn path_simplify_mode_from_str(s: &str) -> PathSimplifyMode { } } -pub fn config_from_args() -> Config { +pub fn config_from_args() -> (PathBuf, PathBuf, Config) { let app = App::new("visioncortex VTracer ".to_owned() + env!("CARGO_PKG_VERSION")) .about("A cmd app to convert images into vector graphics."); @@ -107,13 +107,13 @@ pub fn config_from_args() -> Config { let input_path = matches.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 output_path = PathBuf::from(output_path); + if let Some(value) = matches.value_of("preset") { - config = Config::from_preset(Preset::from_str(value).unwrap(), input_path, output_path); + config = Config::from_preset(Preset::from_str(value).unwrap()); } - config.input_path = PathBuf::from(input_path); - config.output_path = PathBuf::from(output_path); - 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() } @@ -216,12 +216,12 @@ pub fn config_from_args() -> Config { } } - config + (input_path, output_path, config) } fn main() { - let config = config_from_args(); - let result = converter::convert_image_to_svg(config); + let (input_path, output_path, config) = config_from_args(); + let result = converter::convert_image_to_svg(&input_path, &output_path, config); match result { Ok(()) => { println!("Conversion successful."); diff --git a/cmdapp/src/python.rs b/cmdapp/src/python.rs index 36caa54..d3cf7a0 100644 --- a/cmdapp/src/python.rs +++ b/cmdapp/src/python.rs @@ -53,8 +53,6 @@ fn convert_image_to_svg_py( let max_iterations = max_iterations.unwrap_or(10); let config = Config { - input_path, - output_path, color_mode, hierarchical, filter_speckle, @@ -69,7 +67,7 @@ fn convert_image_to_svg_py( ..Default::default() }; - convert_image_to_svg(config).unwrap(); + convert_image_to_svg(&input_path, &output_path, config).unwrap(); Ok(()) } From 75e4b9053d3f67eda6ee19687dbc84a907813318 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Fri, 20 Oct 2023 21:01:13 +0200 Subject: [PATCH 4/4] Add a simplified convert() function This lets the user convert an image from memory, without encoding it to PNG, writing it to a file, then reopening this file and decoding it using the image crate. It also allows the user to not write a SVG directly to a file. --- cmdapp/src/converter.rs | 15 ++++++++++----- cmdapp/src/lib.rs | 3 ++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/cmdapp/src/converter.rs b/cmdapp/src/converter.rs index 40304f4..76bb651 100644 --- a/cmdapp/src/converter.rs +++ b/cmdapp/src/converter.rs @@ -12,14 +12,19 @@ const NUM_UNUSED_COLOR_ITERATIONS: usize = 6; /// the entire image will be keyed. const KEYING_THRESHOLD: f32 = 0.2; +/// Convert an in-memory image into an in-memory SVG +pub fn convert(img: ColorImage, config: Config) -> Result { + let config = config.into_converter_config(); + match config.color_mode { + ColorMode::Color => color_image_to_svg(img, config), + ColorMode::Binary => binary_image_to_svg(img, config), + } +} + /// Convert an image file into svg file pub fn convert_image_to_svg(input_path: &Path, output_path: &Path, config: Config) -> Result<(), String> { let img = read_image(input_path)?; - let config = config.into_converter_config(); - let svg = match config.color_mode { - ColorMode::Color => color_image_to_svg(img, config)?, - ColorMode::Binary => binary_image_to_svg(img, config)?, - }; + let svg = convert(img, config)?; write_svg(svg, output_path) } diff --git a/cmdapp/src/lib.rs b/cmdapp/src/lib.rs index 752f842..cac0954 100644 --- a/cmdapp/src/lib.rs +++ b/cmdapp/src/lib.rs @@ -18,4 +18,5 @@ pub use config::*; pub use converter::*; pub use svg::*; #[cfg(feature = "python-binding")] -pub use python::*; \ No newline at end of file +pub use python::*; +pub use visioncortex::ColorImage;