Final application
You should have this file tree layout:
$ tree
.
├── Cargo.lock
├── Cargo.toml
├── Makefile
├── app
│ ├── app.js
│ ├── image_filter.js <-- generated file
│ ├── image_filter_bg.wasm <-- generated file
│ └── index.html
└── src
└── lib.rs
To recap your final Rust code should look something like this:
use std::io::Cursor;
use std::panic;
use wasm_bindgen::prelude::*;
use rustagram::image::io::Reader;
use rustagram::image::ImageOutputFormat;
use rustagram::RustagramFilter;
#[wasm_bindgen(start)]
pub fn main() {
panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init_with_level(log::Level::Debug).unwrap();
}
#[wasm_bindgen]
pub fn apply_filter(img: &[u8], filter: &str) -> Vec<u8> {
log::debug!("image: {} bytes, filter: {:?}", img.len(), filter);
let img = Reader::new(Cursor::new(img))
.with_guessed_format()
.unwrap()
.decode()
.unwrap();
let filter_type = filter.parse().unwrap();
let out = img.to_rgba8().apply_filter(filter_type);
let mut bytes: Vec<u8> = Vec::new();
out.write_to(&mut Cursor::new(&mut bytes), ImageOutputFormat::Png)
.unwrap();
bytes
}
The frontend in HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Rust Image filter</title>
</head>
<body>
<input type="file" id="files" name="file" accept="image/png, image/jpeg" />
<select name="filter">
<option value="None">none</option>
<option value="1977" selected>1977</option>
<option value="Aden">Aden</option>
<option value="Brannan">Brannan</option>
<option value="Brooklyn">Brooklyn</option>
<option value="Clarendon">Clarendon</option>
<option value="Earlybird">Earlybird</option>
<option value="Gingham">Gingham</option>
<option value="Hudson">Hudson</option>
<option value="Inkwell">Inkwell</option>
<option value="Kelvin">Kelvin</option>
<option value="Lark">Lark</option>
<option value="Lofi">Lofi</option>
<option value="Maven">Maven</option>
<option value="Mayfair">Mayfair</option>
<option value="Moon">Moon</option>
<option value="Nashville">Nashville</option>
<option value="Reyes">Reyes</option>
<option value="Rise">Rise</option>
<option value="Slumber">Slumber</option>
<option value="Stinson">Stinson</option>
<option value="Toaster">Toaster</option>
<option value="Valencia">Valencia</option>
<option value="Walden">Walden</option>
</select>
<span></span>
<br>
<img />
<script type="module" src="app.js"></script>
</body>
</html>
The JavaScript frontend code:
import init, { apply_filter } from './image_filter.js';
await init();
document.querySelector('input[type=file]').onchange = (evt) => {
imageFilter();
};
document.querySelector('select').onchange = (evt) => {
imageFilter();
};
function typedArrayToURL(typedArray) {
return URL.createObjectURL(
new Blob([typedArray.buffer], { type: "image/png" })
);
}
async function imageFilter() {
let files = document.getElementById('files').files;
if (!files.length) {
return;
}
let file = files[0];
let span = document.querySelector('span');
span.innerText = "working...";
let imgEl = document.querySelector('img');
imgEl.src = URL.createObjectURL(file);
imgEl.width = "500";
let filter = document.querySelector("select").value.toLowerCase();
if (filter == "none") {
span.innerText = "done.";
return;
}
let img = await file.arrayBuffer();
let result = apply_filter(new Uint8Array(img), filter);
let blobUrl = typedArrayToURL(result);
imgEl.src = blobUrl;
imgEl.width = "500";
span.innerText = "done.";
}
A demo deployment is available at:
https://tmp.fnordig.de/wasm/image-filter/
Some ideas on what to do next:
- The code
unwrap
s a lot. Introduce some error handling. Can you return an error from your wasm module?