Source Release

This commit is contained in:
Chris Tsang
2020-10-25 14:22:42 +08:00
parent f0a1369dc4
commit f6f4839185
23 changed files with 6333 additions and 3 deletions

60
webapp/src/canvas.rs Executable file
View File

@@ -0,0 +1,60 @@
use wasm_bindgen::{JsCast};
use web_sys::{console, CanvasRenderingContext2d, HtmlCanvasElement};
use visioncortex::{ColorImage};
use super::common::document;
pub struct Canvas {
html_canvas: HtmlCanvasElement,
cctx: CanvasRenderingContext2d,
}
impl Canvas {
pub fn new_from_id(canvas_id: &str) -> Canvas {
let html_canvas = document().get_element_by_id(canvas_id).unwrap();
let html_canvas: HtmlCanvasElement = html_canvas
.dyn_into::<HtmlCanvasElement>()
.map_err(|_| ())
.unwrap();
let cctx = html_canvas
.get_context("2d")
.unwrap()
.unwrap()
.dyn_into::<CanvasRenderingContext2d>()
.unwrap();
Canvas {
html_canvas,
cctx,
}
}
pub fn width(&self) -> usize {
self.html_canvas.width() as usize
}
pub fn height(&self) -> usize {
self.html_canvas.height() as usize
}
pub fn get_image_data(&self, x: u32, y: u32, width: u32, height: u32) -> Vec<u8> {
let image = self
.cctx
.get_image_data(x as f64, y as f64, width as f64, height as f64)
.unwrap();
image.data().to_vec()
}
pub fn get_image_data_as_image(&self, x: u32, y: u32, width: u32, height: u32) -> ColorImage {
ColorImage {
pixels: self.get_image_data(x, y, width, height),
width: width as usize,
height: height as usize,
}
}
pub fn log(&self, string: &str) {
console::log_1(&wasm_bindgen::JsValue::from_str(string));
}
}

7
webapp/src/common.rs Executable file
View File

@@ -0,0 +1,7 @@
pub fn window() -> web_sys::Window {
web_sys::window().unwrap()
}
pub fn document() -> web_sys::Document {
window().document().unwrap()
}

View File

@@ -0,0 +1,97 @@
use wasm_bindgen::prelude::*;
use visioncortex::{clusters::Clusters, Color, ColorName, PointI32, PathSimplifyMode};
use crate::{canvas::*};
use crate::svg::*;
use serde::Deserialize;
use super::util;
#[derive(Debug, Deserialize)]
pub struct BinaryImageConverterParams {
pub canvas_id: String,
pub svg_id: String,
pub mode: String,
pub corner_threshold: f64,
pub length_threshold: f64,
pub max_iterations: usize,
pub splice_threshold: f64,
pub filter_speckle: usize,
}
#[wasm_bindgen]
pub struct BinaryImageConverter {
canvas: Canvas,
svg: Svg,
clusters: Clusters,
counter: usize,
mode: PathSimplifyMode,
params: BinaryImageConverterParams,
}
impl BinaryImageConverter {
pub fn new(params: BinaryImageConverterParams) -> Self {
let canvas = Canvas::new_from_id(&params.canvas_id);
let svg = Svg::new_from_id(&params.svg_id);
Self {
canvas,
svg,
clusters: Clusters::default(),
counter: 0,
mode: util::path_simplify_mode(&params.mode),
params,
}
}
}
#[wasm_bindgen]
impl BinaryImageConverter {
pub fn new_with_string(params: String) -> Self {
let params: BinaryImageConverterParams = serde_json::from_str(params.as_str()).unwrap();
Self::new(params)
}
pub fn init(&mut self) {
let width = self.canvas.width() as u32;
let height = self.canvas.height() as u32;
let image = self.canvas.get_image_data_as_image(0, 0, width, height);
let binary_image = image.to_binary_image(|x| x.r < 128);
self.clusters = binary_image.to_clusters(false);
self.canvas.log(&format!(
"clusters.len() = {}, self.clusters.rect.left = {}",
self.clusters.len(),
self.clusters.rect.left
));
}
pub fn tick(&mut self) -> bool {
if self.counter < self.clusters.len() {
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(
self.mode,
self.params.corner_threshold,
self.params.length_threshold,
self.params.max_iterations,
self.params.splice_threshold
);
let color = Color::color(&ColorName::White);
self.svg.prepend_path_with_fill(
&svg_path,
&PointI32::default(),
&color,
);
}
self.counter += 1;
false
} else {
self.canvas.log("done");
true
}
}
pub fn progress(&self) -> u32 {
100 * self.counter as u32 / self.clusters.len() as u32
}
}

View File

@@ -0,0 +1,133 @@
use wasm_bindgen::prelude::*;
use visioncortex::{PathSimplifyMode, PointI32};
use visioncortex::color_clusters::{IncrementalBuilder, Clusters, Runner, RunnerConfig};
use crate::canvas::*;
use crate::svg::*;
use serde::Deserialize;
use super::util;
#[derive(Debug, Deserialize)]
pub struct ColorImageConverterParams {
pub canvas_id: String,
pub svg_id: String,
pub mode: String,
pub corner_threshold: f64,
pub length_threshold: f64,
pub max_iterations: usize,
pub splice_threshold: f64,
pub filter_speckle: usize,
pub color_precision: i32,
pub layer_difference: i32,
}
#[wasm_bindgen]
pub struct ColorImageConverter {
canvas: Canvas,
svg: Svg,
stage: Stage,
counter: usize,
mode: PathSimplifyMode,
params: ColorImageConverterParams,
}
pub enum Stage {
New,
Clustering(IncrementalBuilder),
Vectorize(Clusters),
}
impl ColorImageConverter {
pub fn new(params: ColorImageConverterParams) -> Self {
let canvas = Canvas::new_from_id(&params.canvas_id);
let svg = Svg::new_from_id(&params.svg_id);
Self {
canvas,
svg,
stage: Stage::New,
counter: 0,
mode: util::path_simplify_mode(&params.mode),
params,
}
}
}
#[wasm_bindgen]
impl ColorImageConverter {
pub fn new_with_string(params: String) -> Self {
let params: ColorImageConverterParams = serde_json::from_str(params.as_str()).unwrap();
Self::new(params)
}
pub fn init(&mut self) {
let width = self.canvas.width() as u32;
let height = self.canvas.height() as u32;
let image = self.canvas.get_image_data_as_image(0, 0, width, height);
let runner = Runner::new(RunnerConfig {
batch_size: 25600,
good_min_area: self.params.filter_speckle,
good_max_area: (width * height) as usize,
is_same_color_a: self.params.color_precision,
is_same_color_b: 1,
deepen_diff: self.params.layer_difference,
hollow_neighbours: 1,
}, image);
self.stage = Stage::Clustering(runner.start());
}
pub fn tick(&mut self) -> bool {
match &mut self.stage {
Stage::New => {
panic!("uninitialized");
},
Stage::Clustering(builder) => {
self.canvas.log("Clustering tick");
if builder.tick() {
self.stage = Stage::Vectorize(builder.result())
}
false
},
Stage::Vectorize(clusters) => {
let view = clusters.view();
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(
&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),
&cluster.residue_color(),
);
self.counter += 1;
false
} else {
self.canvas.log("done");
true
}
}
}
}
pub fn progress(&self) -> i32 {
(match &self.stage {
Stage::New => {
0
},
Stage::Clustering(builder) => {
builder.progress() / 2
},
Stage::Vectorize(clusters) => {
50 + 50 * self.counter as u32 / clusters.view().clusters_output.len() as u32
}
}) as i32
}
}

6
webapp/src/conversion/mod.rs Executable file
View File

@@ -0,0 +1,6 @@
mod binary_image;
mod color_image;
mod util;
pub use binary_image::*;
pub use color_image::*;

10
webapp/src/conversion/util.rs Executable file
View File

@@ -0,0 +1,10 @@
use visioncortex::PathSimplifyMode;
pub fn path_simplify_mode(s: &str) -> PathSimplifyMode {
match s {
"polygon" => PathSimplifyMode::Polygon,
"spline" => PathSimplifyMode::Spline,
"none" => PathSimplifyMode::None,
_ => panic!("unknown PathSimplifyMode {}", s),
}
}

13
webapp/src/lib.rs Executable file
View File

@@ -0,0 +1,13 @@
use wasm_bindgen::prelude::*;
mod conversion;
mod canvas;
mod common;
mod svg;
mod utils;
#[wasm_bindgen(start)]
pub fn main() {
utils::set_panic_hook();
console_log::init().unwrap();
}

33
webapp/src/svg.rs Executable file
View File

@@ -0,0 +1,33 @@
use web_sys::Element;
use visioncortex::{Color, PointI32};
use super::common::document;
pub struct Svg {
element: Element,
}
impl Svg {
pub fn new_from_id(svg_id: &str) -> Self {
let element = document().get_element_by_id(svg_id).unwrap();
Self { element }
}
pub fn prepend_path_with_fill(&mut self, path_string: &str, offset: &PointI32, color: &Color) {
let path = document()
.create_element_ns(Some("http://www.w3.org/2000/svg"), "path")
.unwrap();
path.set_attribute("d", path_string).unwrap();
path.set_attribute(
"transform",
format!("translate({},{})", offset.x, offset.y).as_str(),
)
.unwrap();
path.set_attribute(
"style",
format!("fill: {};", color.to_hex_string()).as_str(),
)
.unwrap();
self.element.prepend_with_node_1(&path).unwrap();
}
}

13
webapp/src/utils.rs Normal file
View File

@@ -0,0 +1,13 @@
extern crate cfg_if;
cfg_if::cfg_if! {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function to get better error messages if we ever panic.
if #[cfg(feature = "console_error_panic_hook")] {
extern crate console_error_panic_hook;
pub use self::console_error_panic_hook::set_once as set_panic_hook;
} else {
#[inline]
pub fn set_panic_hook() {}
}
}