integrated rust imagetransform and webapp

This commit is contained in:
Sander Hautvast 2022-02-24 09:58:24 +01:00
parent 5f434d5e98
commit f8299c7186
6 changed files with 106 additions and 56 deletions

8
Cargo.lock generated
View file

@ -304,6 +304,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "image" name = "image"
version = "0.23.14" version = "0.23.14"
@ -954,8 +960,10 @@ name = "yew-app"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"gloo-utils", "gloo-utils",
"hex",
"image", "image",
"imageproc", "imageproc",
"lazy_static",
"log", "log",
"wasm-bindgen", "wasm-bindgen",
"wasm-logger", "wasm-logger",

View file

@ -28,3 +28,5 @@ web-sys = {version = "0.3.56", features = [
yew = "0.19" yew = "0.19"
image = "0.23.14" image = "0.23.14"
imageproc="0.22.0" imageproc="0.22.0"
lazy_static="1.4.0"
hex="0.4.3"

View file

@ -1,24 +1,33 @@
use gloo_utils::document; use gloo_utils::document;
use wasm_bindgen::JsCast; use image::RgbImage;
use wasm_bindgen::{Clamped, JsCast};
use web_sys::{DragEvent, HtmlImageElement}; use web_sys::{DragEvent, HtmlImageElement};
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, ImageData}; use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, ImageData};
use web_sys::Url; use web_sys::Url;
use yew::{Component, Context, html, Html}; use yew::{Component, Context, html, Html};
use crate::transform;
use crate::transform::ColorSample;
pub enum Msg { pub enum Msg {
Dropped(DragEvent), Dropped(DragEvent),
Dragged(DragEvent), Dragged(DragEvent),
ImageLoaded, ImageLoaded,
} }
pub struct DropPhoto {} pub struct DropPhoto {
// color_samples: Vec<ColorSample>
}
impl Component for DropPhoto { impl Component for DropPhoto {
type Message = Msg; type Message = Msg;
type Properties = (); type Properties = ();
fn create(_ctx: &Context<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
Self {} transform::init();
Self {
// color_samples: transform::init().expect("")
}
} }
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
@ -44,7 +53,6 @@ impl Component for DropPhoto {
let img = document().get_element_by_id("source-image").expect("cannot get #source-image").dyn_into::<HtmlImageElement>().unwrap(); let img = document().get_element_by_id("source-image").expect("cannot get #source-image").dyn_into::<HtmlImageElement>().unwrap();
img.set_src(&url); img.set_src(&url);
} }
} }
true true
} }
@ -64,6 +72,17 @@ impl Component for DropPhoto {
if let Some(drop_zone) = document().get_element_by_id("drop-zone") { if let Some(drop_zone) = document().get_element_by_id("drop-zone") {
drop_zone.set_attribute("style", "display:none").expect("Cannot update attribute"); drop_zone.set_attribute("style", "display:none").expect("Cannot update attribute");
} }
let imgdata = ctx
.get_image_data(0.0, 0.0, canvas.width() as f64, canvas.height() as f64)
.unwrap();
let raw_pixels: Vec<u8> = imgdata.data().to_vec();
let rgb_src = RgbImage::from_raw(canvas.width(), canvas.height(), raw_pixels).unwrap();
// let transformed = transform::apply(rgb_src, &self.color_samples).expect("Cannot transform image");
let image_data = ImageData::new_with_u8_clamped_array_and_sh(Clamped(&rgb_src.to_vec()),
canvas.width(), canvas.height());
ctx.put_image_data(&image_data.expect(""), 0.0, 0.0);
} }
true true
} }

View file

@ -1,6 +1,7 @@
mod app; mod app;
mod transform; mod transform;
mod quantizer; mod quantizer;
mod samples;
fn main() { fn main() {
wasm_logger::init(wasm_logger::Config::default()); wasm_logger::init(wasm_logger::Config::default());

View file

@ -1,37 +1,38 @@
use std::{error::Error, fs, result::Result}; use std::{error::Error, fs, result::Result};
use std::collections::HashMap;
use image::{GenericImageView, ImageBuffer, Pixel, Rgb, RgbImage}; use image::{GenericImageView, ImageBuffer, Pixel, Rgb, RgbImage};
use imageproc::point::Point; use imageproc::point::Point;
use crate::quantizer;
pub fn run(image_filename: &String) -> Result<(), Box<dyn Error>> { use crate::{quantizer, samples};
println!("reading image samples");
let color_samples = read_color_samples()?;
let src: RgbImage = image::open(image_filename).unwrap().into_rgb8(); struct Transformer {
color_samples: HashMap<String, ColorSample>, //cache
println!("applying gaussian blur filter");
let gauss = imageproc::filter::gaussian_blur_f32(&src, 2.0);
println!("applying median filter");
let median = imageproc::filter::median_filter(&gauss, 2, 2);
println!("applying color quantization filter");
let quantized = quantizer::quantize(&median, 256);
println!("applying samples");
let out = apply_samples_to_image(quantized, &color_samples);
out.save_with_format("output.jpg", image::ImageFormat::Jpeg)?;
Ok(())
} }
fn apply_samples_to_image(mut src: RgbImage, color_samples: &Vec<ColorSample>) -> RgbImage{ impl Transformer {
fn new() -> Self {
Self { color_samples: HashMap::new() }
}
pub fn apply(&mut self, src: RgbImage, color_samples: &mut HashMap<String, ColorSample>) -> Result<RgbImage, Box<dyn Error>> {
let gauss = imageproc::filter::gaussian_blur_f32(&src, 2.0);
let median = imageproc::filter::median_filter(&gauss, 2, 2);
let quantized = quantizer::quantize(&median, 256);
let out = apply_samples_to_image(quantized, color_samples);
Ok(out)
}
}
fn apply_samples_to_image(mut src: RgbImage, color_samples: &mut HashMap<String, ColorSample>) -> RgbImage {
let mut imgbuf = RgbImage::new(src.width(), src.height()); let mut imgbuf = RgbImage::new(src.width(), src.height());
unsafe { unsafe {
for y in 0..src.height() { for y in 0..src.height() {
for x in 0..src.width() { for x in 0..src.width() {
let pixel = &src.unsafe_get_pixel(x, y); let pixel = &src.unsafe_get_pixel(x, y);
if imgbuf.unsafe_get_pixel(x, y).channels() == [0, 0, 0] { if imgbuf.unsafe_get_pixel(x, y).channels() == [0, 0, 0] {
if let Some(sample) = get_closest(&color_samples, pixel) { if let Some(sample) = get_closest(olor_samples, pixel) {
fill(&mut src, sample, &mut imgbuf, pixel, x, y); fill(&mut src, sample, &mut imgbuf, pixel, x, y);
} }
} }
@ -105,21 +106,31 @@ fn is_same(p1: &Rgb<u8>, p2: &Rgb<u8>) -> bool {
&& i16::abs(p1[2] as i16 - p2[2] as i16) < 4 && i16::abs(p1[2] as i16 - p2[2] as i16) < 4
} }
fn get_closest<'a>( fn get_closest(
color_samples: &'a Vec<ColorSample>, color_samples: &mut HashMap<String, ColorSample>,
pixel: &Rgb<u8>, pixel: &Rgb<u8>,
) -> Option<&'a ColorSample> { ) -> Option<&ColorSample> {
let mut closest = None; let mut closest = None;
let mut min_diff: f32 = 4294967295.0; //0xFFFFFFFF let mut min_diff: f32 = 4294967295.0; //0xFFFFFFFF
for sample in color_samples { for sample in samples::SAMPLES {
let diff = get_distance(sample.r, sample.g, sample.b, pixel); let r = hex::decode(sample[0..2], ).unwrap()[0];
let g = hex::decode(sample[2..4], ).unwrap()[0];
let b = hex::decode(sample[4..6], ).unwrap()[0];
let diff = get_distance(r,g,b, pixel);
if diff < min_diff { if diff < min_diff {
closest = Some(sample); closest = Some(sample);
min_diff = diff; min_diff = diff;
} }
} }
closest if color_samples.contains(&closest){
return color_samples.get(&closest);
} else {
//download image
let image = ColorSample{r,g,b, image: RgbImage::from_raw(0,0,vec![]).unwrap()};
color_samples.insert(sample.to_owned(), image);
}
None
} }
fn get_distance(r: u8, g: u8, b: u8, c2: &Rgb<u8>) -> f32 { fn get_distance(r: u8, g: u8, b: u8, c2: &Rgb<u8>) -> f32 {
@ -129,36 +140,46 @@ fn get_distance(r: u8, g: u8, b: u8, c2: &Rgb<u8>) -> f32 {
return f32::sqrt(red_dif * red_dif + green_dif * green_dif + blue_dif * blue_dif); return f32::sqrt(red_dif * red_dif + green_dif * green_dif + blue_dif * blue_dif);
} }
fn read_color_samples() -> Result<Vec<ColorSample>, Box<dyn Error>> { // fn read_color_samples() -> Result<Vec<ColorSample>, Box<dyn Error>> {
let paths = fs::read_dir("samples")?; // let paths = fs::read_dir("samples")?;
let mut color_samples: Vec<ColorSample> = Vec::new(); // let mut color_samples: Vec<ColorSample> = Vec::new();
for path in paths { // for path in paths {
let path = path?.path(); // let path = path?.path();
let filename = path.to_str().unwrap().to_owned(); // let filename = path.to_str().unwrap().to_owned();
//
// if filename.ends_with(".jpg") {
// let sample_image: RgbImage = image::open(&filename).unwrap().into_rgb8();
// let hex_r = &filename[8..10];
// let hex_g = &filename[10..12];
// let hex_b = &filename[12..14];
// color_samples.push(ColorSample {
// r: u8::from_str_radix(&hex_r, 16).unwrap(),
// g: u8::from_str_radix(&hex_g, 16).unwrap(),
// b: u8::from_str_radix(&hex_b, 16).unwrap(),
// image: sample_image,
// });
// }
// }
// Ok(color_samples)
// }
if filename.ends_with(".jpg") { pub struct ColorSample {
let sample_image: RgbImage = image::open(&filename).unwrap().into_rgb8();
let hex_r = &filename[8..10];
let hex_g = &filename[10..12];
let hex_b = &filename[12..14];
color_samples.push(ColorSample {
r: u8::from_str_radix(&hex_r, 16).unwrap(),
g: u8::from_str_radix(&hex_g, 16).unwrap(),
b: u8::from_str_radix(&hex_b, 16).unwrap(),
image: sample_image,
});
}
}
Ok(color_samples)
}
struct ColorSample {
r: u8, r: u8,
g: u8, g: u8,
b: u8, b: u8,
image: RgbImage, image: RgbImage,
} }
impl PartialEq for ColorSample{
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
fn ne(&self, other: &Self) -> bool {
self.name != other.name
}
}
#[derive(Debug)] #[derive(Debug)]
struct List { struct List {
head: Option<Box<Node>>, head: Option<Box<Node>>,
@ -197,7 +218,6 @@ struct Node {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
#[test] #[test]