From fc3ff3b27a358764bd4d94e299ea8300f8f29036 Mon Sep 17 00:00:00 2001 From: 101arrowz Date: Fri, 31 Dec 2021 12:26:34 -0800 Subject: [PATCH] Preliminary Rust version --- .cargo/config.toml | 3 + .gitignore | 3 +- Cargo.lock | 126 +++++++++++++++++++++++++++++++++++ Cargo.toml | 11 +++ src-rs/.vscode/settings.json | 3 + src-rs/image/downscale.rs | 107 +++++++++++++++++++++++++++++ src-rs/image/grayscale.rs | 15 +++++ src-rs/image/mod.rs | 27 ++++++++ src-rs/lib.rs | 35 ++++++++++ src/index.tsx | 35 +++++++--- 10 files changed, 356 insertions(+), 9 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src-rs/.vscode/settings.json create mode 100644 src-rs/image/downscale.rs create mode 100644 src-rs/image/grayscale.rs create mode 100644 src-rs/image/mod.rs create mode 100644 src-rs/lib.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..35c5c21 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +[build] +target = "wasm32-unknown-unknown" +# rustflags = "-C target-feature=+simd128" diff --git a/.gitignore b/.gitignore index 9287d0d..636fc85 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ dist/ -.parcel-cache/ \ No newline at end of file +.parcel-cache/ +target/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..72880f6 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,126 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bumpalo" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "scanner" +version = "0.1.0" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "syn" +version = "1.0.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a9bfae4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "scanner" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src-rs/lib.rs" +crate-type = ["cdylib"] + +[dependencies] +wasm-bindgen = "0.2.78" \ No newline at end of file diff --git a/src-rs/.vscode/settings.json b/src-rs/.vscode/settings.json new file mode 100644 index 0000000..f49d8f9 --- /dev/null +++ b/src-rs/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.cargo.target": "wasm32-unknown-unknown", +} \ No newline at end of file diff --git a/src-rs/image/downscale.rs b/src-rs/image/downscale.rs new file mode 100644 index 0000000..0b60452 --- /dev/null +++ b/src-rs/image/downscale.rs @@ -0,0 +1,107 @@ +use super::Image; + +// TODO: SIMD +pub fn downscale(source: &Image, by: f32) -> Image { + assert!(by > 1.0); + let &Image { data: ref source, width, height } = source; + let over_by = 1.0 / by; + let dw = (width as f32 * over_by) as usize; + let dh = (height as f32 * over_by) as usize; + let mut data = vec![0.0; dh * dw]; + let over_by2 = over_by * over_by; + let mi = dh - 1; + let mj = dw - 1; + for i in 1..mi { + let si = i as f32 * by; + let sie = si + by; + let sif = si as usize; + let sic = sif + 1; + let sief = sie as usize; + let sir = (sic as f32) - si; + let sire = sie - (sief as f32); + let ib = i * dw; + for j in 1..mj { + let sj = j as f32 * by; + let sje = sj + by; + let sjf = sj as usize; + let sjc = sjf + 1; + let sjef = sje as usize; + let sjr = (sjc as f32) - sj; + let sjre = sje - (sjef as f32); + let mut sum = 0.0; + for rsi in sic..sief { + for rsj in sjc..sjef { + sum += source[rsi * width + rsj]; + } + } + for rsj in sjc..sjef { + sum += source[sif * width + rsj] * sir + source[sief * width + rsj] * sire; + } + for rsi in sic..sief { + sum += source[rsi * width + sjf] * sjr + source[rsi * width + sjef] * sjre; + } + sum += source[sif * width + sjf] * sir * sjr; + sum += source[sif * width + sjef] * sir * sjre; + sum += source[sief * width + sjf] * sire * sjr; + sum += source[sief * width + sjef] * sire * sjre; + data[ib + j] = sum * over_by2; + } + } + for i in 1..mi { + let ib = i * dw; + let ibe = ib + mj; + data[ib] = data[ib + 1]; + data[ibe] = data[ibe - 1]; + } + let mibe = mi * dw; + let mib = mibe - dw; + for j in 0..dw { + data[j] = data[j + dw]; + data[mibe + j] = data[mib + j]; + } + Image { data, width: dw, height: dh } +} + +// let over_by = 1.0 / by; +// let width = (source.width as f32 * over_by) as usize; +// let height = (source.height as f32 * over_by) as usize; +// let over_by2 = over_by * over_by; +// let mut data = vec![0.0; width * height]; +// let right = 1; +// let below = width; +// let diag = right + below; +// let sub = (by * 2.0) as usize; +// let mw = source.width - sub; +// let mh = source.height - sub; +// for i in 0..=mh { +// for j in 0..=mw { +// let val = unsafe { *source.data.get_unchecked(i * source.width + j) * over_by2 }; +// let si = i as f32 * over_by; +// let sii = si as usize; +// let sirr = si.fract(); +// let sir = 1.0 - sirr; +// let sj = j as f32 * over_by; +// let sji = sj as usize; +// let sjrr = sj.fract(); +// let sjr = 1.0 - sjrr; +// let di = sii * width + sji; +// unsafe { +// *data.get_unchecked_mut(di) += val * sir * sjr; +// *data.get_unchecked_mut(di + right) += val * sirr * sjr; +// *data.get_unchecked_mut(di + below) += val * sir * sjrr; +// *data.get_unchecked_mut(di + diag) += val * sirr * sjrr; +// } +// } +// } +// for dj in 1..height { +// let ind = dj * width - 3; +// let val = data[ind]; +// data[ind + 1] = val; +// data[ind + 2] = val; +// } +// for di in 0..width { +// let ind = (height - 3) * width + di; +// let val = data[ind]; +// data[ind + width] = val; +// data[ind + width * 2] = val; +// } \ No newline at end of file diff --git a/src-rs/image/grayscale.rs b/src-rs/image/grayscale.rs new file mode 100644 index 0000000..fc56de3 --- /dev/null +++ b/src-rs/image/grayscale.rs @@ -0,0 +1,15 @@ +use super::{RGBAImage, Image}; + +// TODO: SIMD +pub fn grayscale(source: &RGBAImage) -> Image { + let &RGBAImage { data: ref source, width, height } = source; + Image { + data: source.chunks_exact(4).map(|rgba| unsafe { + (*rgba.get_unchecked(0) as f32) * 0.00116796875 + + (*rgba.get_unchecked(1) as f32) * 0.00229296875 + + (*rgba.get_unchecked(2) as f32) * 0.0004453125 + }).collect(), + width, + height + } +} \ No newline at end of file diff --git a/src-rs/image/mod.rs b/src-rs/image/mod.rs new file mode 100644 index 0000000..7269511 --- /dev/null +++ b/src-rs/image/mod.rs @@ -0,0 +1,27 @@ +use alloc::vec::Vec; +mod grayscale; +mod downscale; + +pub struct Image { + pub data: Vec, + pub width: usize, + pub height: usize +} + +impl Image { + pub fn downscale(&self, by: f32) -> Image { + downscale::downscale(self, by) + } +} + +pub struct RGBAImage { + pub data: Vec, + pub width: usize, + pub height: usize +} + +impl RGBAImage { + pub fn to_grayscale(&self) -> Image { + grayscale::grayscale(self) + } +} \ No newline at end of file diff --git a/src-rs/lib.rs b/src-rs/lib.rs new file mode 100644 index 0000000..9d7b488 --- /dev/null +++ b/src-rs/lib.rs @@ -0,0 +1,35 @@ +#![no_std] +#[macro_use] +extern crate alloc; + +use wasm_bindgen::prelude::*; +use alloc::vec::Vec; + +#[cfg(not(target_arch = "wasm32"))] +compile_error!("Only compilable to WASM"); + +mod image; +use image::{Image, RGBAImage}; + +struct Point { + x: f32, + y: f32 +} + +struct Rect { + a: Point, + b: Point, + c: Point, + d: Point +} + +impl Into> for Rect { + fn into(self) -> Vec { + vec![self.a.x, self.a.y, self.b.x, self.b.y, self.c.x, self.c.y, self.d.x, self.d.y] + } +} + +#[wasm_bindgen] +pub fn document(data: Vec, width: usize, height: usize, by: f32) -> Vec { + (RGBAImage { data, width, height }).to_grayscale().downscale(by).data +} \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index c3c7374..356e91a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -77,12 +77,13 @@ const grayscale = (src: Uint8ClampedArray, dst = new Float32Array(src.buffer, sr } const downscale = (src: Float32Array, width: number, height: number, by: number, dst?: Float32Array) => { - const dw = Math.floor(width / by); - const dh = Math.floor(height / by); + const overBy = 1 / by; + const dw = Math.floor(width * overBy); + const dh = Math.floor(height * overBy); if (!dst) { dst = new Float32Array(dh * dw); } - const by2 = by * by, mi = dh - 1, mj = dw - 1; + const overBy2 = overBy * overBy, mi = dh - 1, mj = dw - 1; for (let i = 1; i < mi; ++i) { const si = i * by, sie = si + by, sif = Math.floor(si), sic = sif + 1, sief = Math.floor(sie); const sir = sic - si, sire = sie - sief; @@ -107,7 +108,7 @@ const downscale = (src: Float32Array, width: number, height: number, by: number, sum += src[sif * width + sjef] * sir * sjre; sum += src[sief * width + sjf] * sire * sjr; sum += src[sief * width + sjef] * sire * sjre; - dst[i * dw + j] = sum / by2; + dst[i * dw + j] = sum * overBy2; } } for (let i = 1; i < mi; ++i) { @@ -833,12 +834,30 @@ const done = document.getElementById('done') as HTMLButtonElement; const pages: ImageData[] = []; +import init, { document as stuff } from '../pkg'; +let prom = init(); imgInput.addEventListener('change', async () => { const img = await getImage(imgInput.files![0]); - let rect = detectDocument(img); - if (rect) { - pages.push(perspective(img, rect)); - } + await prom; + const scaleFactor = 11.3; + console.time('init') + console.timeLog('init'); + console.timeEnd('init') + console.time('wasm'); + let wasmd = stuff(img.data as unknown as Uint8Array, img.width, img.height, scaleFactor); + console.timeLog('wasm'); + console.timeEnd('wasm') + console.time('js'); + const jsd = downscale(grayscale(img.data, new Float32Array(img.width * img.height)), img.width, img.height, scaleFactor); + console.timeLog('js'); + console.timeEnd('js') + plot(grayscaleToRGB(wasmd), Math.floor(img.width / scaleFactor), Math.floor(img.height / scaleFactor)); + plot(grayscaleToRGB(jsd), Math.floor(img.width / scaleFactor), Math.floor(img.height / scaleFactor)); + + // let rect = detectDocument(img); + // if (rect) { + // pages.push(perspective(img, rect)); + // } }); done.addEventListener('click', async () => {