Relative & compound path

This commit is contained in:
Chris Tsang
2020-11-09 00:38:44 +08:00
parent 99cb79895b
commit 5f18837b61
7 changed files with 59 additions and 54 deletions
+1 -1
View File
@@ -13,4 +13,4 @@ keywords = ["svg", "computer-graphics"]
[dependencies] [dependencies]
clap = "2.33.3" clap = "2.33.3"
image = "0.23.10" image = "0.23.10"
visioncortex = "0.2.0" visioncortex = "0.3.0"
+9 -18
View File
@@ -42,7 +42,7 @@ fn color_image_to_svg(config: ConverterConfig) -> Result<(), String> {
let mut svg = SvgFile::new(width, height); let mut svg = SvgFile::new(width, height);
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 svg_path = cluster.to_svg_path( let paths = cluster.to_compound_path(
&view, &view,
false, false,
config.mode, config.mode,
@@ -51,22 +51,14 @@ fn color_image_to_svg(config: ConverterConfig) -> Result<(), String> {
config.max_iterations, config.max_iterations,
config.splice_threshold 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); write_svg(svg, 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(())
} }
fn binary_image_to_svg(config: ConverterConfig) -> Result<(), String> { fn binary_image_to_svg(config: ConverterConfig) -> Result<(), String> {
let (img, width, height); let (img, width, height);
match read_image(config.input_path) { match read_image(config.input_path) {
Ok(values) => { 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 img = img.to_binary_image(|x| x.r < 128);
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);
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 {
let svg_path = cluster.to_svg_path( let paths = cluster.to_compound_path(
config.mode, config.mode,
config.corner_threshold, config.corner_threshold,
config.length_threshold, config.length_threshold,
config.max_iterations, config.max_iterations,
config.splice_threshold, config.splice_threshold,
); );
let color = Color::color(&ColorName::Black); svg.add_path(paths, Color::color(&ColorName::Black));
svg.add_path(svg_path, color);
} }
} }
@@ -118,8 +109,8 @@ fn write_svg(svg: SvgFile, output_path: PathBuf) -> Result<(), String> {
Ok(file) => file, Ok(file) => file,
Err(_) => return Err(String::from("Cannot create output 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(()) Ok(())
} }
+36 -21
View File
@@ -1,42 +1,57 @@
use visioncortex::Color; use std::fmt;
use visioncortex::{Color, CompoundPath, PointF64};
pub struct SvgPath {
path: String,
color: Color,
}
pub struct SvgFile { pub struct SvgFile {
patches: Vec<SvgPath>, pub paths: Vec<SvgPath>,
width: usize, pub width: usize,
height: usize, pub height: usize,
}
pub struct SvgPath {
pub path: CompoundPath,
pub color: Color,
} }
impl SvgFile { impl SvgFile {
pub fn new(width: usize, height: usize) -> Self { pub fn new(width: usize, height: usize) -> Self {
SvgFile { SvgFile {
patches: vec![], paths: vec![],
width, width,
height, height,
} }
} }
pub fn add_path(&mut self, path: String, color: Color) { pub fn add_path(&mut self, path: CompoundPath, color: Color) {
self.patches.push(SvgPath { self.paths.push(SvgPath {
path, path,
color color,
}) })
} }
}
pub fn to_svg_file(&self) -> String { impl fmt::Display for SvgFile {
let mut result: Vec<String> = vec![format!(r#"<?xml version="1.0" encoding="UTF-8"?> fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="{}" height="{}">"#, self.width, self.height)]; 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 { for path in &self.paths {
let color = patch.color; path.fmt(f)?;
result.push(format!("<path d=\"{}\" fill=\"#{:02x}{:02x}{:02x}\"/>\n", patch.path, color.r, color.g, color.b));
}; };
result.push(String::from("</svg>")); writeln!(f, "</svg>")
result.concat() }
}
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
View File
@@ -22,7 +22,7 @@ console_log = { version = "0.2", features = ["color"] }
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
visioncortex = "0.2.0" visioncortex = "0.3.0"
# The `console_error_panic_hook` crate provides better debugging of panics by # The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires # logging them with `console.error`. This is great for development, but requires
+4 -5
View File
@@ -1,5 +1,5 @@
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use visioncortex::{clusters::Clusters, Color, ColorName, PointI32, PathSimplifyMode}; use visioncortex::{clusters::Clusters, Color, ColorName, PathSimplifyMode};
use crate::{canvas::*}; use crate::{canvas::*};
use crate::svg::*; use crate::svg::*;
@@ -69,7 +69,7 @@ impl BinaryImageConverter {
self.canvas.log(&format!("tick {}", self.counter)); self.canvas.log(&format!("tick {}", self.counter));
let cluster = self.clusters.get_cluster(self.counter); let cluster = self.clusters.get_cluster(self.counter);
if cluster.size() >= self.params.filter_speckle { if cluster.size() >= self.params.filter_speckle {
let svg_path = cluster.to_svg_path( let paths = cluster.to_compound_path(
self.mode, self.mode,
self.params.corner_threshold, self.params.corner_threshold,
self.params.length_threshold, self.params.length_threshold,
@@ -77,9 +77,8 @@ impl BinaryImageConverter {
self.params.splice_threshold self.params.splice_threshold
); );
let color = Color::color(&ColorName::White); let color = Color::color(&ColorName::White);
self.svg.prepend_path_with_fill( self.svg.prepend_path(
&svg_path, &paths,
&PointI32::default(),
&color, &color,
); );
} }
+4 -5
View File
@@ -1,5 +1,5 @@
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use visioncortex::{PathSimplifyMode, PointI32}; use visioncortex::PathSimplifyMode;
use visioncortex::color_clusters::{IncrementalBuilder, Clusters, Runner, RunnerConfig}; use visioncortex::color_clusters::{IncrementalBuilder, Clusters, Runner, RunnerConfig};
use crate::canvas::*; use crate::canvas::*;
@@ -94,16 +94,15 @@ impl ColorImageConverter {
if self.counter < view.clusters_output.len() { if self.counter < view.clusters_output.len() {
self.canvas.log("Vectorize tick"); self.canvas.log("Vectorize tick");
let cluster = view.get_cluster(view.clusters_output[self.counter]); 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, &view, false, self.mode,
self.params.corner_threshold, self.params.corner_threshold,
self.params.length_threshold, self.params.length_threshold,
self.params.max_iterations, self.params.max_iterations,
self.params.splice_threshold self.params.splice_threshold
); );
self.svg.prepend_path_with_fill( self.svg.prepend_path(
&svg_path, &paths,
&PointI32::new(0, 0),
&cluster.residue_color(), &cluster.residue_color(),
); );
self.counter += 1; self.counter += 1;
+4 -3
View File
@@ -1,5 +1,5 @@
use web_sys::Element; use web_sys::Element;
use visioncortex::{Color, PointI32}; use visioncortex::{Color, CompoundPath, PointF64};
use super::common::document; use super::common::document;
pub struct Svg { pub struct Svg {
@@ -13,11 +13,12 @@ impl Svg {
Self { element } 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() let path = document()
.create_element_ns(Some("http://www.w3.org/2000/svg"), "path") .create_element_ns(Some("http://www.w3.org/2000/svg"), "path")
.unwrap(); .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( path.set_attribute(
"transform", "transform",
format!("translate({},{})", offset.x, offset.y).as_str(), format!("translate({},{})", offset.x, offset.y).as_str(),