mod utils; use image::{DynamicImage, RgbaImage}; use std::collections::HashMap; use wasm_bindgen::prelude::*; use web_sys::*; const CANVAS_WIDTH: u32 = 1920; const CANVAS_HEIGHT: u32 = 1080; const VIDEO_ID: &str = "video"; const CANVAS_ID: &str = "canvas"; const BARCODE_ID: &str = "barcode"; const SCAN_BUTTON_ID: &str = "scan_barcode_button"; #[wasm_bindgen] extern "C" { fn alert(s: &str); #[wasm_bindgen(js_namespace = console)] fn log(s: &str); #[wasm_bindgen(js_namespace = console)] fn error(s: &str); } #[wasm_bindgen] pub fn init_camera_feed() -> Result<(), String> { utils::set_panic_hook(); let window = window().unwrap(); let document = window.document().unwrap(); let video: HtmlVideoElement = document .get_element_by_id(VIDEO_ID) .unwrap() .dyn_into() .unwrap(); let video2 = video.clone(); let video3 = video.clone(); let canvas: HtmlCanvasElement = document .get_element_by_id(CANVAS_ID) .unwrap() .dyn_into() .unwrap(); let button: HtmlButtonElement = document .get_element_by_id(SCAN_BUTTON_ID) .unwrap() .dyn_into() .unwrap(); let navigator = window.navigator(); let media_devices = navigator.media_devices().unwrap(); let media_stream_constraints = MediaStreamConstraints::new(); media_stream_constraints.set_video(&JsValue::from_bool(true)); media_stream_constraints.set_audio(&JsValue::from_bool(false)); let user_media = media_devices .get_user_media_with_constraints(&media_stream_constraints) .unwrap(); let succ: Closure = Closure::new(move |stream: JsValue| { video.set_src_object(Some(&MediaStream::unchecked_from_js(stream))); let _ = video.play(); }); let fail: Closure = Closure::new(|err| { error(format!("An error occured: {:?}", err).as_str()); }); let _ = user_media.then(&succ).catch(&fail); succ.forget(); // Memory leak fail.forget(); // Memory leak let set_width_and_height: Closure = Closure::new(move || { let width: u32 = 640; let height: u32 = ((width as f32 / video3.video_width() as f32) * video3.video_height() as f32) as u32; video3.set_width(width); video3.set_height(height); canvas.set_width(CANVAS_WIDTH); canvas.set_height(CANVAS_HEIGHT); }); let _ = video2 .add_event_listener_with_callback("canplay", set_width_and_height.as_ref().unchecked_ref()); set_width_and_height.forget(); // Memory leak // let scan_barcode_wrapper: Closure = Closure::new(move || { match scan_barcode() { Ok(()) => {} Err(e) => error(e.as_str()), }; }); let _ = button .add_event_listener_with_callback("click", scan_barcode_wrapper.as_ref().unchecked_ref()); scan_barcode_wrapper.forget(); // Memory leak Ok(()) } fn convert_rgba_to_luma(data: &[u8]) -> Vec { let rgba = RgbaImage::from_raw((data.len() / 4) as u32, 1, data.to_vec()); let luma = DynamicImage::ImageRgba8(rgba.unwrap()).into_luma8(); luma.to_vec() } fn decode_barcode(data: Vec, width: u32, height: u32) -> Result { let mut hints: rxing::DecodingHintDictionary = HashMap::new(); hints.insert( rxing::DecodeHintType::TRY_HARDER, rxing::DecodeHintValue::TryHarder(true), ); let result = match rxing::helpers::detect_in_luma_with_hints(data, width, height, None, &mut hints) { Ok(r) => r, Err(e) => return Err(e), }; Ok(result.getText().to_string()) } fn scan_barcode() -> Result<(), String> { let window = window().unwrap(); let document = window.document().unwrap(); let video: HtmlVideoElement = document .get_element_by_id(VIDEO_ID) .unwrap() .dyn_into() .unwrap(); let canvas: HtmlCanvasElement = document .get_element_by_id(CANVAS_ID) .unwrap() .dyn_into() .unwrap(); let barcode: HtmlInputElement = document .get_element_by_id(BARCODE_ID) .unwrap() .dyn_into() .unwrap(); let context: CanvasRenderingContext2d = match canvas.get_context("2d").unwrap() { Some(c) => c.dyn_into().unwrap(), None => return Err("Could not get canvas context".to_string()), }; for _ in 0..100 { let _ = context.draw_image_with_html_video_element(&video, 0., 0.); let rgba = context .get_image_data(0., 0., CANVAS_WIDTH.into(), CANVAS_HEIGHT.into()) .unwrap() .data(); let luma = convert_rgba_to_luma(&rgba); let code: String = match decode_barcode(luma, CANVAS_WIDTH, CANVAS_HEIGHT) { Ok(c) => c, Err(rxing::Exceptions::NotFoundException(_)) => { log("."); continue; } Err(e) => return Err(format!("{:?}", e)), }; barcode.set_value(&code); break; } Ok(()) }