calories/browser/src/lib.rs

163 lines
5.1 KiB
Rust
Raw Normal View History

mod utils;
2024-08-19 18:11:59 +02:00
use image::{DynamicImage, RgbaImage};
use std::collections::HashMap;
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";
#[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);
}
#[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(())
}