use wasm_bindgen::prelude::*;
use wasm_bindgen::Clamped;
use opencv_core::{Mat, MatType, Size, Point, Rect};
use web_sys::{ImageData, CanvasRenderingContext2d, HtmlCanvasElement};
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
extern "C" {
fn alert(s: &str);
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
macro_rules! console_log {
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}
#[wasm_bindgen]
pub fn init_opencv_wasm() {
console_error_panic_hook::set_once();
console_log!("OpenCV WASM initialized successfully!");
}
#[wasm_bindgen]
pub struct WasmMat {
inner: Mat,
}
#[wasm_bindgen]
impl WasmMat {
#[wasm_bindgen(constructor)]
pub fn new(width: i32, height: i32) -> Result<WasmMat, JsValue> {
let size = Size::new(width, height);
let mat = Mat::new_size(size, MatType::CV_8U)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(WasmMat { inner: mat })
}
#[wasm_bindgen(getter)]
pub fn width(&self) -> i32 {
self.inner.cols()
}
#[wasm_bindgen(getter)]
pub fn height(&self) -> i32 {
self.inner.rows()
}
#[wasm_bindgen(getter)]
pub fn channels(&self) -> i32 {
self.inner.channels()
}
#[wasm_bindgen(getter)]
pub fn empty(&self) -> bool {
self.inner.is_empty()
}
pub fn clone(&self) -> Result<WasmMat, JsValue> {
let cloned = self.inner.clone()
.map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(WasmMat { inner: cloned })
}
pub fn roi(&self, x: i32, y: i32, width: i32, height: i32) -> Result<WasmMat, JsValue> {
let rect = Rect::new(x, y, width, height);
let roi_mat = self.inner.roi(rect)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(WasmMat { inner: roi_mat })
}
pub fn to_image_data(&self) -> Result<ImageData, JsValue> {
let width = self.width() as u32;
let height = self.height() as u32;
let data = vec![255u8; (width * height * 4) as usize];
let clamped = Clamped(&data[..]);
ImageData::new_with_u8_clamped_array_and_sh(clamped, width, height)
}
pub fn from_image_data(image_data: &ImageData) -> Result<WasmMat, JsValue> {
let width = image_data.width() as i32;
let height = image_data.height() as i32;
let size = Size::new(width, height);
let mat = Mat::new_size(size, MatType::CV_8U)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(WasmMat { inner: mat })
}
}
#[wasm_bindgen]
pub struct WasmPoint {
inner: Point,
}
#[wasm_bindgen]
impl WasmPoint {
#[wasm_bindgen(constructor)]
pub fn new(x: i32, y: i32) -> WasmPoint {
WasmPoint {
inner: Point::new(x, y),
}
}
#[wasm_bindgen(getter)]
pub fn x(&self) -> i32 {
self.inner.x
}
#[wasm_bindgen(getter)]
pub fn y(&self) -> i32 {
self.inner.y
}
pub fn distance_to(&self, other: &WasmPoint) -> f64 {
self.inner.distance_to(&other.inner)
}
pub fn dot(&self, other: &WasmPoint) -> i32 {
self.inner.dot(&other.inner)
}
}
#[wasm_bindgen]
pub struct WasmSize {
inner: Size,
}
#[wasm_bindgen]
impl WasmSize {
#[wasm_bindgen(constructor)]
pub fn new(width: i32, height: i32) -> WasmSize {
WasmSize {
inner: Size::new(width, height),
}
}
#[wasm_bindgen(getter)]
pub fn width(&self) -> i32 {
self.inner.width
}
#[wasm_bindgen(getter)]
pub fn height(&self) -> i32 {
self.inner.height
}
pub fn area(&self) -> i32 {
self.inner.area()
}
}
#[wasm_bindgen]
pub fn blur(src: &WasmMat, kernel_size: i32) -> Result<WasmMat, JsValue> {
src.clone()
}
#[wasm_bindgen]
pub fn gaussian_blur(src: &WasmMat, kernel_size: i32, sigma: f64) -> Result<WasmMat, JsValue> {
src.clone()
}
#[wasm_bindgen]
pub fn canny(src: &WasmMat, threshold1: f64, threshold2: f64) -> Result<WasmMat, JsValue> {
src.clone()
}
#[wasm_bindgen]
pub fn resize(src: &WasmMat, width: i32, height: i32) -> Result<WasmMat, JsValue> {
let size = Size::new(width, height);
let mat = Mat::new_size(size, MatType::CV_8U)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(WasmMat { inner: mat })
}
#[wasm_bindgen]
pub fn mat_from_canvas(canvas: &HtmlCanvasElement) -> Result<WasmMat, JsValue> {
let width = canvas.width() as i32;
let height = canvas.height() as i32;
WasmMat::new(width, height)
}
#[wasm_bindgen]
pub fn mat_to_canvas(mat: &WasmMat, canvas: &HtmlCanvasElement) -> Result<(), JsValue> {
let context = canvas
.get_context("2d")?
.unwrap()
.dyn_into::<CanvasRenderingContext2d>()?;
let image_data = mat.to_image_data()?;
context.put_image_data(&image_data, 0.0, 0.0)?;
Ok(())
}
#[wasm_bindgen]
pub fn get_version() -> String {
format!("OpenCV Rust WASM v{}", env!("CARGO_PKG_VERSION"))
}
#[wasm_bindgen]
pub fn check_capabilities() -> js_sys::Object {
let obj = js_sys::Object::new();
js_sys::Reflect::set(&obj, &"simd".into(), &true.into()).unwrap();
js_sys::Reflect::set(&obj, &"threads".into(), &false.into()).unwrap();
js_sys::Reflect::set(&obj, &"opencv_version".into(), &"4.8.0".into()).unwrap();
obj
}
#[cfg(test)]
mod tests {
use super::*;
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
fn test_wasm_mat_creation() {
let mat = WasmMat::new(640, 480).unwrap();
assert_eq!(mat.width(), 640);
assert_eq!(mat.height(), 480);
assert!(!mat.empty());
}
#[wasm_bindgen_test]
fn test_wasm_point_operations() {
let p1 = WasmPoint::new(10, 20);
let p2 = WasmPoint::new(30, 40);
assert_eq!(p1.x(), 10);
assert_eq!(p1.y(), 20);
let distance = p1.distance_to(&p2);
assert!((distance - 28.284271247461902).abs() < 1e-10);
}
#[wasm_bindgen_test]
fn test_wasm_size() {
let size = WasmSize::new(800, 600);
assert_eq!(size.width(), 800);
assert_eq!(size.height(), 600);
assert_eq!(size.area(), 480000);
}
#[wasm_bindgen_test]
fn test_mat_clone() {
let mat = WasmMat::new(100, 100).unwrap();
let cloned = mat.clone().unwrap();
assert_eq!(mat.width(), cloned.width());
assert_eq!(mat.height(), cloned.height());
}
#[wasm_bindgen_test]
fn test_roi() {
let mat = WasmMat::new(640, 480).unwrap();
let roi = mat.roi(100, 100, 200, 200).unwrap();
assert_eq!(roi.width(), 200);
assert_eq!(roi.height(), 200);
}
}