symcode_webapp/
app.rs

1use bit_vec::BitVec;
2use rand::{RngCore, SeedableRng, rngs::StdRng};
3use visioncortex::{BinaryImage, ColorImage, PointI32};
4use wasm_bindgen::prelude::*;
5
6use symcode::acute32::{Acute32, Acute32SymcodeConfig, AlphabetReader, AlphabetReaderParams, GlyphLabel};
7use symcode::interfaces::{Decoder, Finder, FinderElement, Fitter, Reader, Encoder, SymcodeScanner, SymcodeGenerator};
8use symcode::math::{into_bitvec, num_bits_to_store};
9use crate::{canvas::Canvas, util::console_log_util};
10use crate::debugger::{Debugger, render_binary_image_to_canvas};
11use super::helper::is_black_hsv;
12
13#[wasm_bindgen]
14pub struct Acute32SymcodeMain {
15    config: Acute32SymcodeConfig,
16    rng: StdRng,
17}
18
19impl Default for Acute32SymcodeMain {
20    fn default() -> Self {
21        Self::new()
22    }
23}
24
25impl Acute32SymcodeMain {
26    const PAYLOAD_BITS: usize = 20;
27
28    pub fn from_config(config: Acute32SymcodeConfig, seed: u64) -> Self {
29        Self {
30            config,
31            rng: StdRng::seed_from_u64(seed),
32        }
33    }
34}
35
36#[wasm_bindgen]
37impl Acute32SymcodeMain {
38
39    pub fn new() -> Self {
40        let mut config = Acute32SymcodeConfig::default();
41        if let Some(debug_canvas) = Canvas::new_from_id("debug") {
42            config.debugger = Box::new(Debugger{ debug_canvas });
43        }
44        Self::from_config(config, 125)
45    }
46
47    pub fn seed_rng(&mut self, seed: u64) {
48        self.rng = StdRng::seed_from_u64(seed);
49    }
50
51    /// Takes the id of the canvas element storing the alphabet.
52    pub fn load_alphabet_from_canvas_id(&mut self, canvas_id: &str) {
53        let params = AlphabetReaderParams::default();
54        let canvas = &match Canvas::new_from_id(canvas_id) {
55            Some(c) => c,
56            None => panic!("Canvas with id ".to_owned() + canvas_id + " is not found!"),
57        };
58        let image = canvas
59            .get_image_data_as_color_image(0, 0, canvas.width() as u32, canvas.height() as u32)
60            .to_binary_image(|c| is_black_hsv(&c.to_hsv()));
61        match AlphabetReader::read_alphabet_to_library(image, params, &self.config) {
62            Ok(library) => self.config.symbol_library = Box::new(library),
63            Err(e) => console_log_util(e),
64        }
65    }
66
67    pub fn scan_from_canvas_id(&self, canvas_id: &str) -> Result<String, JsValue> {
68        if self.config.symbol_library.is_empty() {
69            return Err("No templates loaded into the SymcodeScanner instance yet!".into());
70        }
71
72        // Stage 0: Prepare the raw input
73        let raw_frame = if let Some(canvas) = &Canvas::new_from_id(canvas_id) {
74            canvas.get_image_data_as_color_image(0, 0, canvas.width() as u32, canvas.height() as u32)
75        } else {
76            return Err("Cannot read input image from canvas.".into());
77        };
78
79        let symcode = self.scan(raw_frame)?;
80
81        if false {
82            let _debug_code_string = format!("{:?}", symcode);
83        }
84
85        let decoded_bit_string = self.decode(symcode)?;
86        Ok(format!("{:?}", decoded_bit_string))
87    }
88
89    pub fn generate_symcode_to_canvas(&self, canvas_id: &str, payload: &str) -> Result<String, JsValue> {
90        if payload.len() > Self::PAYLOAD_BITS {
91            return Err("Payload has too many bits!".into());
92        }
93
94        let result = usize::from_str_radix(payload, 2);
95        if result.is_err() {
96            return Err("Failed to parse payload as binary number".into());
97        }
98        let payload = result.unwrap();
99
100        let canvas = if let Some(canvas) = Canvas::new_from_id(canvas_id) {
101            canvas
102        } else {
103            return Err("Code generation: Canvas does not exist.".into());
104        };
105
106        let (symcode, ground_truth_code) = self.generate_symcode_with_payload(payload)?;
107
108        if render_binary_image_to_canvas(&canvas, &symcode).is_err() {
109            return Err("Cannot render generated symcode to canvas.".into());
110        }
111
112        Ok(ground_truth_code)
113    }
114
115    pub fn generate_random_symcode_to_canvas(&mut self, canvas_id: &str) -> Result<String, JsValue> {
116        let canvas = if let Some(canvas) = Canvas::new_from_id(canvas_id) {
117            canvas
118        } else {
119            return Err("Code generation: Canvas does not exist.".into());
120        };
121        let (symcode, ground_truth_code) = self.generate_symcode_random()?;
122
123        if render_binary_image_to_canvas(&canvas, &symcode).is_err() {
124            return Err("Cannot render generated symcode to canvas.".into());
125        }
126
127        Ok(ground_truth_code)
128    }
129
130    fn generate_symcode_with_payload(&self, payload: usize) -> Result<(BinaryImage, String), &str> {
131        let payload = into_bitvec(payload, Self::PAYLOAD_BITS);
132        let payload_bit_string = format!("{:?}", payload);
133
134        let num_symbols = self.config.num_glyphs_in_code();
135
136        let acute32 = Acute32::new(&self.config);
137        let symcode_representation = acute32.get_encoder().encode(payload, num_symbols)?;
138        let symcode_string = format!("{:?}", symcode_representation);
139
140        let code_image = self.generate(symcode_representation);
141
142        let msg = format!("{}\n{}", symcode_string, payload_bit_string);
143
144        //console_log_util(&msg);
145
146        Ok((code_image, msg)) 
147    }
148
149    /// Randomly generate a 20-bit bit string, calculate CRC5 checksum (which is 5 bits)
150    /// Then encode the 25-bit bit string into a symcode and generate the code image
151    fn generate_symcode_random(&mut self) -> Result<(BinaryImage, String), &str> {
152        let symbol_num_bits = num_bits_to_store(GlyphLabel::num_variants());
153        let num_symbols = self.config.num_glyphs_in_code();
154
155        // Dummy data
156        let payload = BitVec::from_fn(
157            symbol_num_bits*num_symbols - 5, // Reserve 5 bits for CRC5 checksum
158            |_| { self.rng.next_u32() < (std::u32::MAX >> 1) }
159        );
160        let payload_bit_string = format!("{:?}", payload);
161
162        let acute32 = Acute32::new(&self.config);
163        let symcode_representation = acute32.get_encoder().encode(payload, num_symbols)?;
164        let symcode_string = format!("{:?}", symcode_representation);
165
166        let code_image = self.generate(symcode_representation);
167
168        let msg = format!("{}\n{}", symcode_string, payload_bit_string);
169
170        //console_log_util(&msg);
171
172        Ok((code_image, msg))
173    }
174}
175
176impl SymcodeScanner for Acute32SymcodeMain {
177    type SymcodeRepresentation = Vec<GlyphLabel>;
178
179    type Err = JsValue;
180
181    fn scan(&self, image: ColorImage) -> Result<Self::SymcodeRepresentation, Self::Err> {
182        let acute32 = Acute32::new(&self.config);
183
184        // Stage 1: Locate finder candidates
185        let finder_positions = match acute32.get_finder().find(
186            &image
187        ) {
188            Ok(finder_positions) => finder_positions,
189            Err(e) => {
190                return Err(("Failed at Stage 1: ".to_owned() + e).into());
191            }
192        };
193
194        // Stage 2: Fit a perspective transform from the image space to the object space
195        let image_to_object = match acute32.get_fitter().fit(
196            finder_positions,
197            image.width,
198            image.height
199        ) {
200            Ok(image_to_object) => image_to_object,
201            Err(e) => {
202                return Err(("Failed at Stage 2: ".to_owned() + e).into());
203            }
204        };
205
206        // Stage 3: Recognize the glyphs
207        let symcode_instance = match acute32.get_reader().read(
208            image,
209            image_to_object
210        ) {
211            Ok(symcode_instance) => symcode_instance,
212            Err(e) => {
213                return Err(("Failed at Stage 3: ".to_owned() + e).into());
214            }
215        };
216
217        Ok(symcode_instance)
218    }
219
220    fn decode(&self, symcode: Self::SymcodeRepresentation) -> Result<bit_vec::BitVec, Self::Err> {
221        let acute32 = Acute32::new(&self.config);
222
223        // Stage 4: Decode the Symcode
224        match acute32.get_decoder().decode(
225            symcode
226        ) {
227            Ok(decoded_symcode) => {
228                Ok(decoded_symcode)
229            },
230            Err(e) => {
231                Err(("Failed at Stage 4: ".to_owned() + e).into())
232            }
233        }
234    }
235}
236
237impl SymcodeGenerator for Acute32SymcodeMain {
238    type SymcodeRepresentation = Vec<GlyphLabel>;
239
240    fn generate(&self, symcode: Self::SymcodeRepresentation) -> BinaryImage {
241        let mut symcode_image = BinaryImage::new_w_h(self.config.code_width, self.config.code_height);
242
243        // Put in the finders
244        let finder_image = self.config.finder.to_image(self.config.symbol_width, self.config.symbol_height);
245        self.config.finder_positions.iter().for_each(|finder_center| {
246            let top_left = finder_center.to_point_i32() - PointI32::new((self.config.symbol_width >> 1) as i32, (self.config.symbol_height >> 1) as i32);
247            symcode_image.paste_from(&finder_image, top_left);
248        });
249
250        // Put in the glyphs
251        symcode.iter().enumerate().for_each(|(i, &glyph_label)| {
252            if glyph_label != GlyphLabel::Invalid {
253                let glyph_top_left = self.config.glyph_anchors[i];
254                if let Some(glyph) = self.config.symbol_library.get_glyph_with_label(glyph_label) {
255                    symcode_image.paste_from(&glyph.image, glyph_top_left.to_point_i32());
256                }
257            }
258        });
259
260        symcode_image
261    }
262}