2024-08-16 13:05:57 +02:00
|
|
|
mod utils;
|
|
|
|
|
2024-08-19 18:11:59 +02:00
|
|
|
use image::{DynamicImage, RgbaImage};
|
|
|
|
use std::collections::HashMap;
|
2024-08-16 13:05:57 +02:00
|
|
|
use wasm_bindgen::prelude::*;
|
2024-08-19 18:11:59 +02:00
|
|
|
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";
|
2024-08-16 13:05:57 +02:00
|
|
|
|
|
|
|
#[wasm_bindgen]
|
|
|
|
extern "C" {
|
|
|
|
fn alert(s: &str);
|
2024-08-19 18:11:59 +02:00
|
|
|
|
|
|
|
#[wasm_bindgen(js_namespace = console)]
|
|
|
|
fn log(s: &str);
|
|
|
|
|
|
|
|
#[wasm_bindgen(js_namespace = console)]
|
|
|
|
fn error(s: &str);
|
2024-08-16 13:05:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[wasm_bindgen]
|
2024-08-19 18:11:59 +02:00
|
|
|
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<dyn FnMut(JsValue)> = Closure::new(move |stream: JsValue| {
|
|
|
|
video.set_src_object(Some(&MediaStream::unchecked_from_js(stream)));
|
|
|
|
let _ = video.play();
|
|
|
|
});
|
|
|
|
let fail: Closure<dyn FnMut(JsValue)> = 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<dyn FnMut()> = 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<dyn FnMut()> = 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<u8> {
|
|
|
|
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<u8>, width: u32, height: u32) -> Result<String, rxing::Exceptions> {
|
|
|
|
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(())
|
2024-08-16 13:05:57 +02:00
|
|
|
}
|