From 36b16de17a576bce773a431f7d1c93f612acb564 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Wed, 29 May 2024 15:10:02 +0100 Subject: [PATCH] Key transparent image in webapp --- webapp/Cargo.toml | 2 +- webapp/app/index.js | 8 ++- webapp/src/conversion/binary_image.rs | 2 +- webapp/src/conversion/color_image.rs | 87 ++++++++++++++++++++++++++- webapp/src/conversion/mod.rs | 3 - 5 files changed, 92 insertions(+), 10 deletions(-) diff --git a/webapp/Cargo.toml b/webapp/Cargo.toml index d06f8cd..55c16f4 100644 --- a/webapp/Cargo.toml +++ b/webapp/Cargo.toml @@ -22,7 +22,7 @@ console_log = { version = "0.2", features = ["color"] } wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -visioncortex = "0.6.0" +visioncortex = "0.8.1" # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires diff --git a/webapp/app/index.js b/webapp/app/index.js index 2a45f98..5b561d8 100644 --- a/webapp/app/index.js +++ b/webapp/app/index.js @@ -35,7 +35,11 @@ document.addEventListener('paste', function (e) { // Download as SVG document.getElementById('export').addEventListener('click', function (e) { - const blob = new Blob([new XMLSerializer().serializeToString(svg)], {type: 'octet/stream'}), + const blob = new Blob([ + `\n`, + `\n`, + new XMLSerializer().serializeToString(svg) + ], {type: 'octet/stream'}), url = window.URL.createObjectURL(blob); this.href = url; @@ -444,7 +448,7 @@ class ConverterRunner { this.converter.init(); this.stopped = false; if (clustering_mode == 'binary') { - svg.style.background = '#000'; + svg.style.background = '#fff'; canvas.style.display = 'none'; } else { svg.style.background = ''; diff --git a/webapp/src/conversion/binary_image.rs b/webapp/src/conversion/binary_image.rs index 22c8d9a..7c302c4 100644 --- a/webapp/src/conversion/binary_image.rs +++ b/webapp/src/conversion/binary_image.rs @@ -77,7 +77,7 @@ impl BinaryImageConverter { self.params.max_iterations, self.params.splice_threshold ); - let color = Color::color(&ColorName::White); + let color = Color::color(&ColorName::Black); self.svg.prepend_path( &paths, &color, diff --git a/webapp/src/conversion/color_image.rs b/webapp/src/conversion/color_image.rs index b196601..0cd15ad 100644 --- a/webapp/src/conversion/color_image.rs +++ b/webapp/src/conversion/color_image.rs @@ -1,6 +1,6 @@ use wasm_bindgen::prelude::*; -use visioncortex::PathSimplifyMode; -use visioncortex::color_clusters::{IncrementalBuilder, Clusters, Runner, RunnerConfig, HIERARCHICAL_MAX}; +use visioncortex::{Color, ColorImage, PathSimplifyMode}; +use visioncortex::color_clusters::{Clusters, Runner, RunnerConfig, HIERARCHICAL_MAX, IncrementalBuilder, KeyingAction}; use crate::canvas::*; use crate::svg::*; @@ -8,6 +8,8 @@ use crate::svg::*; use serde::Deserialize; use super::util; +const KEYING_THRESHOLD: f32 = 0.2; + #[derive(Debug, Deserialize)] pub struct ColorImageConverterParams { pub canvas_id: String, @@ -67,7 +69,26 @@ impl ColorImageConverter { 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_color_image(0, 0, width, height); + let mut image = self.canvas.get_image_data_as_color_image(0, 0, width, height); + + let key_color = if Self::should_key_image(&image) { + if let Ok(key_color) = Self::find_unused_color_in_image(&image) { + for y in 0..height as usize { + for x in 0..width as usize { + if image.get_pixel(x, y).a == 0 { + image.set_pixel(x, y, &key_color); + } + } + } + key_color + } else { + Color::default() + } + } else { + // The default color is all zeroes, which is treated by visioncortex as a special value meaning no keying will be applied. + Color::default() + }; + let runner = Runner::new(RunnerConfig { diagonal: self.params.layer_difference == 0, hierarchical: HIERARCHICAL_MAX, @@ -78,6 +99,12 @@ impl ColorImageConverter { is_same_color_b: 1, deepen_diff: self.params.layer_difference, hollow_neighbours: 1, + key_color, + keying_action: if self.params.hierarchical == "cutout" { + KeyingAction::Keep + } else { + KeyingAction::Discard + }, }, image); self.stage = Stage::Clustering(runner.start()); } @@ -108,6 +135,8 @@ impl ColorImageConverter { is_same_color_b: 1, deepen_diff: 0, hollow_neighbours: 0, + key_color: Default::default(), + keying_action: KeyingAction::Discard, }, image); self.stage = Stage::Reclustering(runner.start()); }, @@ -167,4 +196,56 @@ impl ColorImageConverter { }) as i32 } + fn color_exists_in_image(img: &ColorImage, color: Color) -> bool { + for y in 0..img.height { + for x in 0..img.width { + let pixel_color = img.get_pixel(x, y); + if pixel_color.r == color.r && pixel_color.g == color.g && pixel_color.b == color.b { + return true + } + } + } + false + } + + fn find_unused_color_in_image(img: &ColorImage) -> Result { + let special_colors = IntoIterator::into_iter([ + Color::new(255, 0, 0), + Color::new(0, 255, 0), + Color::new(0, 0, 255), + Color::new(255, 255, 0), + Color::new(0, 255, 255), + Color::new(255, 0, 255), + Color::new(128, 128, 128), + ]); + for color in special_colors { + if !Self::color_exists_in_image(img, color) { + return Ok(color); + } + } + Err(String::from("unable to find unused color in image to use as key")) + } + + fn should_key_image(img: &ColorImage) -> bool { + if img.width == 0 || img.height == 0 { + return false; + } + + // Check for transparency at several scanlines + let threshold = ((img.width * 2) as f32 * KEYING_THRESHOLD) as usize; + let mut num_transparent_boundary_pixels = 0; + let y_positions = [0, img.height / 4, img.height / 2, 3 * img.height / 4, img.height - 1]; + for y in y_positions { + for x in 0..img.width { + if img.get_pixel(x, y).a == 0 { + num_transparent_boundary_pixels += 1; + } + if num_transparent_boundary_pixels >= threshold { + return true; + } + } + } + + false + } } \ No newline at end of file diff --git a/webapp/src/conversion/mod.rs b/webapp/src/conversion/mod.rs index cbf4055..5959281 100644 --- a/webapp/src/conversion/mod.rs +++ b/webapp/src/conversion/mod.rs @@ -1,6 +1,3 @@ mod binary_image; mod color_image; mod util; - -pub use binary_image::*; -pub use color_image::*; \ No newline at end of file