diff --git a/src/js/index.js b/src/js/index.js index 32a93ec..3e9ff3f 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -23,10 +23,13 @@ import("../../webclient/pkg").then((module) => { // module.gaussian_blur(rust_image, blur_factor); // module.median(rust_image, blur_factor, blur_factor); - module.spiegel(rust_image, blur_factor).then((pi) => { - module.putImageData(canvas, ctx, pi); - document.getElementById("msg").remove(); - }); + + module.spiegel(rust_image, blur_factor); + module.putImageData(canvas, ctx, rust_image); + const msg = document.getElementById("msg"); + if (msg) { + msg.remove(); + } } function setUpCanvas() { diff --git a/webclient/Cargo.lock b/webclient/Cargo.lock index 0dc72ba..63bef1a 100644 --- a/webclient/Cargo.lock +++ b/webclient/Cargo.lock @@ -383,6 +383,7 @@ checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -405,12 +406,34 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + [[package]] name = "futures-sink" version = "0.3.30" @@ -429,11 +452,16 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -1654,6 +1682,7 @@ version = "0.1.0" dependencies = [ "anyhow", "console_error_panic_hook", + "futures", "hex", "image 0.24.9", "imageproc 0.23.0", @@ -1661,7 +1690,6 @@ dependencies = [ "photon-rs", "reqwest-wasm", "wasm-bindgen", - "wasm-bindgen-futures", ] [[package]] diff --git a/webclient/Cargo.toml b/webclient/Cargo.toml index 4e4cec9..6022674 100644 --- a/webclient/Cargo.toml +++ b/webclient/Cargo.toml @@ -14,8 +14,9 @@ anyhow = "1.0" photon-rs = { version = "0.3.2", default-features = false } console_error_panic_hook = { version = "0.1.7", optional = true } wasm-bindgen = "0.2.78" -wasm-bindgen-futures = "0.4" +# wasm-bindgen-futures = "0.4" reqwest-wasm = "0.11" +futures = "0.3" [lib] crate-type = ["cdylib", "rlib"] diff --git a/webclient/src/lib.rs b/webclient/src/lib.rs index 30245e7..51c6689 100644 --- a/webclient/src/lib.rs +++ b/webclient/src/lib.rs @@ -1,10 +1,10 @@ +use anyhow::Error; +use futures::executor::block_on; +use image::io::Reader as ImageReader; +use image::{GenericImageView, ImageBuffer, Pixel, Rgb, RgbImage, Rgba, RgbaImage}; +use photon_rs::PhotonImage; use std::collections::HashSet; use std::io::Cursor; - -use anyhow::Error; -use image::io::Reader as ImageReader; -use image::{GenericImageView, ImageBuffer, Pixel, Rgb, RgbImage, RgbaImage}; -use photon_rs::PhotonImage; mod quantizer; mod samples; @@ -34,51 +34,54 @@ pub fn median(photon_image: &mut PhotonImage, x_radius: u32, y_radius: u32) { *photon_image = PhotonImage::new(filtered, width, height); } -pub fn determine_colors(image: &PhotonImage) -> HashSet { - let mut unique_colors = HashSet::new(); - let pixels = image.get_raw_pixels(); - for pix in (0..pixels.len()).step_by(4) { - // assume rgba - let mut hex = String::new(); - hex.push_str(&format!("{:X}", pixels[pix])); - hex.push_str(&format!("{:X}", pixels[pix + 1])); - hex.push_str(&format!("{:X}", pixels[pix + 2])); - - unique_colors.insert(hex); - } - unique_colors -} - #[wasm_bindgen] -pub async fn spiegel(photon_image: PhotonImage, median_kernelsize: u32) -> PhotonImage { +pub fn spiegel(photon_image: &mut PhotonImage, median_kernelsize: u32) { + // let width = photon_image.get_width(); + // let height = photon_image.get_height(); + + // let raw_pixels = photon_image.get_raw_pixels().to_vec(); //argh!, slice should work but doesn't + // let rs_image = RgbImage::from_vec(width, height, raw_pixels).unwrap(); + + // // // // println!("applying gaussian blur filter"); + // // let gauss = imageproc::filter::gaussian_blur_f32(rs_image, 4.0); + // // // println!("applying median filter"); + // let median = imageproc::filter::median_filter(&rs_image, median_kernelsize, median_kernelsize) + // .into_raw(); + // // // println!("applying color quantization filter"); + // // // let quantized = quantizer::quantize(&median, 256); + + // // // println!("applying samples"); + // // let out = block_on(apply_samples_to_image(&mut median)); + + // *photon_image = PhotonImage::new(median, width, height); + // let width = photon_image.get_width(); let height = photon_image.get_height(); + if width == 0 || height == 0 { + return; + } + let raw_pixels = photon_image.get_raw_pixels().to_vec(); //argh!, slice should work but doesn't - let rs_image = RgbImage::from_vec(width, height, raw_pixels).unwrap(); + let rs_image = RgbaImage::from_vec(width, height, raw_pixels).unwrap(); - // println!("applying gaussian blur filter"); - // let gauss = imageproc::filter::gaussian_blur_f32(&src, 4.0); - println!("applying median filter"); - let median = imageproc::filter::median_filter(&rs_image, median_kernelsize, median_kernelsize); - println!("applying color quantization filter"); - let quantized = quantizer::quantize(&median, 256); + let mut filtered = + imageproc::filter::median_filter(&rs_image, median_kernelsize, median_kernelsize); + let out = block_on(apply_samples_to_image(&mut filtered)); - println!("applying samples"); - let out = apply_samples_to_image(quantized).await.into_raw(); - - PhotonImage::new(out, width, height) + *photon_image = PhotonImage::new(out.into_raw(), width, height); } -async fn apply_samples_to_image(mut src: RgbImage) -> RgbImage { - let mut imgbuf = RgbImage::new(src.width(), src.height()); +async fn apply_samples_to_image(src: &mut RgbaImage) -> RgbaImage { + log("applying"); + let mut imgbuf = RgbaImage::new(src.width(), src.height()); unsafe { for y in 0..src.height() { for x in 0..src.width() { let pixel = &src.unsafe_get_pixel(x, y); if imgbuf.unsafe_get_pixel(x, y).channels() == [0, 0, 0] { if let Ok(sample) = get_image(pixel).await { - fill(&mut src, sample, &mut imgbuf, pixel, x, y); + fill(src, sample, &mut imgbuf, pixel, x, y); } } } @@ -87,8 +90,27 @@ async fn apply_samples_to_image(mut src: RgbImage) -> RgbImage { imgbuf } -async fn get_image(pixel: &Rgb) -> anyhow::Result { +#[wasm_bindgen] +extern "C" { + // Use `js_namespace` here to bind `console.log(..)` instead of just + // `log(..)` + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); + + // The `console.log` is quite polymorphic, so we can bind it with multiple + // signatures. Note that we need to use `js_name` to ensure we always call + // `log` in JS. + #[wasm_bindgen(js_namespace = console, js_name = log)] + fn log_u32(a: u32); + + // Multiple arguments too! + #[wasm_bindgen(js_namespace = console, js_name = log)] + fn log_many(a: &str, b: &str); +} + +async fn get_image(pixel: &Rgba) -> anyhow::Result { let rgb = format!("{:02X?}{:02X?}{:02X?}", pixel[0], pixel[1], pixel[2]); + log(&format!("get {}", rgb)); let bytes = reqwest_wasm::get(format!("/api/color/{}", rgb)) .await? @@ -100,7 +122,7 @@ async fn get_image(pixel: &Rgb) -> anyhow::Result { .with_guessed_format() .unwrap() .decode()? - .as_rgb8() + .as_rgba8() .unwrap() .clone()) @@ -108,14 +130,14 @@ async fn get_image(pixel: &Rgb) -> anyhow::Result { } fn fill( - src: &mut ImageBuffer, Vec>, - sample: RgbImage, - dest: &mut ImageBuffer, Vec>, - color: &Rgb, + src: &mut RgbaImage, + sample: RgbaImage, + dest: &mut RgbaImage, + color: &Rgba, px: u32, py: u32, ) { - if color.channels() == [0, 0, 0] { + if color.channels() == [0, 0, 0, 0] { return; } let height = sample.height(); @@ -141,7 +163,7 @@ fn fill( yy -= height; } dest.put_pixel(x, y, *sample.get_pixel(xx, yy)); - src.put_pixel(x, y, Rgb([0, 0, 0])); + src.put_pixel(x, y, Rgba([0, 0, 0, 0])); if x > 1 { points.push(Point::new(x - 1, y)); } @@ -163,7 +185,7 @@ fn fill( } } -fn is_same(p1: &Rgb, p2: &Rgb) -> bool { +fn is_same(p1: &Rgba, p2: &Rgba) -> bool { let p1 = p1.channels(); let p2 = p2.channels(); i16::abs(p1[0] as i16 - p2[0] as i16) < 4 @@ -171,27 +193,11 @@ fn is_same(p1: &Rgb, p2: &Rgb) -> bool { && i16::abs(p1[2] as i16 - p2[2] as i16) < 4 } -fn get_closest<'a>( - color_samples: &'a Vec, - pixel: &Rgb, -) -> Option<&'a ColorSample> { - let mut closest = None; - let mut min_diff: f32 = 4294967295.0; //0xFFFFFFFF - for sample in color_samples { - let diff = get_distance(sample.r, sample.g, sample.b, pixel); - if diff < min_diff { - closest = Some(sample); - min_diff = diff; - } - } - - closest -} - -fn get_distance(r: u8, g: u8, b: u8, c2: &Rgb) -> f32 { +fn get_distance(r: u8, g: u8, b: u8, c2: &Rgba) -> f32 { let red_dif = r as f32 - c2.channels()[0] as f32; let green_dif = g as f32 - c2.channels()[1] as f32; let blue_dif = b as f32 - c2.channels()[2] as f32; + // ignore alpha channel return f32::sqrt(red_dif * red_dif + green_dif * green_dif + blue_dif * blue_dif); }