integrated rust imagetransform and webapp
This commit is contained in:
parent
5f434d5e98
commit
f8299c7186
6 changed files with 106 additions and 56 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
27
src/app.rs
27
src/app.rs
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
120
src/transform.rs
120
src/transform.rs
|
|
@ -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]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue