opencv_wasm/
lib.rs

1//! OpenCV WebAssembly bindings for browser deployment
2
3use wasm_bindgen::prelude::*;
4use wasm_bindgen::Clamped;
5use opencv_core::{Mat, MatType, Size, Point, Rect};
6use web_sys::{ImageData, CanvasRenderingContext2d, HtmlCanvasElement};
7
8// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global allocator.
9#[cfg(feature = "wee_alloc")]
10#[global_allocator]
11static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
12
13#[wasm_bindgen]
14extern "C" {
15    fn alert(s: &str);
16    
17    #[wasm_bindgen(js_namespace = console)]
18    fn log(s: &str);
19}
20
21// Macro for console.log
22macro_rules! console_log {
23    ($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
24}
25
26#[wasm_bindgen]
27pub fn init_opencv_wasm() {
28    console_error_panic_hook::set_once();
29    console_log!("OpenCV WASM initialized successfully!");
30}
31
32/// WebAssembly-compatible Mat wrapper
33#[wasm_bindgen]
34pub struct WasmMat {
35    inner: Mat,
36}
37
38#[wasm_bindgen]
39impl WasmMat {
40    #[wasm_bindgen(constructor)]
41    pub fn new(width: i32, height: i32) -> Result<WasmMat, JsValue> {
42        let size = Size::new(width, height);
43        let mat = Mat::new_size(size, MatType::CV_8U)
44            .map_err(|e| JsValue::from_str(&e.to_string()))?;
45        
46        Ok(WasmMat { inner: mat })
47    }
48
49    #[wasm_bindgen(getter)]
50    pub fn width(&self) -> i32 {
51        self.inner.cols()
52    }
53
54    #[wasm_bindgen(getter)]
55    pub fn height(&self) -> i32 {
56        self.inner.rows()
57    }
58
59    #[wasm_bindgen(getter)]
60    pub fn channels(&self) -> i32 {
61        self.inner.channels()
62    }
63
64    #[wasm_bindgen(getter)]
65    pub fn empty(&self) -> bool {
66        self.inner.is_empty()
67    }
68
69    /// Clone the matrix
70    pub fn clone(&self) -> Result<WasmMat, JsValue> {
71        let cloned = self.inner.clone()
72            .map_err(|e| JsValue::from_str(&e.to_string()))?;
73        Ok(WasmMat { inner: cloned })
74    }
75
76    /// Get region of interest
77    pub fn roi(&self, x: i32, y: i32, width: i32, height: i32) -> Result<WasmMat, JsValue> {
78        let rect = Rect::new(x, y, width, height);
79        let roi_mat = self.inner.roi(rect)
80            .map_err(|e| JsValue::from_str(&e.to_string()))?;
81        Ok(WasmMat { inner: roi_mat })
82    }
83
84    /// Convert to ImageData for canvas rendering
85    pub fn to_image_data(&self) -> Result<ImageData, JsValue> {
86        let width = self.width() as u32;
87        let height = self.height() as u32;
88        
89        // For now, create a placeholder ImageData
90        // In a full implementation, this would convert Mat data to RGBA format
91        let data = vec![255u8; (width * height * 4) as usize];
92        let clamped = Clamped(&data[..]);
93        
94        ImageData::new_with_u8_clamped_array_and_sh(clamped, width, height)
95    }
96
97    /// Create Mat from ImageData
98    pub fn from_image_data(image_data: &ImageData) -> Result<WasmMat, JsValue> {
99        let width = image_data.width() as i32;
100        let height = image_data.height() as i32;
101        
102        let size = Size::new(width, height);
103        let mat = Mat::new_size(size, MatType::CV_8U)
104            .map_err(|e| JsValue::from_str(&e.to_string()))?;
105        
106        Ok(WasmMat { inner: mat })
107    }
108}
109
110/// WebAssembly-compatible Point wrapper
111#[wasm_bindgen]
112pub struct WasmPoint {
113    inner: Point,
114}
115
116#[wasm_bindgen]
117impl WasmPoint {
118    #[wasm_bindgen(constructor)]
119    pub fn new(x: i32, y: i32) -> WasmPoint {
120        WasmPoint {
121            inner: Point::new(x, y),
122        }
123    }
124
125    #[wasm_bindgen(getter)]
126    pub fn x(&self) -> i32 {
127        self.inner.x
128    }
129
130    #[wasm_bindgen(getter)]
131    pub fn y(&self) -> i32 {
132        self.inner.y
133    }
134
135    pub fn distance_to(&self, other: &WasmPoint) -> f64 {
136        self.inner.distance_to(&other.inner)
137    }
138
139    pub fn dot(&self, other: &WasmPoint) -> i32 {
140        self.inner.dot(&other.inner)
141    }
142}
143
144/// WebAssembly-compatible Size wrapper
145#[wasm_bindgen]
146pub struct WasmSize {
147    inner: Size,
148}
149
150#[wasm_bindgen]
151impl WasmSize {
152    #[wasm_bindgen(constructor)]
153    pub fn new(width: i32, height: i32) -> WasmSize {
154        WasmSize {
155            inner: Size::new(width, height),
156        }
157    }
158
159    #[wasm_bindgen(getter)]
160    pub fn width(&self) -> i32 {
161        self.inner.width
162    }
163
164    #[wasm_bindgen(getter)]
165    pub fn height(&self) -> i32 {
166        self.inner.height
167    }
168
169    pub fn area(&self) -> i32 {
170        self.inner.area()
171    }
172}
173
174// Image processing functions as standalone functions instead of module
175#[wasm_bindgen]
176pub fn blur(src: &WasmMat, kernel_size: i32) -> Result<WasmMat, JsValue> {
177    // For now, return a clone - full implementation would call opencv_imgproc::blur
178    src.clone()
179}
180
181#[wasm_bindgen]
182pub fn gaussian_blur(src: &WasmMat, kernel_size: i32, sigma: f64) -> Result<WasmMat, JsValue> {
183    // For now, return a clone - full implementation would call opencv_imgproc::gaussian_blur
184    src.clone()
185}
186
187#[wasm_bindgen]
188pub fn canny(src: &WasmMat, threshold1: f64, threshold2: f64) -> Result<WasmMat, JsValue> {
189    // For now, return a clone - full implementation would call opencv_imgproc::canny
190    src.clone()
191}
192
193#[wasm_bindgen]
194pub fn resize(src: &WasmMat, width: i32, height: i32) -> Result<WasmMat, JsValue> {
195    // Create new Mat with target size
196    let size = Size::new(width, height);
197    let mat = Mat::new_size(size, MatType::CV_8U)
198        .map_err(|e| JsValue::from_str(&e.to_string()))?;
199    
200    Ok(WasmMat { inner: mat })
201}
202
203// Utility functions
204#[wasm_bindgen]
205pub fn mat_from_canvas(canvas: &HtmlCanvasElement) -> Result<WasmMat, JsValue> {
206    let width = canvas.width() as i32;
207    let height = canvas.height() as i32;
208    
209    WasmMat::new(width, height)
210}
211
212#[wasm_bindgen]
213pub fn mat_to_canvas(mat: &WasmMat, canvas: &HtmlCanvasElement) -> Result<(), JsValue> {
214    let context = canvas
215        .get_context("2d")?
216        .unwrap()
217        .dyn_into::<CanvasRenderingContext2d>()?;
218
219    let image_data = mat.to_image_data()?;
220    context.put_image_data(&image_data, 0.0, 0.0)?;
221    
222    Ok(())
223}
224
225#[wasm_bindgen]
226pub fn get_version() -> String {
227    format!("OpenCV Rust WASM v{}", env!("CARGO_PKG_VERSION"))
228}
229
230#[wasm_bindgen]
231pub fn check_capabilities() -> js_sys::Object {
232    let obj = js_sys::Object::new();
233    js_sys::Reflect::set(&obj, &"simd".into(), &true.into()).unwrap();
234    js_sys::Reflect::set(&obj, &"threads".into(), &false.into()).unwrap();
235    js_sys::Reflect::set(&obj, &"opencv_version".into(), &"4.8.0".into()).unwrap();
236    obj
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242    use wasm_bindgen_test::*;
243
244    wasm_bindgen_test_configure!(run_in_browser);
245
246    #[wasm_bindgen_test]
247    fn test_wasm_mat_creation() {
248        let mat = WasmMat::new(640, 480).unwrap();
249        assert_eq!(mat.width(), 640);
250        assert_eq!(mat.height(), 480);
251        assert!(!mat.empty());
252    }
253
254    #[wasm_bindgen_test]
255    fn test_wasm_point_operations() {
256        let p1 = WasmPoint::new(10, 20);
257        let p2 = WasmPoint::new(30, 40);
258        
259        assert_eq!(p1.x(), 10);
260        assert_eq!(p1.y(), 20);
261        
262        let distance = p1.distance_to(&p2);
263        assert!((distance - 28.284271247461902).abs() < 1e-10);
264    }
265
266    #[wasm_bindgen_test]
267    fn test_wasm_size() {
268        let size = WasmSize::new(800, 600);
269        assert_eq!(size.width(), 800);
270        assert_eq!(size.height(), 600);
271        assert_eq!(size.area(), 480000);
272    }
273
274    #[wasm_bindgen_test]
275    fn test_mat_clone() {
276        let mat = WasmMat::new(100, 100).unwrap();
277        let cloned = mat.clone().unwrap();
278        
279        assert_eq!(mat.width(), cloned.width());
280        assert_eq!(mat.height(), cloned.height());
281    }
282
283    #[wasm_bindgen_test]
284    fn test_roi() {
285        let mat = WasmMat::new(640, 480).unwrap();
286        let roi = mat.roi(100, 100, 200, 200).unwrap();
287        
288        assert_eq!(roi.width(), 200);
289        assert_eq!(roi.height(), 200);
290    }
291}