From 9cb3463826a30a28d6c9038a00fd5eb0bce99227 Mon Sep 17 00:00:00 2001 From: Knyffen Date: Mon, 19 Aug 2024 18:11:59 +0200 Subject: [PATCH] Implement barcode scanning --- Cargo.lock | 454 ++++++++++++++++++++++++++++++- browser/Cargo.toml | 6 + browser/config.toml | 2 + browser/src/lib.rs | 156 ++++++++++- browser/src/utils.rs | 1 + javascript/camera.js | 82 ++++++ server/config.toml | 2 + server/src/main.rs | 78 +++++- server/src/my_structs/myerror.rs | 8 + templates/camera_test.html | 8 + templates/view_calories.html | 10 + 11 files changed, 792 insertions(+), 15 deletions(-) create mode 100644 browser/config.toml create mode 100644 javascript/camera.js create mode 100644 server/config.toml create mode 100644 templates/camera_test.html create mode 100644 templates/view_calories.html diff --git a/Cargo.lock b/Cargo.lock index 3c85d40..19ea2c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,22 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ab_glyph" +version = "0.2.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79faae4620f45232f599d9bc7b290f88247a0834162c4495ab2f02d60004adfb" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + [[package]] name = "addr2line" version = "0.22.0" @@ -82,6 +98,15 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arbitrary" version = "1.3.2" @@ -260,12 +285,33 @@ dependencies = [ "syn 2.0.74", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bit_field" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" +[[package]] +name = "bit_reverse" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99528ca30abb9495c7e106bf7c3177b257c62040fc0f2909fe470b0f43097296" + [[package]] name = "bitflags" version = "1.3.2" @@ -407,7 +453,12 @@ name = "calories_browser" version = "0.1.0" dependencies = [ "console_error_panic_hook", + "image 0.25.2", + "js-sys", + "rxing", "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] @@ -483,6 +534,28 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "chrono-tz" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -503,6 +576,15 @@ dependencies = [ "cc", ] +[[package]] +name = "codepage-437" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40c1169585d8d08e5675a39f2fc056cd19a258fc4cba5e3bbf4a9c1026de535" +dependencies = [ + "csv", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -671,6 +753,27 @@ dependencies = [ "quote", ] +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "darling" version = "0.20.10" @@ -784,6 +887,70 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encoding" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" +dependencies = [ + "encoding-index-japanese", + "encoding-index-korean", + "encoding-index-simpchinese", + "encoding-index-singlebyte", + "encoding-index-tradchinese", +] + +[[package]] +name = "encoding-index-japanese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-korean" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-simpchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-singlebyte" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-tradchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding_index_tests" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" + [[package]] name = "equivalent" version = "1.0.1" @@ -816,6 +983,17 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "fancy-regex" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + [[package]] name = "fastrand" version = "2.1.0" @@ -1064,8 +1242,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1319,6 +1499,24 @@ dependencies = [ "quick-error", ] +[[package]] +name = "imageproc" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2393fb7808960751a52e8a154f67e7dd3f8a2ef9bd80d1553078a7b4e8ed3f0d" +dependencies = [ + "ab_glyph", + "approx", + "getrandom", + "image 0.25.2", + "itertools", + "nalgebra", + "num", + "rand", + "rand_distr", + "rayon", +] + [[package]] name = "imgref" version = "1.10.1" @@ -1512,6 +1710,16 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "maybe-rayon" version = "0.1.1" @@ -1571,6 +1779,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +dependencies = [ + "serde", +] + [[package]] name = "mysql" version = "25.0.1" @@ -1653,6 +1870,21 @@ dependencies = [ "zstd", ] +[[package]] +name = "nalgebra" +version = "0.32.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" +dependencies = [ + "approx", + "matrixmultiply", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + [[package]] name = "named_pipe" version = "0.4.1" @@ -1701,6 +1933,20 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -1711,6 +1957,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1737,6 +1992,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.2" @@ -1755,6 +2021,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1816,6 +2083,24 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "owned_ttf_parser" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490d3a563d3122bf7c911a59b0add9389e5ec0f5f0c3ac6b91ff235a0e6a7f90" +dependencies = [ + "ttf-parser 0.24.1", +] + +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + [[package]] name = "paste" version = "1.0.15" @@ -1857,6 +2142,44 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.5" @@ -1910,7 +2233,7 @@ dependencies = [ "plotters-backend", "plotters-bitmap", "plotters-svg", - "ttf-parser", + "ttf-parser 0.20.0", "wasm-bindgen", "web-sys", ] @@ -2122,6 +2445,16 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + [[package]] name = "rav1e" version = "0.7.1" @@ -2171,6 +2504,12 @@ dependencies = [ "rgb", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.10.0" @@ -2334,12 +2673,56 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +[[package]] +name = "rxing" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786ea2f74b327842bab8c9e611fa3e40f1549d7931fb6e63d1fc2f84e06c2323" +dependencies = [ + "bit_reverse", + "chrono", + "chrono-tz", + "codepage-437", + "encoding", + "fancy-regex", + "image 0.25.2", + "imageproc", + "multimap", + "num", + "once_cell", + "regex", + "rxing-one-d-proc-derive", + "thiserror", + "unicode-segmentation", + "uriparse", + "urlencoding", +] + +[[package]] +name = "rxing-one-d-proc-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59c92a4da773eed06d54210368684b4325bc70a0f8b0cc4d50a106bf71b954f2" +dependencies = [ + "quote", + "syn 2.0.74", +] + [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "safe_arch" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -2496,6 +2879,19 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simba" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -2517,6 +2913,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "smallvec" version = "1.13.2" @@ -2917,6 +3319,12 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" +[[package]] +name = "ttf-parser" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a" + [[package]] name = "twox-hash" version = "1.6.3" @@ -2964,6 +3372,22 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "uriparse" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff" +dependencies = [ + "fnv", + "lazy_static", +] + [[package]] name = "url" version = "2.5.2" @@ -2975,6 +3399,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "uuid" version = "1.10.0" @@ -3052,6 +3482,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.93" @@ -3097,6 +3539,16 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +[[package]] +name = "wide" +version = "0.7.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b828f995bf1e9622031f8009f8481a85406ce1f4d4588ff746d872043e855690" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/browser/Cargo.toml b/browser/Cargo.toml index da1f54f..ca47e51 100644 --- a/browser/Cargo.toml +++ b/browser/Cargo.toml @@ -18,6 +18,12 @@ wasm-bindgen = "0.2.84" # code size when deploying. console_error_panic_hook = { version = "0.1.7", optional = true } +rxing = "0.6.1" +image = "0.25.2" +web-sys = { version = "0.3.70", features = ["Navigator", "MediaDevices", "Document", "HtmlCanvasElement", "CanvasRenderingContext2d", "VideoFrame", "HtmlVideoElement", "ImageData", "Window", "HtmlInputElement", "HtmlButtonElement", "MediaStreamConstraints", "MediaStream", "HtmlMediaElement", "console", "EventTarget"] } +wasm-bindgen-futures = "0.4.43" +js-sys = "0.3.70" + [profile.release] # Tell `rustc` to optimize for small code size. opt-level = "s" diff --git a/browser/config.toml b/browser/config.toml new file mode 100644 index 0000000..8467175 --- /dev/null +++ b/browser/config.toml @@ -0,0 +1,2 @@ +[build] +rustflags = ["--cfg=web_sys_unstable_apis"] diff --git a/browser/src/lib.rs b/browser/src/lib.rs index 2441e8c..a7c87ef 100644 --- a/browser/src/lib.rs +++ b/browser/src/lib.rs @@ -1,16 +1,162 @@ 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"; -// Import the `window.alert` function from the Web. #[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); } -// Export a `greet` function from Rust to JavaScript, that alerts a -// hello message. #[wasm_bindgen] -pub fn greet(name: &str) { - alert(&format!("Hello, {}!", name)); +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(()) } diff --git a/browser/src/utils.rs b/browser/src/utils.rs index b1d7929..c4be847 100644 --- a/browser/src/utils.rs +++ b/browser/src/utils.rs @@ -1,3 +1,4 @@ +#[allow(dead_code)] pub fn set_panic_hook() { // When the `console_error_panic_hook` feature is enabled, we can call the // `set_panic_hook` function at least once during initialization, and then diff --git a/javascript/camera.js b/javascript/camera.js new file mode 100644 index 0000000..90a0935 --- /dev/null +++ b/javascript/camera.js @@ -0,0 +1,82 @@ +import init, { init_camera_feed } from '/pkg/calories_browser.js'; +await init(); +init_camera_feed(); + +// (() => { + +// // The width and height of the captured photo. We will set the +// // width to the value defined here, but the height will be +// // calculated based on the aspect ratio of the input stream. + +// const width = 1080; // We will scale the photo width to this +// let height = 0; // This will be computed based on the input stream + +// // |streaming| indicates whether or not we're currently streaming +// // video from the camera. Obviously, we start at false. + +// let streaming = false; + +// // The various HTML elements we need to configure or control. These +// // will be set by the startup() function. + +// let video = null; +// let canvas = null; +// let startbutton = null; + +// async function startup() { +// await init(); + +// video = document.getElementById("video"); +// canvas = document.getElementById("canvas"); +// startbutton = document.getElementById("startbutton"); + +// navigator.mediaDevices +// .getUserMedia({ video: true, audio: false }) +// .then((stream) => { +// video.srcObject = stream; +// video.play(); +// }) +// .catch((err) => { +// console.error(`An error occurred: ${err}`); +// }); + +// video.addEventListener( +// "canplay", +// (ev) => { +// if (!streaming) { +// height = video.videoHeight / (video.videoWidth / width); + +// // Firefox currently has a bug where the height can't be read from +// // the video, so we will make assumptions if this happens. +// if (isNaN(height)) { +// height = width / (4 / 3); +// } + +// video.setAttribute("width", width); +// video.setAttribute("height", height); +// canvas.setAttribute("width", width); +// canvas.setAttribute("height", height); +// streaming = true; +// } +// }, +// false, +// ); + +// startbutton.addEventListener( +// "click", +// (ev) => { +// takepicture(); +// ev.preventDefault(); +// }, +// false, +// ); +// } + +// function takepicture() { +// fill_barcode(); +// } + +// // Set up our event listener to run the startup process +// // once loading is complete. +// // window.addEventListener("load", startup, false); +// })(); diff --git a/server/config.toml b/server/config.toml new file mode 100644 index 0000000..8467175 --- /dev/null +++ b/server/config.toml @@ -0,0 +1,2 @@ +[build] +rustflags = ["--cfg=web_sys_unstable_apis"] diff --git a/server/src/main.rs b/server/src/main.rs index 7a09b80..c6d45bb 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -58,6 +58,7 @@ async fn main() { .route("/calorie_intake.svg", get(svg_calorie_intake)) .route("/calorie_intake", get(calorie_intake)) .route("/wasm_test", get(wasm_test)) + .route("/camera_test", get(camera_test)) .nest_service("/pkg", get_service(ServeDir::new("../browser/pkg"))) .with_state(shared_state) .fallback(not_found); @@ -130,15 +131,74 @@ async fn calorie_intake(State(_state): State>) -> Html { Html("".to_string()) } -async fn wasm_test(State(_state): State>) -> Html { +fn construct_js(path: &str) -> Result { + let javascript = std::fs::read_to_string(format!("../javascript/{}", path))?; + let module = format!( + " + +", + javascript + ); + Ok(module) +} + +fn construct_tmpl(path: &str) -> Result { + Ok(std::fs::read_to_string(format!("../templates/{}", path))?) +} + +fn construct_html( + js_paths: Vec<&str>, + tmpl_paths: Vec<&str>, +) -> Result { + let js_modules: Vec = js_paths + .into_iter() + .map(construct_js) + .collect::, my_structs::MyError>>()?; + + let tmpl_snippets: Vec = tmpl_paths + .into_iter() + .map(construct_tmpl) + .collect::, my_structs::MyError>>()?; + + let html = format!( + " + + + + {} + + + {} + + ", + js_modules.join(""), + tmpl_snippets.join("") + ); + + Ok(html) +} +fn get_template(path: &str) -> Result { + Ok(std::fs::read_to_string(format!("../templates/{}", path))?) +} + +async fn wasm_test( + State(_state): State>, +) -> Result, my_structs::MyError> { println!("Serving: wasm_test"); - Html( - "wasm_test" - .to_string(), - ) + let content = get_template("view_calories.html")?; + + Ok(Html(content)) +} + +async fn camera_test( + State(_state): State>, +) -> Result, my_structs::MyError> { + println!("Serving: camera_test"); + + let html = construct_html(vec!["camera.js"], vec!["camera_test.html"])?; + + Ok(Html(html)) } diff --git a/server/src/my_structs/myerror.rs b/server/src/my_structs/myerror.rs index c1da163..4b3f280 100644 --- a/server/src/my_structs/myerror.rs +++ b/server/src/my_structs/myerror.rs @@ -5,6 +5,7 @@ use axum::{ pub enum MyError { MySQLError(mysql::Error), + IOError(std::io::Error), } impl IntoResponse for MyError { @@ -12,6 +13,7 @@ impl IntoResponse for MyError { let (status, message) = match self { // # TODO: Don't expose error messages once this goes into production MyError::MySQLError(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + MyError::IOError(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), }; (status, message).into_response() @@ -23,3 +25,9 @@ impl From for MyError { Self::MySQLError(error) } } + +impl From for MyError { + fn from(error: std::io::Error) -> Self { + Self::IOError(error) + } +} diff --git a/templates/camera_test.html b/templates/camera_test.html new file mode 100644 index 0000000..e8365e1 --- /dev/null +++ b/templates/camera_test.html @@ -0,0 +1,8 @@ +
+ + +
+
+ + +
diff --git a/templates/view_calories.html b/templates/view_calories.html new file mode 100644 index 0000000..e2dbbc5 --- /dev/null +++ b/templates/view_calories.html @@ -0,0 +1,10 @@ + + + + + wasm_test +