├── .gitignore ├── wasm ├── malloc.wasm ├── mazegen.wasm ├── random.wasm ├── thinning.wasm ├── browniantree.wasm ├── containers.wasm ├── findcontours.wasm ├── disttransform.wasm └── traceskeleton.wasm ├── screenshots ├── malloc.png ├── random.png ├── mazegen.png ├── thinning.png ├── containers.png ├── browniantree.png ├── disttransform.png ├── findcontours.png └── traceskeleton.png ├── tests ├── assets │ ├── horse.png │ └── manuscript.png ├── random.html ├── thinning.html ├── bowniantree.html ├── disttransform.html ├── mazegen.html ├── malloc.html ├── findcontours.html ├── traceskeleton.html └── containers.html ├── compile.py ├── LICENSE ├── README.md └── wat ├── browniantree.wat ├── thinning.wat ├── disttransform.wat ├── mazegen.wat ├── malloc.wat ├── findcontours.wat ├── random.wat └── traceskeleton.wat /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | */.DS_Store 3 | */*/.DS_Store 4 | wat2wasm 5 | -------------------------------------------------------------------------------- /wasm/malloc.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/wasm/malloc.wasm -------------------------------------------------------------------------------- /wasm/mazegen.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/wasm/mazegen.wasm -------------------------------------------------------------------------------- /wasm/random.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/wasm/random.wasm -------------------------------------------------------------------------------- /wasm/thinning.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/wasm/thinning.wasm -------------------------------------------------------------------------------- /screenshots/malloc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/screenshots/malloc.png -------------------------------------------------------------------------------- /screenshots/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/screenshots/random.png -------------------------------------------------------------------------------- /tests/assets/horse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/tests/assets/horse.png -------------------------------------------------------------------------------- /wasm/browniantree.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/wasm/browniantree.wasm -------------------------------------------------------------------------------- /wasm/containers.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/wasm/containers.wasm -------------------------------------------------------------------------------- /wasm/findcontours.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/wasm/findcontours.wasm -------------------------------------------------------------------------------- /screenshots/mazegen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/screenshots/mazegen.png -------------------------------------------------------------------------------- /screenshots/thinning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/screenshots/thinning.png -------------------------------------------------------------------------------- /wasm/disttransform.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/wasm/disttransform.wasm -------------------------------------------------------------------------------- /wasm/traceskeleton.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/wasm/traceskeleton.wasm -------------------------------------------------------------------------------- /screenshots/containers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/screenshots/containers.png -------------------------------------------------------------------------------- /tests/assets/manuscript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/tests/assets/manuscript.png -------------------------------------------------------------------------------- /screenshots/browniantree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/screenshots/browniantree.png -------------------------------------------------------------------------------- /screenshots/disttransform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/screenshots/disttransform.png -------------------------------------------------------------------------------- /screenshots/findcontours.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/screenshots/findcontours.png -------------------------------------------------------------------------------- /screenshots/traceskeleton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingDong-/wasm-fun/HEAD/screenshots/traceskeleton.png -------------------------------------------------------------------------------- /compile.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | 4 | wats = glob("wat/*.wat") 5 | 6 | for w in wats: 7 | cmd = "./wat2wasm "+w+" -o wasm/"+w.split("/")[-1].replace(".wat",".wasm") 8 | print(cmd) 9 | os.system(cmd) 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /tests/random.html: -------------------------------------------------------------------------------- 1 | 2 | 41 | 42 | -------------------------------------------------------------------------------- /tests/thinning.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/bowniantree.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/disttransform.html: -------------------------------------------------------------------------------- 1 | 2 | 45 | 46 | -------------------------------------------------------------------------------- /tests/mazegen.html: -------------------------------------------------------------------------------- 1 | 2 | 45 | 46 | -------------------------------------------------------------------------------- /tests/malloc.html: -------------------------------------------------------------------------------- 1 | 2 | 75 | 76 | -------------------------------------------------------------------------------- /tests/findcontours.html: -------------------------------------------------------------------------------- 1 | 2 | 64 | -------------------------------------------------------------------------------- /tests/traceskeleton.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wasm-fun 2 | 3 | *Non-trivial programs in hand-written WebAssembly* 4 | 5 | This repo is a collection of useful algorithms written from scratch in WebAssembly text format (`.wat`) and assembled to `.wasm` via [wabt](https://github.com/WebAssembly/wabt). 6 | 7 | 8 | See [Overview](#Overview) section for a list of programs included. See [wat](wat/) folder for the fully-commented and well-explained source code. See [wasm](wasm/) for compiled binaries you can grab and use (with your JS projects). See [tests](tests/) for usage examples. 9 | 10 | Despite that the algorithms are non-trivial, the assembled binaries are extremely small (compared to say what you get from emscripten)! Average file size is ~900 bytes. 11 | 12 | ## Motivation 13 | 14 | I find WebAssembly a wonderful technology, and have been compiling C's and C++'s to wasm via emscripten for many projects. So I thought I should learn at least learn the "real" thing -- coding in WebAssembly directly. Fortunately, the lisp-like S-expressions makes the language quite pleasant to work with. 15 | 16 | I haven't found many handwritten examples beyond the "fizzbuzz" and the "99 beers". (This [Conway's Game of Life in WebAssembly](https://blog.scottlogic.com/2018/04/26/webassembly-by-hand.html) gave me a lot of inspiration though). So I started my own little collection of algorithms I enjoyed, in hand-written WebAssembly, with ample comments and explanations for the reader. 17 | 18 | I think WebAssembly is the sort of language where the beauty is derived from its simplicity. 19 | 20 | ## Overview 21 | 22 | | | | 23 | |---|---| 24 | | | [**thinning.wat**](wat/thinning.wat) | 25 | | ![](screenshots/thinning.png) | Skeletonization/thinning of binary images. Implements Zhang-Suen (1984). [Paper](http://agcggs680.pbworks.com/f/Zhan-Suen_algorithm.pdf) | 26 | | | [**disttransform.wat**](wat/disttransform.wat) | 27 | | ![](screenshots/disttransform.png) | Compute distance transform of binary images. Implements Meijster distance. [Paper](http://fab.cba.mit.edu/classes/S62.12/docs/Meijster_distance.pdf) | 28 | | | [**findcontours.wat**](wat/findcontours.wat) | 29 | | ![](screenshots/findcontours.png) | Trace contours from binary images (vector output). This is the same as OpenCV's `findContours`. Implements Suzuki-Abe (1983). [Paper](https://www.academia.edu/15495158/Topological_Structural_Analysis_of_Digitized_Binary_Images_by_Border_Following) | 30 | | | [**browniantree.wat**](wat/browniantree.wat) | 31 | | ![](screenshots/browniantree.png) | Generate brownian fractal trees (aka Diffusion-limited aggregation). | 32 | | | [**mazegen.wat**](wat/mazegen.wat) | 33 | | ![](screenshots/mazegen.png) | Generate mazes using Wilson's algorithm, which guarantees an unbiased sample from the uniform distribution over all mazes. | 34 | | | [**malloc.wat**](wat/malloc.wat) | 35 | | ![](screenshots/malloc.png) | Very baseline 32-bit implicit-free-list first-fit malloc. | 36 | | | [**traceskeleton.wat**](wat/traceskeleton.wat) | 37 | | ![](screenshots/traceskeleton.png) | Retrieve topological skeleton as a set of polylines from binary images. [Original algorithm](https://github.com/LingDong-/skeleton-tracing). Includes a malloc implementation and a linked list implementation. ~300% faster than the version complied with emscripten (and ~10x smaller), ~500% faster than vanilla Javascript. | 38 | | | [**containers.wat**](wat/containers.wat) | 39 | | ![](screenshots/containers.png) | Implements STL-like polymorphic container types `arr` (continous resizable array), `list` (doubly linked list) and `map` (hash table). | 40 | | | [**random.wat**](wat/random.wat) | 41 | | ![](screenshots/random.png) | Uniform, Perlin, Gaussian, and exponential randomness. | 42 | -------------------------------------------------------------------------------- /wat/browniantree.wat: -------------------------------------------------------------------------------- 1 | ;;========================================================;; 2 | ;; BROWNIAN TREE (DLA) WITH HANDWRITTEN WEBASSEMBLY ;; 3 | ;; `browniantree.wat` Lingdong Huang 2020 Public Domain ;; 4 | ;;========================================================;; 5 | ;;https://wikipedia.org/wiki/Diffusion-limited_aggregation;; 6 | ;;--------------------------------------------------------;; 7 | 8 | (module 9 | 10 | ;; 4 pages = 4 x 64kb = max dimension 512x512 (pixel=1 byte) 11 | (memory $mem 4) 12 | 13 | ;; shr3 random number generator seed 14 | (global $jsr (mut i32) (i32.const 0x5EED)) 15 | 16 | ;; shr3 random number generator 17 | (func $shr3 (result i32) 18 | (global.set $jsr (i32.xor (global.get $jsr) (i32.shl (global.get $jsr) (i32.const 17)))) 19 | (global.set $jsr (i32.xor (global.get $jsr) (i32.shr_u (global.get $jsr) (i32.const 13)))) 20 | (global.set $jsr (i32.xor (global.get $jsr) (i32.shl (global.get $jsr) (i32.const 5 )))) 21 | (global.get $jsr) 22 | ) 23 | (func $set_seed (param $seed i32) (global.set $jsr (local.get $seed))) 24 | 25 | ;; pixels are stored as 8-bit row-major array in memory 26 | ;; reading a pixel: mem[i=y*w+x] 27 | (func $get_pixel (param $w i32) (param $x i32) (param $y i32) (result i32) 28 | (i32.load8_u (i32.add 29 | (i32.mul (local.get $w) (local.get $y)) 30 | (local.get $x) 31 | )) 32 | ) 33 | 34 | ;; writing a pixel: mem[i=y*w+x]=v 35 | (func $set_pixel (param $w i32) (param $x i32) (param $y i32) (param $v i32) 36 | (i32.store8 (i32.add 37 | (i32.mul (local.get $w) (local.get $y)) 38 | (local.get $x) 39 | ) (local.get $v)) 40 | ) 41 | 42 | ;; one iteration of brownian tree simulation: 43 | ;; - a particle starts at random location 44 | ;; - drunken walk in 8 directions 45 | ;; - upon hitting a neighboring on-pixel, turn on the current pixel and return 46 | ;; - upon going out of bounds, return 47 | ;; w: width, h: height 48 | (func $bt_iteration (param $w i32) (param $h i32) 49 | ;; local variable declaration 50 | ;; particle coordinate 51 | (local $x i32) (local $y i32) 52 | ;; random number 53 | (local $r i32) 54 | ;; pixel Moore neighborhood 55 | (local $p2 i32) (local $p3 i32) (local $p4 i32) (local $p5 i32) 56 | (local $p6 i32) (local $p7 i32) (local $p8 i32) (local $p9 i32) 57 | 58 | ;; random start location (x,y)=(rand()%w,rand()%h) 59 | (local.set $x (i32.rem_u (call $shr3) (local.get $w))) 60 | (local.set $y (i32.rem_u (call $shr3) (local.get $h))) 61 | 62 | loop 63 | ;; boundry conditions: exit 64 | (if (i32.eqz (local.get $x)) (then br 2 )) 65 | (if (i32.eqz (local.get $y)) (then br 2 )) 66 | (if (i32.eq (local.get $x) (i32.sub (local.get $w) (i32.const 1))) (then br 2 )) 67 | (if (i32.eq (local.get $y) (i32.sub (local.get $h) (i32.const 1))) (then br 2 )) 68 | 69 | ;; pixel's Moore (8-connected) neighborhood: 70 | 71 | ;; p9 p2 p3 72 | ;; p8 p4 73 | ;; p7 p6 p5 74 | (local.set $p2 (call $get_pixel (local.get $w) 75 | (local.get $x) 76 | (i32.sub (local.get $y) (i32.const 1)) 77 | )) 78 | (local.set $p3 (call $get_pixel (local.get $w) 79 | (i32.add (local.get $x) (i32.const 1)) 80 | (i32.sub (local.get $y) (i32.const 1)) 81 | )) 82 | (local.set $p4 (call $get_pixel (local.get $w) 83 | (i32.add (local.get $x) (i32.const 1)) 84 | (local.get $y) 85 | )) 86 | (local.set $p5 (call $get_pixel (local.get $w) 87 | (i32.add (local.get $x) (i32.const 1)) 88 | (i32.add (local.get $y) (i32.const 1)) 89 | )) 90 | (local.set $p6 (call $get_pixel (local.get $w) 91 | (local.get $x) 92 | (i32.add (local.get $y) (i32.const 1)) 93 | )) 94 | (local.set $p7 (call $get_pixel (local.get $w) 95 | (i32.sub (local.get $x) (i32.const 1)) 96 | (i32.add (local.get $y) (i32.const 1)) 97 | )) 98 | (local.set $p8 (call $get_pixel (local.get $w) 99 | (i32.sub (local.get $x) (i32.const 1)) 100 | (local.get $y) 101 | )) 102 | (local.set $p9 (call $get_pixel (local.get $w) 103 | (i32.sub (local.get $x) (i32.const 1)) 104 | (i32.sub (local.get $y) (i32.const 1)) 105 | )) 106 | 107 | ;; found a neighboring on-pixel 108 | (if (i32.or (i32.or (i32.or 109 | (i32.or (local.get $p2) (local.get $p3)) 110 | (i32.or (local.get $p4) (local.get $p5))) 111 | (i32.or (local.get $p6) (local.get $p7))) 112 | (i32.or (local.get $p8) (local.get $p9))) 113 | (then 114 | (call $set_pixel (local.get $w) (local.get $x) (local.get $y) (i32.const 1)) 115 | br 2 116 | )) 117 | 118 | ;; generate random number in interval [0,8) 119 | ;; ==rand()%8 120 | (local.set $r (i32.rem_u (call $shr3) (i32.const 8))) 121 | 122 | ;; switch (r) case 0,1,...7 123 | ;; walk in one of the 8 directions 124 | (if (i32.eqz (local.get $r)) (then 125 | (local.set $x (i32.sub (local.get $x) (i32.const 1))) 126 | 127 | )(else(if (i32.eq (local.get $r) (i32.const 1)) (then 128 | (local.set $x (i32.add (local.get $x) (i32.const 1))) 129 | 130 | )(else(if (i32.eq (local.get $r) (i32.const 2)) (then 131 | (local.set $y (i32.sub (local.get $y) (i32.const 1))) 132 | 133 | )(else(if (i32.eq (local.get $r) (i32.const 3)) (then 134 | (local.set $y (i32.add (local.get $y) (i32.const 1))) 135 | 136 | )(else(if (i32.eq (local.get $r) (i32.const 4)) (then 137 | (local.set $x (i32.sub (local.get $x) (i32.const 1))) 138 | (local.set $y (i32.sub (local.get $y) (i32.const 1))) 139 | 140 | )(else(if (i32.eq (local.get $r) (i32.const 5)) (then 141 | (local.set $x (i32.sub (local.get $x) (i32.const 1))) 142 | (local.set $y (i32.add (local.get $y) (i32.const 1))) 143 | 144 | )(else(if (i32.eq (local.get $r) (i32.const 6)) (then 145 | (local.set $x (i32.add (local.get $x) (i32.const 1))) 146 | (local.set $y (i32.sub (local.get $y) (i32.const 1))) 147 | 148 | )(else 149 | (local.set $x (i32.add (local.get $x) (i32.const 1))) 150 | (local.set $y (i32.add (local.get $y) (i32.const 1))) 151 | )))))))))))))) 152 | (br 0) 153 | end 154 | 155 | ) 156 | 157 | ;; simulate a batch of particles building the brownian tree 158 | ;; (just bt_iteration() in a loop) 159 | ;; w: width, h: height, n: number of particles 160 | (func $bt_batch (param $w i32) (param $h i32) (param $n i32) 161 | (local $i i32) 162 | loop $l0 163 | (call $bt_iteration (local.get $w) (local.get $h)) 164 | (local.set $i (i32.add (local.get $i) (i32.const 1))) 165 | (br_if $l0 (i32.lt_u (local.get $i) (local.get $n))) 166 | end 167 | ) 168 | 169 | ;; exported API's 170 | (export "bt_iteration" (func $bt_iteration)) 171 | (export "bt_batch" (func $bt_batch)) 172 | (export "get_pixel" (func $get_pixel)) 173 | (export "set_pixel" (func $set_pixel)) 174 | (export "shr3" (func $shr3)) 175 | (export "set_seed" (func $set_seed)) 176 | (export "mem" (memory $mem)) 177 | ) -------------------------------------------------------------------------------- /wat/thinning.wat: -------------------------------------------------------------------------------- 1 | ;;========================================================;; 2 | ;; SKELETONIZATION WITH HANDWRITTEN WEBASSEMBLY ;; 3 | ;; `thinning.wat` Lingdong Huang 2020 Public Domain ;; 4 | ;;========================================================;; 5 | ;; Binary image thinning (skeletonization) in-place. ;; 6 | ;; Implements Zhang-Suen algorithm. ;; 7 | ;; http://agcggs680.pbworks.com/f/Zhan-Suen_algorithm.pdf ;; 8 | ;;--------------------------------------------------------;; 9 | 10 | (module 11 | ;; 16 pages = 16 x 64kb = max dimension 1024x1024 (pixel=1 byte) 12 | (memory $mem 16) 13 | 14 | ;; pixels are stored as 8-bit row-major array in memory 15 | ;; reading a pixel: mem[i=y*w+x] 16 | (func $get_pixel (param $w i32) (param $x i32) (param $y i32) (result i32) 17 | (i32.load8_u (i32.add 18 | (i32.mul (local.get $w) (local.get $y)) 19 | (local.get $x) 20 | )) 21 | ) 22 | 23 | ;; writing a pixel: mem[i=y*w+x]=v 24 | (func $set_pixel (param $w i32) (param $x i32) (param $y i32) (param $v i32) 25 | (i32.store8 (i32.add 26 | (i32.mul (local.get $w) (local.get $y)) 27 | (local.get $x) 28 | ) (local.get $v)) 29 | ) 30 | 31 | ;; one iteration of the thinning algorithm 32 | ;; w: width, h: height, iter: 0=even-subiteration, 1=odd-subiteration 33 | ;; returns 0 if no further thinning possible (finished), 1 otherwise 34 | (func $thinning_zs_iteration (param $w i32) (param $h i32) (param $iter i32) (result i32) 35 | ;; local variable declarations 36 | ;; iterators 37 | (local $i i32) (local $j i32) 38 | ;; pixel Moore neighborhood 39 | (local $p2 i32) (local $p3 i32) (local $p4 i32) (local $p5 i32) 40 | (local $p6 i32) (local $p7 i32) (local $p8 i32) (local $p9 i32) 41 | ;; temporary computation results 42 | (local $A i32) (local $B i32) 43 | (local $m1 i32) (local $m2 i32) 44 | ;; bools for updating image and determining stop condition 45 | (local $diff i32) 46 | (local $mark i32) 47 | (local $neu i32) 48 | (local $old i32) 49 | 50 | (local.set $diff (i32.const 0)) 51 | 52 | ;; raster scan the image (loop over every pixel) 53 | 54 | ;; for (i = 1; i < h-1; i++) 55 | (local.set $i (i32.const 1)) 56 | loop $loop_i 57 | 58 | ;; for (j = 1; j < w-1; j++) 59 | (local.set $j (i32.const 1)) 60 | loop $loop_j 61 | 62 | ;; pixel's Moore (8-connected) neighborhood: 63 | 64 | ;; p9 p2 p3 65 | ;; p8 p4 66 | ;; p7 p6 p5 67 | 68 | (local.set $p2 (i32.and (call $get_pixel (local.get $w) 69 | (local.get $j) 70 | (i32.sub (local.get $i) (i32.const 1)) 71 | ) (i32.const 1) )) 72 | 73 | (local.set $p3 (i32.and (call $get_pixel (local.get $w) 74 | (i32.add (local.get $j) (i32.const 1)) 75 | (i32.sub (local.get $i) (i32.const 1)) 76 | ) (i32.const 1) )) 77 | 78 | (local.set $p4 (i32.and (call $get_pixel (local.get $w) 79 | (i32.add (local.get $j) (i32.const 1)) 80 | (local.get $i) 81 | ) (i32.const 1) )) 82 | 83 | (local.set $p5 (i32.and (call $get_pixel (local.get $w) 84 | (i32.add (local.get $j) (i32.const 1)) 85 | (i32.add (local.get $i) (i32.const 1)) 86 | ) (i32.const 1) )) 87 | 88 | (local.set $p6 (i32.and (call $get_pixel (local.get $w) 89 | (local.get $j) 90 | (i32.add (local.get $i) (i32.const 1)) 91 | ) (i32.const 1) )) 92 | 93 | (local.set $p7 (i32.and (call $get_pixel (local.get $w) 94 | (i32.sub (local.get $j) (i32.const 1)) 95 | (i32.add (local.get $i) (i32.const 1)) 96 | ) (i32.const 1) )) 97 | 98 | (local.set $p8 (i32.and (call $get_pixel (local.get $w) 99 | (i32.sub (local.get $j) (i32.const 1)) 100 | (local.get $i) 101 | ) (i32.const 1) )) 102 | 103 | (local.set $p9 (i32.and (call $get_pixel (local.get $w) 104 | (i32.sub (local.get $j) (i32.const 1)) 105 | (i32.sub (local.get $i) (i32.const 1)) 106 | ) (i32.const 1) )) 107 | 108 | ;; A is the number of 01 patterns in the ordered set p2,p3,p4,...p8,p9 109 | (local.set $A (i32.add (i32.add( i32.add (i32.add( i32.add( i32.add( i32.add 110 | (i32.and (i32.eqz (local.get $p2)) (i32.eq (local.get $p3) (i32.const 1))) 111 | (i32.and (i32.eqz (local.get $p3)) (i32.eq (local.get $p4) (i32.const 1)))) 112 | (i32.and (i32.eqz (local.get $p4)) (i32.eq (local.get $p5) (i32.const 1)))) 113 | (i32.and (i32.eqz (local.get $p5)) (i32.eq (local.get $p6) (i32.const 1)))) 114 | (i32.and (i32.eqz (local.get $p6)) (i32.eq (local.get $p7) (i32.const 1)))) 115 | (i32.and (i32.eqz (local.get $p7)) (i32.eq (local.get $p8) (i32.const 1)))) 116 | (i32.and (i32.eqz (local.get $p8)) (i32.eq (local.get $p9) (i32.const 1)))) 117 | (i32.and (i32.eqz (local.get $p9)) (i32.eq (local.get $p2) (i32.const 1)))) 118 | ) 119 | ;; B = p2 + p3 + p4 + ... + p8 + p9 120 | (local.set $B (i32.add (i32.add( i32.add 121 | (i32.add (local.get $p2) (local.get $p3)) 122 | (i32.add (local.get $p4) (local.get $p5))) 123 | (i32.add (local.get $p6) (local.get $p7))) 124 | (i32.add (local.get $p8) (local.get $p9))) 125 | ) 126 | 127 | (if (i32.eqz (local.get $iter)) (then 128 | ;; first subiteration, m1 = p2*p4*p6, m2 = p4*p6*p8 129 | (local.set $m1 (i32.mul(i32.mul (local.get $p2) (local.get $p4)) (local.get $p6))) 130 | (local.set $m2 (i32.mul(i32.mul (local.get $p4) (local.get $p6)) (local.get $p8))) 131 | )(else 132 | ;; second subiteration, m1 = p2*p4*p8, m2 = p2*p6*p8 133 | (local.set $m1 (i32.mul(i32.mul (local.get $p2) (local.get $p4)) (local.get $p8))) 134 | (local.set $m2 (i32.mul(i32.mul (local.get $p2) (local.get $p6)) (local.get $p8))) 135 | )) 136 | 137 | ;; the contour point is deleted if it satisfies the following conditions: 138 | ;; A == 1 && 2 <= B <= 6 && m1 == 0 && m2 == 0 139 | (if (i32.and(i32.and(i32.and(i32.and 140 | (i32.eq (local.get $A) (i32.const 1)) 141 | (i32.lt_u (i32.const 1) (local.get $B))) 142 | (i32.lt_u (local.get $B) (i32.const 7))) 143 | (i32.eqz (local.get $m1))) 144 | (i32.eqz (local.get $m2))) 145 | (then 146 | ;; we cannot erase the pixel directly because computation for neighboring pixels 147 | ;; depends on the current state of this pixel. And instead of using 2 matrices, 148 | ;; we do a |= 2 to set the second LSB to denote a to-be-erased pixel 149 | (call $set_pixel (local.get $w) (local.get $j) (local.get $i) 150 | (i32.or 151 | (call $get_pixel (local.get $w) (local.get $j) (local.get $i)) 152 | (i32.const 2) 153 | ) 154 | ) 155 | )(else)) 156 | 157 | ;; increment loopers 158 | (local.set $j (i32.add (local.get $j) (i32.const 1))) 159 | (br_if $loop_j (i32.lt_u (local.get $j) (i32.sub (local.get $w) (i32.const 1))) ) 160 | end 161 | (local.set $i (i32.add (local.get $i) (i32.const 1))) 162 | (br_if $loop_i (i32.lt_u (local.get $i) (i32.sub (local.get $h) (i32.const 1)))) 163 | end 164 | 165 | ;; for (i = 1; i < h; i++) 166 | (local.set $i (i32.const 0)) 167 | loop $loop_i2 168 | 169 | ;; for (j = 0; j < w; j++) 170 | (local.set $j (i32.const 0)) 171 | loop $loop_j2 172 | ;; bit-twiddling to retrive the new image stored in the second LSB 173 | ;; and check if the image has changed 174 | ;; mark = mem[i,j] >> 1 175 | ;; old = mem[i,j] & 1 176 | ;; mem[i,j] = old & (!marker) 177 | (local.set $neu (call $get_pixel (local.get $w) (local.get $j) (local.get $i))) 178 | (local.set $mark (i32.shr_u (local.get $neu) (i32.const 1))) 179 | (local.set $old (i32.and (local.get $neu) (i32.const 1))) 180 | (local.set $neu (i32.and (local.get $old) (i32.eqz (local.get $mark)))) 181 | 182 | (call $set_pixel (local.get $w) (local.get $j) (local.get $i) (local.get $neu)) 183 | 184 | ;; image has changed, tell caller function that we will need more iterations 185 | (if (i32.ne (local.get $neu) (local.get $old)) (then 186 | (local.set $diff (i32.const 1)) 187 | )) 188 | 189 | (local.set $j (i32.add (local.get $j) (i32.const 1))) 190 | (br_if $loop_j2 (i32.lt_u (local.get $j) (local.get $w))) 191 | end 192 | (local.set $i (i32.add (local.get $i) (i32.const 1))) 193 | (br_if $loop_i2 (i32.lt_u (local.get $i) (local.get $h))) 194 | end 195 | 196 | ;; return 197 | (local.get $diff) 198 | ) 199 | 200 | ;; main thinning routine 201 | ;; run thinning iteration until done 202 | ;; w: width, h:height 203 | (func $thinning_zs (param $w i32) (param $h i32) 204 | (local $diff i32) 205 | (local.set $diff (i32.const 1)) 206 | loop $l0 207 | ;; even subiteration 208 | (local.set $diff (i32.and 209 | (local.get $diff) 210 | (call $thinning_zs_iteration (local.get $w) (local.get $h) (i32.const 0)) 211 | )) 212 | ;; odd subiteration 213 | (local.set $diff (i32.and 214 | (local.get $diff) 215 | (call $thinning_zs_iteration (local.get $w) (local.get $h) (i32.const 1)) 216 | )) 217 | ;; no change -> done! 218 | (br_if $l0 (i32.eq (local.get $diff) (i32.const 1))) 219 | end 220 | ) 221 | 222 | ;; exported API's 223 | (export "thinning_zs_iteration" (func $thinning_zs_iteration)) 224 | (export "thinning_zs" (func $thinning_zs)) 225 | (export "get_pixel" (func $get_pixel)) 226 | (export "set_pixel" (func $set_pixel)) 227 | (export "mem" (memory $mem)) 228 | ) 229 | 230 | -------------------------------------------------------------------------------- /tests/containers.html: -------------------------------------------------------------------------------- 1 | 2 | 391 | 392 | -------------------------------------------------------------------------------- /wat/disttransform.wat: -------------------------------------------------------------------------------- 1 | ;;========================================================;; 2 | ;; DISTANCE TRANSFORM WITH HANDWRITTEN WEBASSEMBLY ;; 3 | ;; `disttransform.wat` Lingdong Huang 2020 Public Domain ;; 4 | ;;========================================================;; 5 | ;; Computing distance transform of binary images ;; 6 | ;; Implements Meijster-Roerdink-Hesselink algorithm ;; 7 | ;; fab.cba.mit.edu/classes/S62.12/docs/Meijster_distance.pdf 8 | ;;--------------------------------------------------------;; 9 | 10 | (module 11 | 12 | (memory $mem 64) 13 | 14 | ;; memory layout 15 | ;;0_________________INPUT___________________ 16 | ;;| m x n bytes input image 17 | ;;|_________________OUTPUT__________________ 18 | ;;| m x n x 4 bytes distance transform 19 | ;;|_______________INTERNALS_________________ 20 | ;;| m x n x 4 bytes g matrix 21 | ;;|......................................... 22 | ;;| m x 4 bytes s vector 23 | ;;|......................................... 24 | ;;| m x 4 bytes t vector 25 | ;;|_________________________________________ 26 | 27 | (global $m (mut i32) (i32.const 512 )) ;; width 28 | (global $n (mut i32) (i32.const 512 )) ;; height 29 | (global $dt_offset (mut i32) (i32.const 262144 )) ;; memory offsets 30 | (global $g_offset (mut i32) (i32.const 1370720)) ;; .. 31 | (global $s_offset (mut i32) (i32.const 2359296)) ;; .. 32 | (global $t_offset (mut i32) (i32.const 2361344)) ;; .. 33 | 34 | ;; getters and setters for accessing differerent matrices in memory 35 | ;; basically: offset + ( y * width + x ) * element_size 36 | 37 | ;; read input image at coordinate 38 | (func $get_b (param $x i32) (param $y i32) (result i32) 39 | (i32.load8_u (i32.add 40 | (i32.mul (global.get $m) (local.get $y)) 41 | (local.get $x) 42 | )) 43 | ) 44 | ;; write input image at coordinate 45 | (func $set_b (param $x i32) (param $y i32) (param $v i32) 46 | (i32.store8 (i32.add 47 | (i32.mul (global.get $m) (local.get $y)) 48 | (local.get $x) 49 | ) (local.get $v)) 50 | ) 51 | ;; read output distance transform at coordinate 52 | (func $get_dt (param $x i32) (param $y i32) (result i32) 53 | (i32.load (i32.add (global.get $dt_offset) (i32.mul 54 | (i32.add (i32.mul (global.get $m) (local.get $y)) (local.get $x)) 55 | (i32.const 4) 56 | ))) 57 | ) 58 | ;; write output distance transform at coordinate 59 | (func $set_dt (param $x i32) (param $y i32) (param $v i32) 60 | (i32.store (i32.add (global.get $dt_offset) (i32.mul 61 | (i32.add (i32.mul (global.get $m) (local.get $y)) (local.get $x)) 62 | (i32.const 4) 63 | )) (local.get $v)) 64 | ) 65 | ;; read g matrix at coordinate 66 | (func $get_g (param $x i32) (param $y i32) (result i32) 67 | (i32.load (i32.add (global.get $g_offset) (i32.mul 68 | (i32.add (i32.mul (global.get $m) (local.get $y)) (local.get $x)) 69 | (i32.const 4) 70 | ))) 71 | ) 72 | ;; write g matrix at coordinate 73 | (func $set_g (param $x i32) (param $y i32) (param $v i32) 74 | (i32.store (i32.add (global.get $g_offset) (i32.mul 75 | (i32.add (i32.mul (global.get $m) (local.get $y)) (local.get $x)) 76 | (i32.const 4) 77 | )) (local.get $v)) 78 | ) 79 | ;; read s vector at coordinate 80 | (func $get_s (param $q i32) (result i32) 81 | (i32.load (i32.add (global.get $s_offset) 82 | (i32.mul (local.get $q) (i32.const 4)) 83 | )) 84 | ) 85 | ;; write s vector at coordinate 86 | (func $set_s (param $q i32) (param $v i32) 87 | (i32.store (i32.add (global.get $s_offset) 88 | (i32.mul (local.get $q) (i32.const 4)) 89 | ) (local.get $v)) 90 | ) 91 | ;; read t vector at coordinate 92 | (func $get_t (param $q i32) (result i32) 93 | (i32.load (i32.add (global.get $t_offset) 94 | (i32.mul (local.get $q) (i32.const 4)) 95 | )) 96 | ) 97 | ;; write t vector at coordinate 98 | (func $set_t (param $q i32) (param $v i32) 99 | (i32.store (i32.add (global.get $t_offset) 100 | (i32.mul (local.get $q) (i32.const 4)) 101 | ) (local.get $v)) 102 | ) 103 | 104 | ;; f(x,i) = (x-i)^2+g(i)^2 105 | (func $edt_f (param $x i32) (param $i i32) (param $y i32) (result i32) 106 | (local $gi i32) ;; g(i)=G(i,y) 107 | (local $x_i i32) 108 | (local.set $gi (call $get_g (local.get $i) (local.get $y))) 109 | (local.set $x_i (i32.sub (local.get $x) (local.get $i))) 110 | (i32.add 111 | (i32.mul (local.get $x_i) (local.get $x_i)) 112 | (i32.mul (local.get $gi) (local.get $gi)) 113 | ) 114 | ) 115 | ;; Sep(i,u) = (u^2 - i^2 - g(u)^2 - g(i)^2 ) div (2(u-i)) 116 | (func $edt_sep (param $i i32) (param $u i32) (param $y i32) (result i32) 117 | (local $gi i32) ;; g(i)=G(i,y) 118 | (local $gu i32) 119 | (local.set $gi (call $get_g (local.get $i) (local.get $y))) 120 | (local.set $gu (call $get_g (local.get $u) (local.get $y))) 121 | (i32.div_u 122 | (i32.sub 123 | (i32.add 124 | (i32.sub 125 | (i32.mul (local.get $u) (local.get $u)) 126 | (i32.mul (local.get $i) (local.get $i)) 127 | ) 128 | (i32.mul (local.get $gu) (local.get $gu)) 129 | ) 130 | (i32.mul (local.get $gi) (local.get $gi)) 131 | ) 132 | (i32.mul (i32.const 2) 133 | (i32.sub (local.get $u) (local.get $i)) 134 | ) 135 | ) 136 | ) 137 | 138 | ;; setup: specify width and height of input image 139 | ;; (call before dist_transform()) 140 | (func $setup (param $w i32) (param $h i32) 141 | (global.set $m (local.get $w)) 142 | (global.set $n (local.get $h)) 143 | 144 | ;; memory offsets are all computed here 145 | (global.set $dt_offset (i32.mul (local.get $w) (local.get $h))) 146 | (global.set $g_offset (i32.mul (global.get $dt_offset) (i32.const 5))) 147 | (global.set $s_offset (i32.mul (global.get $dt_offset) (i32.const 9))) 148 | (global.set $t_offset (i32.add 149 | (global.get $s_offset) 150 | (i32.mul (local.get $w) (i32.const 4)) 151 | )) 152 | 153 | ) 154 | 155 | ;; main distance transform algorithm 156 | ;; before calling: 157 | ;; - use setup() for initialization 158 | ;; - use set_b() for setting pixels of the image 159 | ;; after calling: 160 | ;; - use get_dt() for reading output 161 | (func $dist_transform 162 | ;; this implementation is a verbatim translation of pseudocode described in 163 | ;; http://fab.cba.mit.edu/classes/S62.12/docs/Meijster_distance.pdf 164 | 165 | ;; local variables 166 | ;; variables from the paper 167 | (local $x i32) 168 | (local $y i32) 169 | (local $q i32) 170 | (local $u i32) 171 | (local $w i32) 172 | ;; temporary computation results 173 | (local $gxy1 i32) 174 | 175 | ;; FIRST PHASE 176 | 177 | ;; forall x E [0..m-1] do 178 | (local.set $x (i32.const 0)) 179 | loop $phase1 180 | 181 | ;; if b[x,0] then 182 | ;; g[x,0] := 0 183 | ;; else 184 | ;; g[x,0] := infinity 185 | ;; endif 186 | (if (call $get_b (local.get $x) (i32.const 0)) (then 187 | (call $set_g (local.get $x) (i32.const 0) (i32.const 0)) 188 | )(else 189 | (call $set_g (local.get $x) (i32.const 0) (i32.add (global.get $m) (global.get $n))) 190 | )) 191 | 192 | ;; for y := 1 to n-1 do 193 | ;; if b[x,y] then 194 | ;; g[x,y] := 0 195 | ;; else 196 | ;; g[x,y] := 1 + g[x,y-1] 197 | ;; endif 198 | (local.set $y (i32.const 1)) 199 | loop $scan1 200 | (if (call $get_b (local.get $x) (local.get $y)) (then 201 | (call $set_g (local.get $x) (local.get $y) (i32.const 0)) 202 | )(else 203 | (call $set_g (local.get $x) (local.get $y) 204 | (i32.add (i32.const 1) 205 | (call $get_g (local.get $x) (i32.sub (local.get $y) (i32.const 1)) ) 206 | ) 207 | ) 208 | )) 209 | (local.set $y (i32.add (local.get $y) (i32.const 1))) 210 | (br_if $scan1 (i32.lt_u (local.get $y) (global.get $n))) 211 | end 212 | 213 | ;; for y := n-2 downto 0 do 214 | ;; if g[x,y+1] < g[x,y] then 215 | ;; g[x,y] := (1 + g[x,y+1]) 216 | ;; endif 217 | (local.set $y (i32.sub (global.get $n) (i32.const 2))) 218 | loop $scan2 219 | (local.set $gxy1 220 | (call $get_g (local.get $x) (i32.add (local.get $y) (i32.const 1))) 221 | ) 222 | (if (i32.lt_u (local.get $gxy1) 223 | (call $get_g (local.get $x) (local.get $y))) 224 | (then 225 | (call $set_g (local.get $x) (local.get $y) 226 | (i32.add (i32.const 1) (local.get $gxy1)) 227 | ) 228 | )) 229 | (local.set $y (i32.sub (local.get $y) (i32.const 1))) 230 | (br_if $scan2 (i32.gt_s (local.get $y) (i32.const -1))) 231 | end 232 | 233 | (local.set $x (i32.add (local.get $x) (i32.const 1))) 234 | (br_if $phase1 (i32.lt_u (local.get $x) (global.get $m))) 235 | end 236 | 237 | ;; SECOND PHASE 238 | 239 | ;; forall y E [0..n-1] do 240 | (local.set $y (i32.const 0)) 241 | loop $phase2 242 | ;; q := 0; s[0] := 0; t[0] := 0; 243 | (local.set $q (i32.const 0)) 244 | (call $set_s (i32.const 0) (i32.const 0)) 245 | (call $set_t (i32.const 0) (i32.const 0)) 246 | 247 | ;; for u := 1 to m-1 do (* scan 3 *) 248 | ;; while q >= 0 ^ f(t[q],s[q]) > f(t[q],u) do 249 | ;; q := q - 1 250 | ;; if q < 0 then 251 | ;; q := 0; s[0] := u 252 | ;; else 253 | ;; w := 1 + Sep(q[q],u) 254 | ;; if w < m then 255 | ;; q := q + 1; s[q] := u; t[q] := w 256 | ;; end if 257 | ;; end if 258 | ;; end for 259 | (local.set $u (i32.const 1)) 260 | loop $scan3 261 | loop $while 262 | (if (i32.and 263 | (i32.gt_s (local.get $q) (i32.const -1)) 264 | (i32.gt_s 265 | (call $edt_f (call $get_t (local.get $q)) (call $get_s (local.get $q)) (local.get $y)) 266 | (call $edt_f (call $get_t (local.get $q)) (local.get $u) (local.get $y)) 267 | ) 268 | )(then 269 | (local.set $q (i32.sub (local.get $q) (i32.const 1))) 270 | (br $while) 271 | )) 272 | end 273 | (if (i32.lt_s (local.get $q) (i32.const 0))(then 274 | (local.set $q (i32.const 0)) 275 | (call $set_s (i32.const 0) (local.get $u)) 276 | )(else 277 | (local.set $w (i32.add (i32.const 1) 278 | (call $edt_sep (call $get_s (local.get $q)) (local.get $u) (local.get $y)) 279 | )) 280 | (if (i32.lt_u (local.get $w) (global.get $m)) (then 281 | (local.set $q (i32.add (local.get $q) (i32.const 1))) 282 | (call $set_s (local.get $q) (local.get $u)) 283 | (call $set_t (local.get $q) (local.get $w)) 284 | )) 285 | )) 286 | 287 | (local.set $u (i32.add (local.get $u) (i32.const 1))) 288 | (br_if $scan3 (i32.lt_u (local.get $u) (global.get $m))) 289 | end 290 | 291 | ;; for u := m - 1 downto 0 do (* scan 4 *) 292 | ;; dt[u,y] := f(u,s[q]) 293 | ;; if u = t[q] then q := q - 1 294 | ;; end for 295 | (local.set $u (i32.sub (global.get $m) (i32.const 1))) 296 | loop $scan4 297 | (call $set_dt (local.get $u) (local.get $y) 298 | (call $edt_f (local.get $u) (call $get_s (local.get $q)) (local.get $y)) 299 | ) 300 | (if (i32.eq (local.get $u) (call $get_t (local.get $q))) (then 301 | (local.set $q (i32.sub (local.get $q) (i32.const 1))) 302 | )) 303 | (local.set $u (i32.sub (local.get $u) (i32.const 1))) 304 | (br_if $scan4 (i32.gt_s (local.get $u) (i32.const -1))) 305 | end 306 | 307 | 308 | (local.set $y (i32.add (local.get $y) (i32.const 1))) 309 | (br_if $phase2 (i32.lt_u (local.get $y) (global.get $n))) 310 | end 311 | ) 312 | 313 | 314 | ;; exported API's 315 | (export "setup" (func $setup )) 316 | (export "dist_transform" (func $dist_transform)) 317 | (export "set_b" (func $set_b )) 318 | (export "get_dt" (func $get_dt )) 319 | (export "mem" (memory $mem )) 320 | ) 321 | 322 | -------------------------------------------------------------------------------- /wat/mazegen.wat: -------------------------------------------------------------------------------- 1 | ;;========================================================;; 2 | ;; MAZE GENERATION WITH HANDWRITTEN WEBASSEMBLY ;; 3 | ;; `mazegen.wat` Lingdong Huang 2020 Public Domain ;; 4 | ;;========================================================;; 5 | ;; Generate mazes with Wilson's algorithm ;; 6 | ;; (loop-erased random walks) ;; 7 | ;; https://en.wikipedia.org/wiki/Maze_generation_algorithm;; 8 | ;;--------------------------------------------------------;; 9 | 10 | (module 11 | 12 | ;; This maze generator uses Wilson's algorithm, which isn't very 13 | ;; efficient at all, but has the nice property of being unbiased 14 | ;; (which is one of the motivations to implement it in something 15 | ;; fast like WebAssembly): 16 | ;; "... depth-first search is biased toward long corridors, while 17 | ;; Kruskal's/Prim's algorithms are biased toward many short dead 18 | ;; ends. Wilson's algorithm, on the other hand, generates an 19 | ;; unbiased sample from the uniform distribution over all mazes, 20 | ;; using loop-erased random walks." -- Wikipedia 21 | 22 | ;; 4 pages = 4 x 64kb = max dimension 512x512 (cell=1 byte) 23 | (memory $mem 4) 24 | 25 | ;; All intermediate and final data used by the algorithm are stored 26 | ;; in each cell, which is thriftly encoded by one byte, as follows: 27 | ;; 28 | ;; |--- MSB --- 29 | ;; 7 UNUSED 30 | ;; 6 (TEMP) WALK: EXIT DIRECTION - or + 31 | ;; 5 (TEMP) WALK: EXIT DIRECTION x or y 32 | ;; 4 VISITED? 33 | ;; 3 LEFT WALL OPEN? 34 | ;; 2 BOTTOM WALL OPEN? 35 | ;; 1 TOP WALL OPEN? 36 | ;; 0 RIGHT WALL OPEN? 37 | ;; |--- LSB --- 38 | 39 | 40 | (global $jsr (mut i32) (i32.const 0x5EED)) ;; shr3 random seed 41 | (global $w (mut i32) (i32.const 512 )) ;; width 42 | (global $h (mut i32) (i32.const 512 )) ;; height 43 | 44 | ;; shr3 random number generator 45 | (func $shr3 (result i32) 46 | (global.set $jsr (i32.xor (global.get $jsr) (i32.shl (global.get $jsr) (i32.const 17)))) 47 | (global.set $jsr (i32.xor (global.get $jsr) (i32.shr_u (global.get $jsr) (i32.const 13)))) 48 | (global.set $jsr (i32.xor (global.get $jsr) (i32.shl (global.get $jsr) (i32.const 5 )))) 49 | (global.get $jsr) 50 | ) 51 | (func $set_seed (param $seed i32) (global.set $jsr (local.get $seed))) 52 | 53 | 54 | ;; get the highest three bits of a cell at given coordinate 55 | ;; (used by the random walks) 56 | (func $get_hi3 (param $x i32) (param $y i32) (result i32) 57 | (i32.and (i32.shr_u (i32.load8_u (i32.add 58 | (i32.mul (global.get $w) (local.get $y)) 59 | (local.get $x) 60 | )) (i32.const 5)) (i32.const 0x7)) 61 | ) 62 | ;; get the lowest five bits of a cell at given coordinate 63 | ;; (used to mark visited cells and walls) 64 | (func $get_lo5 (param $x i32) (param $y i32) (result i32) 65 | (i32.and (i32.load8_u (i32.add 66 | (i32.mul (global.get $w) (local.get $y)) 67 | (local.get $x) 68 | )) (i32.const 0x1F)) 69 | ) 70 | ;; erase the highest three bits of a cell at given coordinate 71 | ;; (to clean up the leftovers from the walks) 72 | (func $clear_hi3 (param $x i32) (param $y i32) 73 | (local $o i32) 74 | (local.set $o (i32.add (i32.mul (global.get $w) (local.get $y)) (local.get $x))) 75 | (i32.store8 (local.get $o) 76 | (i32.and (i32.load8_u (local.get $o)) (i32.const 0x1F)) 77 | ) 78 | ) 79 | ;; set the highest three bits of a cell at given coordinate 80 | ;; (used by the random walks) 81 | (func $set_hi3 (param $x i32) (param $y i32) (param $v i32) 82 | (local $o i32) 83 | (local.set $o (i32.add (i32.mul (global.get $w) (local.get $y)) (local.get $x))) 84 | (i32.store8 (local.get $o) 85 | (i32.or 86 | (i32.and (i32.load8_u (local.get $o)) (i32.const 0x1F)) 87 | (i32.shl (local.get $v) (i32.const 5)) 88 | ) 89 | ) 90 | ) 91 | ;; turn on the nth bit of a cell at given coordinate 92 | ;; (lsb=0,msb=7) 93 | (func $on_bitn (param $x i32) (param $y i32) (param $n i32) 94 | (local $o i32) 95 | (local.set $o (i32.add (i32.mul (global.get $w) (local.get $y)) (local.get $x))) 96 | (i32.store8 (local.get $o) 97 | (i32.or (i32.load8_u (local.get $o)) (i32.shl (i32.const 0x1) (local.get $n))) 98 | ) 99 | ) 100 | ;; read all 8-bits of a cell at given coordinate 101 | (func $get_cell (param $x i32) (param $y i32) (result i32) 102 | (i32.load8_u (i32.add 103 | (i32.mul (global.get $w) (local.get $y)) 104 | (local.get $x) 105 | )) 106 | ) 107 | ;; set all 8-bits of a cell at given coordinate 108 | (func $set_cell (param $x i32) (param $y i32) (param $v i32) 109 | (i32.store8 (i32.add 110 | (i32.mul (global.get $w) (local.get $y)) 111 | (local.get $x) 112 | ) (local.get $v)) 113 | ) 114 | 115 | ;; main routine for generating the maze 116 | ;; params: width, height -> maze dimensions in cells 117 | (func $generate_maze (param $width i32) (param $height i32) 118 | 119 | ;; local variables 120 | (local $x i32) ;; current coordinate 121 | (local $y i32) ;; | 122 | (local $sx i32) ;; start coordinate of the walk 123 | (local $sy i32) ;; | 124 | (local $ox i32) ;; last coordinate 125 | (local $oy i32) ;; | 126 | (local $r i32) ;; random number (for walk direction) 127 | 128 | (global.set $w (local.get $width )) 129 | (global.set $h (local.get $height)) 130 | 131 | ;; clear the entire matrix (from previous runs) 132 | (local.set $y (i32.const 0)) 133 | loop $clear_y 134 | (local.set $x (i32.const 0)) 135 | loop $clear_x 136 | (call $set_cell (local.get $x) (local.get $y) (i32.const 0)) 137 | (local.set $x (i32.add (local.get $x) (i32.const 1))) 138 | (br_if $clear_x (i32.lt_u (local.get $x) (global.get $w))) 139 | end 140 | (local.set $y (i32.add (local.get $y) (i32.const 1))) 141 | (br_if $clear_y (i32.lt_u (local.get $y) (global.get $h))) 142 | end 143 | 144 | ;; starting cell is (0,0) (but any other cell would work too) 145 | (call $set_cell (i32.const 0) (i32.const 0) (i32.const 8)) 146 | 147 | ;; start walking from another unvisited cell 148 | ;; (again, any cell would work, but (1,0) is picked out of laziness) 149 | (local.set $sx (i32.const 1)) 150 | (local.set $sy (i32.const 0)) 151 | (local.set $x (local.get $sx)) 152 | (local.set $y (local.get $sy)) 153 | 154 | loop $walk 155 | 156 | (local.set $ox (local.get $x)) 157 | (local.set $oy (local.get $y)) 158 | loop $dir 159 | 160 | ;; pick a random direction = rand()%4 161 | (local.set $r (i32.rem_u (call $shr3) (i32.const 4))) 162 | 163 | ;; the LSB of the random number determines the axis, x or y 164 | (if (i32.eqz (i32.and (local.get $r) (i32.const 1))) (then 165 | ;; the MSB determines direction, - or + 166 | (local.set $x (i32.add (local.get $x) 167 | (i32.sub (i32.and (local.get $r) (i32.const 2)) (i32.const 1)) 168 | )) 169 | )(else 170 | (local.set $y (i32.add (local.get $y) 171 | (i32.sub (i32.and (local.get $r) (i32.const 2)) (i32.const 1)) 172 | )) 173 | )) 174 | ;; illegal moves (out of bounds), retry 175 | (if (i32.or(i32.or(i32.or 176 | (i32.lt_s (local.get $x) (i32.const 0)) 177 | (i32.lt_s (local.get $y) (i32.const 0))) 178 | (i32.gt_s (local.get $x) (i32.sub (global.get $w) (i32.const 1)))) 179 | (i32.gt_s (local.get $y) (i32.sub (global.get $h) (i32.const 1)))) 180 | (then 181 | ;; undo 182 | (local.set $x (local.get $ox)) 183 | (local.set $y (local.get $oy)) 184 | (br $dir) ;; redo 185 | )) 186 | end 187 | 188 | ;; record the walk 189 | (call $set_hi3 (local.get $ox) (local.get $oy) (local.get $r) ) 190 | ;; Instead of keeping the cells visited by the walk in a stack, and having to 191 | ;; detect and remove loops etc., we can simply (and more efficiently) overwrite 192 | ;; the "exit direction" for each cell, and upon re-tracing the walk when adding 193 | ;; it to the maze by following these directions, the loops are automatically 194 | ;; avoided because of the overwrite. Inspired by 195 | ;; http://weblog.jamisbuck.org/2011/1/20/maze-generation-wilson-s-algorithm.html 196 | 197 | 198 | (if (call $get_lo5 (local.get $x) (local.get $y)) (then 199 | ;; we've hit a cell that's part of the maze. the walk is done! 200 | 201 | (local.set $ox (local.get $x)) ;; save the end point of the walk 202 | (local.set $oy (local.get $y)) 203 | 204 | (local.set $x (local.get $sx)) ;; go back to the start point 205 | (local.set $y (local.get $sy)) 206 | 207 | ;; add the walk to the maze 208 | loop $retrace 209 | 210 | ;; recover the exit direction 211 | (local.set $r (call $get_hi3 (local.get $x) (local.get $y)) ) 212 | 213 | ;; turn on the "visited" bit 214 | (call $on_bitn (local.get $x) (local.get $y) (i32.const 4)) 215 | 216 | ;; break the walls and goto next cell 217 | ;; notice the vim "HJKL" ordering for walls :) 218 | (if (i32.eqz (i32.and (local.get $r) (i32.const 1))) (then 219 | (if (i32.eqz (i32.and (local.get $r) (i32.const 2))) (then 220 | ;; break left wall of this cell 221 | (call $on_bitn (local.get $x) (local.get $y) (i32.const 3)) 222 | ;; exit left 223 | (local.set $x (i32.sub (local.get $x) (i32.const 1))) 224 | ;; break right wall of next cell 225 | (call $on_bitn (local.get $x) (local.get $y) (i32.const 0)) 226 | 227 | )(else 228 | ;; break right wall of this cell 229 | (call $on_bitn (local.get $x) (local.get $y) (i32.const 0)) 230 | ;; exit right 231 | (local.set $x (i32.add (local.get $x) (i32.const 1))) 232 | ;; break left wall of next cell 233 | (call $on_bitn (local.get $x) (local.get $y) (i32.const 3)) 234 | 235 | )) 236 | )(else 237 | (if (i32.eqz (i32.and (local.get $r) (i32.const 2))) (then 238 | ;; break top wall of this cell 239 | (call $on_bitn (local.get $x) (local.get $y) (i32.const 1)) 240 | ;; exit top 241 | (local.set $y (i32.sub (local.get $y) (i32.const 1))) 242 | ;; break bottom wall of next cell 243 | (call $on_bitn (local.get $x) (local.get $y) (i32.const 2)) 244 | 245 | )(else 246 | ;; break bottom wall of this cell 247 | (call $on_bitn (local.get $x) (local.get $y) (i32.const 2)) 248 | ;; exit bottom 249 | (local.set $y (i32.add (local.get $y) (i32.const 1))) 250 | ;; break top wall of next cell 251 | (call $on_bitn (local.get $x) (local.get $y) (i32.const 1)) 252 | 253 | )) 254 | )) 255 | ;; check if we've reached the end of the walk 256 | (if (i32.and 257 | (i32.eq (local.get $x) (local.get $ox)) 258 | (i32.eq (local.get $y) (local.get $oy)) 259 | )(then)(else 260 | (br $retrace) ;; haven't reached the end yet, back to looping 261 | )) 262 | end 263 | 264 | ;; reset starting point 265 | (local.set $sx (i32.const -1)) 266 | (local.set $sy (i32.const -1)) 267 | 268 | ;; clean up the leftover from the walk for the cells 269 | ;; and scan for a new starting point 270 | (local.set $y (i32.const 0)) 271 | loop $clean_y 272 | (local.set $x (i32.const 0)) 273 | loop $clean_x 274 | (call $clear_hi3 (local.get $x) (local.get $y)) 275 | 276 | ;; check if we still haven't found a new starting point... 277 | (if (i32.and 278 | (i32.eq (local.get $sx) (i32.const -1)) 279 | (i32.eq (local.get $sy) (i32.const -1)) 280 | )(then 281 | ;; ... and if a cell is elligible (unvisited) 282 | (if (i32.eqz (call $get_lo5 (local.get $x) (local.get $y)))(then 283 | (local.set $sx (local.get $x)) 284 | (local.set $sy (local.get $y)) 285 | )) 286 | )) 287 | (local.set $x (i32.add (local.get $x) (i32.const 1))) 288 | (br_if $clean_x (i32.lt_u (local.get $x) (global.get $w))) 289 | end 290 | (local.set $y (i32.add (local.get $y) (i32.const 1))) 291 | (br_if $clean_y (i32.lt_u (local.get $y) (global.get $h))) 292 | end 293 | 294 | ;; check if we've found a new starting point 295 | (if (i32.and 296 | (i32.eq (local.get $sx) (i32.const -1)) 297 | (i32.eq (local.get $sy) (i32.const -1)) 298 | )(then)(else 299 | ;; new starting point found, start over 300 | (local.set $x (local.get $sx)) 301 | (local.set $y (local.get $sy)) 302 | (local.set $ox (local.get $x)) 303 | (local.set $oy (local.get $y)) 304 | 305 | (br $walk) 306 | )) 307 | ;; if this point is reached, algorithm is finished, 308 | ;; function returns. 309 | 310 | )(else ;; haven't hit a cell that's part of the maze 311 | (br $walk) ;; keep walking 312 | )) 313 | 314 | end 315 | ) 316 | 317 | ;; exported API's 318 | (export "generate_maze" (func $generate_maze)) 319 | (export "get_cell" (func $get_cell )) 320 | (export "set_seed" (func $set_seed )) 321 | (export "mem" (memory $mem )) 322 | ) -------------------------------------------------------------------------------- /wat/malloc.wat: -------------------------------------------------------------------------------- 1 | ;;========================================================;; 2 | ;; BASELINE MALLOC WITH HANDWRITTEN WEBASSEMBLY ;; 3 | ;; `malloc.wat` Lingdong Huang 2020 Public Domain ;; 4 | ;;========================================================;; 5 | ;; 32-bit implicit-free-list first-fit baseline malloc ;; 6 | ;;--------------------------------------------------------;; 7 | 8 | (module 9 | 10 | ;; IMPLICIT FREE LIST: 11 | ;; Worse utilization and throughput than explicit/segregated, but easier 12 | ;; to implement :P 13 | ;; 14 | ;; HEAP LO HEAP HI 15 | ;; +---------------------+---------------------+...+---------------------+ 16 | ;; | HDR | PAYLOAD | FTR | HDR | PAYLOAD | FTR |...+ HDR | PAYLOAD | FTR | 17 | ;; +----------^----------+---------------------+...+---------------------+ 18 | ;; |_ i.e. user data 19 | ;; 20 | ;; LAYOUT OF A BLOCK: 21 | ;; Since memory is aligned to multiple of 4 bytes, the last two bits of 22 | ;; payload_size is redundant. Therefore the last bit of header is used to 23 | ;; store the is_free flag. 24 | ;; 25 | ;; |---- HEADER (4b)---- 26 | ;; | ,--payload size (x4)--. ,-is free? 27 | ;; | 0b . . . . . . . . . . . . 0 0 28 | ;; |------ PAYLOAD ----- 29 | ;; | 30 | ;; | user data (N x 4b) 31 | ;; | 32 | ;; |---- FOOTER (4b)---- (duplicate of header) 33 | ;; | ,--payload size (x4)--. ,-is free? 34 | ;; | 0b . . . . . . . . . . . . 0 0 35 | ;; |-------------------- 36 | ;; 37 | ;; FORMULAS: 38 | ;; (these formulas are used throughout the code, so they're listed here 39 | ;; instead of explained each time encountered) 40 | ;; 41 | ;; payload_size = block_size - (header_size + footer_size) = block_size - 8 42 | ;; 43 | ;; payload_pointer = header_pointer + header_size = header_pointer + 4 44 | ;; 45 | ;; footer_pointer = header_pointer + header_size + payload_size 46 | ;; = (header_pointer + payload_size) + 4 47 | ;; 48 | ;; next_header_pointer = footer_pointer + footer_size = footer_pointer + 4 49 | ;; 50 | ;; prev_footer_pointer = header_pointer - footer_size = header_pointer - 4 51 | 52 | (memory $mem 1) ;; start with 1 page (64K) 53 | (global $max_addr (mut i32) (i32.const 65536)) ;; initial heap size (64K) 54 | (global $did_init (mut i32) (i32.const 0)) ;; init() called? 55 | 56 | ;; helpers to pack/unpack payload_size/is_free from header/footer 57 | ;; by masking out bits 58 | 59 | ;; read payload_size from header/footer given pointer to header/footer 60 | (func $hdr_get_size (param $ptr i32) (result i32) 61 | (i32.and (i32.load (local.get $ptr)) (i32.const 0xFFFFFFFC)) 62 | ) 63 | ;; read is_free from header/footer 64 | (func $hdr_get_free (param $ptr i32) (result i32) 65 | (i32.and (i32.load (local.get $ptr)) (i32.const 0x00000001)) 66 | ) 67 | ;; write payload_size to header/footer 68 | (func $hdr_set_size (param $ptr i32) (param $n i32) 69 | (i32.store (local.get $ptr) (i32.or 70 | (i32.and (i32.load (local.get $ptr)) (i32.const 0x00000003)) 71 | (local.get $n) 72 | )) 73 | ) 74 | ;; write is_free to header/footer 75 | (func $hdr_set_free (param $ptr i32) (param $n i32) 76 | (i32.store (local.get $ptr) (i32.or 77 | (i32.and (i32.load (local.get $ptr)) (i32.const 0xFFFFFFFE)) 78 | (local.get $n) 79 | )) 80 | ) 81 | ;; align memory by 4 bytes 82 | (func $align4 (param $x i32) (result i32) 83 | (i32.and 84 | (i32.add (local.get $x) (i32.const 3)) 85 | (i32.const -4) 86 | ) 87 | ) 88 | 89 | ;; initialize heap 90 | ;; make the whole heap a big free block 91 | ;; - automatically invoked by first malloc() call 92 | ;; - can be manually called to nuke the whole heap 93 | (func $init 94 | ;; write payload_size to header and footer 95 | (call $hdr_set_size (i32.const 0) (i32.sub (global.get $max_addr) (i32.const 8))) 96 | (call $hdr_set_size (i32.sub (global.get $max_addr) (i32.const 4)) 97 | (i32.sub (global.get $max_addr) (i32.const 8)) 98 | ) 99 | ;; write is_free to header and footer 100 | (call $hdr_set_free (i32.const 0) (i32.const 1)) 101 | (call $hdr_set_free (i32.sub (global.get $max_addr) (i32.const 4)) (i32.const 1)) 102 | 103 | ;; set flag to tell malloc() that we've already called init() 104 | (global.set $did_init (i32.const 1)) 105 | ) 106 | 107 | ;; extend (grow) the heap (to accomodate more blocks) 108 | ;; parameter: number of pages (64K) to grow 109 | ;; - automatically invoked by malloc() when current heap has insufficient free space 110 | ;; - can be manually called to get more space in advance 111 | (func $extend (param $n_pages i32) 112 | (local $n_bytes i32) 113 | (local $ftr i32) 114 | (local $prev_ftr i32) 115 | (local $prev_hdr i32) 116 | (local $prev_size i32) 117 | 118 | (local.set $prev_ftr (i32.sub (global.get $max_addr) (i32.const 4)) ) 119 | 120 | ;; compute number of bytes from page count (1page = 64K = 65536bytes) 121 | (local.set $n_bytes (i32.mul (local.get $n_pages) (i32.const 65536))) 122 | 123 | ;; system call to grow memory (`drop` discards the (useless) return value of memory.grow) 124 | (drop (memory.grow (local.get $n_pages) )) 125 | 126 | ;; make the newly acquired memory a big free block 127 | (call $hdr_set_size (global.get $max_addr) (i32.sub (local.get $n_bytes) (i32.const 8))) 128 | (call $hdr_set_free (global.get $max_addr) (i32.const 1)) 129 | 130 | (global.set $max_addr (i32.add (global.get $max_addr) (local.get $n_bytes) )) 131 | (local.set $ftr (i32.sub (global.get $max_addr) (i32.const 4))) 132 | 133 | (call $hdr_set_size (local.get $ftr) 134 | (i32.sub (local.get $n_bytes) (i32.const 8)) 135 | ) 136 | (call $hdr_set_free (local.get $ftr) (i32.const 1)) 137 | 138 | ;; see if we can join the new block with the last block of the old heap 139 | (if (i32.eqz (call $hdr_get_free (local.get $prev_ftr)))(then)(else 140 | 141 | ;; the last block is free, join it. 142 | (local.set $prev_size (call $hdr_get_size (local.get $prev_ftr))) 143 | (local.set $prev_hdr 144 | (i32.sub (i32.sub (local.get $prev_ftr) (local.get $prev_size)) (i32.const 4)) 145 | ) 146 | (call $hdr_set_size (local.get $prev_hdr) 147 | (i32.add (local.get $prev_size) (local.get $n_bytes) ) 148 | ) 149 | (call $hdr_set_size (local.get $ftr) 150 | (i32.add (local.get $prev_size) (local.get $n_bytes) ) 151 | ) 152 | )) 153 | 154 | ) 155 | 156 | ;; find a free block that fit the request number of bytes 157 | ;; modifies the heap once a candidate is found 158 | ;; first-fit: not the best policy, but the simplest 159 | (func $find (param $n_bytes i32) (result i32) 160 | (local $ptr i32) 161 | (local $size i32) 162 | (local $is_free i32) 163 | (local $pay_ptr i32) 164 | (local $rest i32) 165 | 166 | ;; loop through all blocks 167 | (local.set $ptr (i32.const 0)) 168 | loop $search 169 | ;; we reached the end of heap and haven't found anything, return NULL 170 | (if (i32.lt_u (local.get $ptr) (global.get $max_addr))(then)(else 171 | (i32.const 0) 172 | return 173 | )) 174 | 175 | ;; read info about current block 176 | (local.set $size (call $hdr_get_size (local.get $ptr))) 177 | (local.set $is_free (call $hdr_get_free (local.get $ptr))) 178 | (local.set $pay_ptr (i32.add (local.get $ptr) (i32.const 4) )) 179 | 180 | ;; check if the current block is free 181 | (if (i32.eq (local.get $is_free) (i32.const 1))(then 182 | 183 | ;; it's free, but too small, move on 184 | (if (i32.gt_u (local.get $n_bytes) (local.get $size))(then 185 | (local.set $ptr (i32.add (local.get $ptr) (i32.add (local.get $size) (i32.const 8)))) 186 | (br $search) 187 | 188 | ;; it's free, and large enough to be split into two blocks 189 | )(else(if (i32.lt_u (local.get $n_bytes) (i32.sub (local.get $size) (i32.const 8)))(then 190 | ;; OLD HEAP 191 | ;; ...+-------------------------------------------+... 192 | ;; ...| HDR | FREE | FTR |... 193 | ;; ...+-------------------------------------------+... 194 | ;; NEW HEAP 195 | ;; ...+---------------------+---------------------+... 196 | ;; ...| HDR | ALLOC | FTR | HDR | FREE | FTR |... 197 | ;; ...+---------------------+---------------------+... 198 | 199 | ;; size of the remaining half 200 | (local.set $rest (i32.sub (i32.sub (local.get $size) (local.get $n_bytes) ) (i32.const 8))) 201 | 202 | ;; update headers and footers to reflect the change (see FORMULAS) 203 | 204 | (call $hdr_set_size (local.get $ptr) (local.get $n_bytes)) 205 | (call $hdr_set_free (local.get $ptr) (i32.const 0)) 206 | 207 | (call $hdr_set_size (i32.add (i32.add (local.get $ptr) (local.get $n_bytes)) (i32.const 4)) 208 | (local.get $n_bytes) 209 | ) 210 | (call $hdr_set_free (i32.add (i32.add (local.get $ptr) (local.get $n_bytes)) (i32.const 4)) 211 | (i32.const 0) 212 | ) 213 | (call $hdr_set_size (i32.add (i32.add (local.get $ptr) (local.get $n_bytes)) (i32.const 8)) 214 | (local.get $rest) 215 | ) 216 | (call $hdr_set_free (i32.add (i32.add (local.get $ptr) (local.get $n_bytes)) (i32.const 8)) 217 | (i32.const 1) 218 | ) 219 | (call $hdr_set_size (i32.add (i32.add (local.get $ptr) (local.get $size)) (i32.const 4)) 220 | (local.get $rest) 221 | ) 222 | 223 | (local.get $pay_ptr) 224 | return 225 | 226 | )(else 227 | ;; the block is free, but not large enough to be split into two blocks 228 | ;; we return the whole block as one 229 | (call $hdr_set_free (local.get $ptr) (i32.const 0)) 230 | (call $hdr_set_free (i32.add (i32.add (local.get $ptr) (local.get $size)) (i32.const 4)) 231 | (i32.const 0) 232 | ) 233 | (local.get $pay_ptr) 234 | return 235 | )))) 236 | )(else 237 | ;; the block is not free, we move on to the next block 238 | (local.set $ptr (i32.add (local.get $ptr) (i32.add (local.get $size) (i32.const 8)))) 239 | (br $search) 240 | )) 241 | end 242 | 243 | ;; theoratically we will not reach here 244 | ;; return NULL 245 | (i32.const 0) 246 | ) 247 | 248 | 249 | ;; malloc - allocate the requested number of bytes on the heap 250 | ;; returns a pointer to the block of memory allocated 251 | ;; returns NULL (0) when OOM 252 | ;; if heap is not large enough, grows it via extend() 253 | (func $malloc (param $n_bytes i32) (result i32) 254 | (local $ptr i32) 255 | (local $n_pages i32) 256 | 257 | ;; call init() if we haven't done so yet 258 | (if (i32.eqz (global.get $did_init)) (then 259 | (call $init) 260 | )) 261 | 262 | ;; payload size is aligned to multiple of 4 263 | (local.set $n_bytes (call $align4 (local.get $n_bytes))) 264 | 265 | ;; attempt allocation 266 | (local.set $ptr (call $find (local.get $n_bytes)) ) 267 | 268 | ;; NULL -> OOM -> extend heap 269 | (if (i32.eqz (local.get $ptr))(then 270 | ;; compute # of pages from # of bytes, rounding up 271 | (local.set $n_pages 272 | (i32.div_u 273 | (i32.add (local.get $n_bytes) (i32.const 65527) ) 274 | (i32.const 65528) 275 | ) 276 | ) 277 | (call $extend (local.get $n_pages)) 278 | 279 | ;; try again 280 | (local.set $ptr (call $find (local.get $n_bytes)) ) 281 | )) 282 | (local.get $ptr) 283 | ) 284 | 285 | ;; free - free an allocated block given a pointer to it 286 | (func $free (param $ptr i32) 287 | (local $hdr i32) 288 | (local $ftr i32) 289 | (local $size i32) 290 | (local $prev_hdr i32) 291 | (local $prev_ftr i32) 292 | (local $prev_size i32) 293 | (local $prev_free i32) 294 | (local $next_hdr i32) 295 | (local $next_ftr i32) 296 | (local $next_size i32) 297 | (local $next_free i32) 298 | 299 | ;; step I: mark the block as free 300 | 301 | (local.set $hdr (i32.sub (local.get $ptr) (i32.const 4))) 302 | (local.set $size (call $hdr_get_size (local.get $hdr))) 303 | (local.set $ftr (i32.add (i32.add (local.get $hdr) (local.get $size)) (i32.const 4))) 304 | 305 | (call $hdr_set_free (local.get $hdr) (i32.const 1)) 306 | (call $hdr_set_free (local.get $ftr) (i32.const 1)) 307 | 308 | ;; step II: try coalasce 309 | 310 | ;; coalasce with previous block 311 | 312 | ;; check that we're not already the first block 313 | (if (i32.eqz (local.get $hdr)) (then)(else 314 | 315 | ;; read info about previous block 316 | (local.set $prev_ftr (i32.sub (local.get $hdr) (i32.const 4))) 317 | (local.set $prev_size (call $hdr_get_size (local.get $prev_ftr))) 318 | (local.set $prev_hdr 319 | (i32.sub (i32.sub (local.get $prev_ftr) (local.get $prev_size)) (i32.const 4)) 320 | ) 321 | 322 | ;; check if previous block is free -> merge them 323 | (if (i32.eqz (call $hdr_get_free (local.get $prev_ftr))) (then) (else 324 | (local.set $size (i32.add (i32.add (local.get $size) (local.get $prev_size)) (i32.const 8))) 325 | (call $hdr_set_size (local.get $prev_hdr) (local.get $size)) 326 | (call $hdr_set_size (local.get $ftr) (local.get $size)) 327 | 328 | ;; set current header pointer to previous header 329 | (local.set $hdr (local.get $prev_hdr)) 330 | )) 331 | )) 332 | 333 | ;; coalasce with next block 334 | 335 | (local.set $next_hdr (i32.add (local.get $ftr) (i32.const 4))) 336 | 337 | ;; check that we're not already the last block 338 | (if (i32.eq (local.get $next_hdr) (global.get $max_addr)) (then)(else 339 | 340 | ;; read info about next block 341 | (local.set $next_size (call $hdr_get_size (local.get $next_hdr))) 342 | (local.set $next_ftr 343 | (i32.add (i32.add (local.get $next_hdr) (local.get $next_size)) (i32.const 4)) 344 | ) 345 | 346 | ;; check if next block is free -> merge them 347 | (if (i32.eqz (call $hdr_get_free (local.get $next_hdr))) (then) (else 348 | (local.set $size (i32.add (i32.add (local.get $size) (local.get $next_size)) (i32.const 8))) 349 | (call $hdr_set_size (local.get $hdr) (local.get $size)) 350 | (call $hdr_set_size (local.get $next_ftr) (local.get $size)) 351 | )) 352 | 353 | )) 354 | 355 | ) 356 | ;; copy a block of memory over, from src pointer to dst pointer 357 | ;; WebAssembly seems to be planning to support memory.copy 358 | ;; until then, this function uses a loop and i32.store8/load8 359 | (func $memcpy (param $dst i32) (param $src i32) (param $n_bytes i32) 360 | (local $ptr i32) 361 | (local $offset i32) 362 | (local $data i32) 363 | (local.set $offset (i32.const 0)) 364 | 365 | loop $cpy 366 | (local.set $data (i32.load8_u (i32.add (local.get $src) (local.get $offset)))) 367 | (i32.store8 (i32.add (local.get $dst) (local.get $offset)) (local.get $data)) 368 | 369 | (local.set $offset (i32.add (local.get $offset) (i32.const 1))) 370 | (br_if $cpy (i32.lt_u (local.get $offset) (local.get $n_bytes))) 371 | end 372 | ) 373 | 374 | ;; reallocate memory to new size 375 | ;; currently does not support contraction 376 | ;; nothing will happen if n_bytes is smaller than current payload size 377 | (func $realloc (param $ptr i32) (param $n_bytes i32) (result i32) 378 | (local $hdr i32) 379 | (local $next_hdr i32) 380 | (local $next_ftr i32) 381 | (local $next_size i32) 382 | (local $ftr i32) 383 | (local $size i32) 384 | (local $rest_hdr i32) 385 | (local $rest_size i32) 386 | (local $new_ptr i32) 387 | 388 | (local.set $hdr (i32.sub (local.get $ptr) (i32.const 4))) 389 | (local.set $size (call $hdr_get_size (local.get $hdr))) 390 | 391 | (if (i32.gt_u (local.get $n_bytes) (local.get $size)) (then) (else 392 | (local.get $ptr) 393 | return 394 | )) 395 | 396 | ;; payload size is aligned to multiple of 4 397 | (local.set $n_bytes (call $align4 (local.get $n_bytes))) 398 | 399 | (local.set $next_hdr (i32.add (i32.add (local.get $hdr) (local.get $size)) (i32.const 8))) 400 | 401 | ;; Method I: try to expand the current block 402 | 403 | ;; check that we're not already the last block 404 | (if (i32.lt_u (local.get $next_hdr) (global.get $max_addr) )(then 405 | (if (call $hdr_get_free (local.get $next_hdr)) (then 406 | 407 | (local.set $next_size (call $hdr_get_size (local.get $next_hdr))) 408 | (local.set $rest_size (i32.sub 409 | (local.get $next_size) 410 | (i32.sub (local.get $n_bytes) (local.get $size)) 411 | )) 412 | (local.set $next_ftr (i32.add (i32.add (local.get $next_hdr) (local.get $next_size)) (i32.const 4))) 413 | 414 | ;; next block is big enough to be split into two 415 | (if (i32.gt_s (local.get $rest_size) (i32.const 0) ) (then 416 | 417 | (call $hdr_set_size (local.get $hdr) (local.get $n_bytes)) 418 | 419 | (local.set $ftr (i32.add (i32.add (local.get $hdr) (local.get $n_bytes) ) (i32.const 4))) 420 | (call $hdr_set_size (local.get $ftr) (local.get $n_bytes)) 421 | (call $hdr_set_free (local.get $ftr) (i32.const 0)) 422 | 423 | (local.set $rest_hdr (i32.add (local.get $ftr) (i32.const 4) )) 424 | (call $hdr_set_size (local.get $rest_hdr) (local.get $rest_size)) 425 | (call $hdr_set_free (local.get $rest_hdr) (i32.const 1)) 426 | 427 | (call $hdr_set_size (local.get $next_ftr) (local.get $rest_size)) 428 | (call $hdr_set_free (local.get $next_ftr) (i32.const 1)) 429 | 430 | (local.get $ptr) 431 | return 432 | 433 | ;; next block is not big enough to be split, but is 434 | ;; big enough to merge with the current one into one 435 | )(else (if (i32.gt_s (local.get $rest_size) (i32.const -9) ) (then 436 | 437 | (local.set $size (i32.add (i32.add (local.get $size) (i32.const 8) ) (local.get $next_size))) 438 | (call $hdr_set_size (local.get $hdr) (local.get $size)) 439 | (call $hdr_set_size (local.get $next_ftr) (local.get $size)) 440 | (call $hdr_set_free (local.get $next_ftr) (i32.const 0)) 441 | 442 | (local.get $ptr) 443 | return 444 | )))) 445 | 446 | )) 447 | )) 448 | 449 | ;; Method II: allocate a new block and copy over 450 | 451 | (local.set $new_ptr (call $malloc (local.get $n_bytes))) 452 | (call $memcpy (local.get $new_ptr) (local.get $ptr) (local.get $n_bytes)) 453 | (call $free (local.get $ptr)) 454 | (local.get $new_ptr) 455 | 456 | ) 457 | 458 | ;; exported API's 459 | (export "init" (func $init )) 460 | (export "extend" (func $extend )) 461 | (export "malloc" (func $malloc )) 462 | (export "free" (func $free )) 463 | (export "memcpy" (func $memcpy )) 464 | (export "realloc" (func $realloc)) 465 | (export "mem" (memory $mem )) 466 | ) -------------------------------------------------------------------------------- /wat/findcontours.wat: -------------------------------------------------------------------------------- 1 | ;;========================================================;; 2 | ;; CONTOUR TRACING WITH HANDWRITTEN WEBASSEMBLY ;; 3 | ;; `findcontours.wat` Lingdong Huang 2020 Public Domain ;; 4 | ;;========================================================;; 5 | ;; Finding contours in binary image ;; 6 | ;; Implements Suzuki-Abe algorithm. ;; 7 | ;; https://www.academia.edu/15495158/ ;; 8 | ;;--------------------------------------------------------;; 9 | 10 | 11 | (module 12 | 13 | (memory $mem 48) 14 | 15 | ;; datastructure 16 | ;; 17 | ;; 0--- image section - - - - - (size computed from input size) 18 | ;; | (i16) 1st pixel data 19 | ;; | (i16) 2nd pixel data 20 | ;; | ............... 21 | ;; |--- contour info table - - -(fixed size ~ max contours) 22 | ;; |--- 1st contour 23 | ;; | (i16) parent (i8) is_hole (i8) unused 24 | ;; | (i32) absolute memory offset 25 | ;; | (i32) length | 26 | ;; | (i32) unused | 27 | ;; |--- 2nd contour | 28 | ;; | ............... | 29 | ;; |--- contour data - - -|- - -(grows as needed until OOM) 30 | ;; |--- 1st contour \|/ 31 | ;; | (i32) [y0*w+x0] vertex0 32 | ;; | (i32) [y1*w+x1] vertex1 33 | ;; | (i32) [y2*w+x2] vertex2 34 | ;; | (i32) ... ... 35 | ;; |--- 2nd contour 36 | ;; | ............... 37 | ;; |---- end 38 | 39 | 40 | ;; global variables 41 | ;; some random defaults to be overwritten by setup() 42 | (global $w (mut i32) (i32.const 512 )) ;; width 43 | (global $h (mut i32) (i32.const 512 )) ;; height 44 | (global $max_contours (mut i32) (i32.const 8192 )) 45 | (global $offset_contour_info (mut i32) (i32.const 524288)) 46 | (global $offset_contour_data (mut i32) (i32.const 557056)) 47 | 48 | ;; align memory by 4 bytes (int) 49 | (func $align4 (param $x i32) (result i32) 50 | (i32.and 51 | (i32.add (local.get $x) (i32.const 3)) 52 | (i32.const -4) 53 | ) 54 | ) 55 | 56 | ;; setup: specify width, height, and maximum number of contours expected 57 | (func $setup (param $width i32) (param $height i32) (param $max_cnt i32) 58 | (global.set $w (local.get $width)) 59 | (global.set $h (local.get $height)) 60 | (global.set $max_contours (local.get $max_cnt)) 61 | (global.set $offset_contour_info (call $align4 62 | (i32.mul (i32.mul (global.get $w) (global.get $h)) (i32.const 2)) 63 | )) 64 | (global.set $offset_contour_data (i32.add 65 | (i32.mul (global.get $max_contours) (i32.const 4)) 66 | (global.get $offset_contour_info) 67 | )) 68 | 69 | ) 70 | 71 | ;; pixels are stored as signed 16-bit row-major array in memory 72 | ;; get pixel by row # and column # 73 | (func $get_ij (param $i i32) (param $j i32) (result i32) 74 | (i32.load16_s (i32.mul (i32.add 75 | (i32.mul (global.get $w) (local.get $i)) 76 | (local.get $j)) (i32.const 2) 77 | )) 78 | ) 79 | 80 | ;; set pixel by row # and column # 81 | (func $set_ij (param $i i32) (param $j i32) (param $v i32) 82 | (i32.store16 (i32.mul (i32.add 83 | (i32.mul (global.get $w) (local.get $i)) 84 | (local.get $j)) (i32.const 2) 85 | ) (local.get $v)) 86 | ) 87 | 88 | ;; get pixel by index (pre-computed from row and column) 89 | (func $get_idx (param $idx i32) (result i32) 90 | (i32.load16_s (i32.mul (local.get $idx) (i32.const 2))) 91 | ) 92 | 93 | ;; set pixel by index 94 | (func $set_idx (param $idx i32) (param $v i32) 95 | (i32.store16 (i32.mul (local.get $idx) (i32.const 2)) (local.get $v)) 96 | ) 97 | 98 | ;; get the offset of the header of the nth contour 99 | (func $get_nth_contour_info (param $n i32) (result i32) 100 | (i32.add 101 | (global.get $offset_contour_info) 102 | (i32.mul (local.get $n) (i32.const 16)) 103 | ) 104 | ) 105 | 106 | ;; set the header of the nth contour 107 | (func $set_nth_contour_info (param $n i32) 108 | (param $parent i32) 109 | (param $is_hole i32) 110 | (param $offset i32) 111 | (param $len i32) 112 | (local $o i32) 113 | (local.set $o (call $get_nth_contour_info (local.get $n))) 114 | (i32.store16 (local.get $o) (local.get $parent)) 115 | (i32.store8 (i32.add (local.get $o) (i32.const 2)) (local.get $is_hole)) 116 | (i32.store (i32.add (local.get $o) (i32.const 4)) (local.get $offset )) 117 | (i32.store (i32.add (local.get $o) (i32.const 8)) (local.get $len )) 118 | ) 119 | 120 | ;; set the length of the nth contour in the header 121 | ;; this exists in addition to set_nth_contour_info because 122 | ;; length of the contour changes dynamically unlike the other attributes 123 | (func $set_nth_contour_length (param $n i32) (param $len i32) 124 | (i32.store (i32.add 125 | (call $get_nth_contour_info (local.get $n)) 126 | (i32.const 8)) 127 | (local.get $len) 128 | ) 129 | ) 130 | 131 | ;; write vertex coordinates at pointer location 132 | ;; encoded as i*w+j 133 | (func $write_vertex (param $ptr i32) (param $i i32) (param $j i32) 134 | (i32.store (local.get $ptr) 135 | (i32.add (i32.mul (local.get $i) (global.get $w)) (local.get $j)) 136 | ) 137 | ) 138 | 139 | ;; absolute value for i32 140 | (func $abs_i32 (param $x i32) (result i32) 141 | (if (i32.lt_s (local.get $x) (i32.const 0))(then 142 | (i32.sub (i32.const 0) (local.get $x)) 143 | return 144 | )) 145 | (local.get $x) 146 | ) 147 | 148 | ;; each neighbor in the moore (8-connected) neighborhood is given an ID 149 | ;; counter-clockwise for easy access: 150 | ;; 3 2 1 151 | ;; 4 0 152 | ;; 5 6 7 153 | 154 | ;; convert aforementioned neighbor id to index (i*w+j) 155 | (func $neighbor_id2idx (param $i i32) (param $j i32) (param $id i32) (result i32) 156 | (local $ii i32) 157 | (local $jj i32) 158 | 159 | (if (i32.eqz (local.get $id)) (then 160 | (local.set $ii (local.get $i) ) 161 | (local.set $jj (i32.add (local.get $j) (i32.const 1))) 162 | 163 | )(else(if (i32.eq (local.get $id) (i32.const 1)) (then 164 | (local.set $ii (i32.sub (local.get $i) (i32.const 1))) 165 | (local.set $jj (i32.add (local.get $j) (i32.const 1))) 166 | 167 | )(else(if (i32.eq (local.get $id) (i32.const 2)) (then 168 | (local.set $ii (i32.sub (local.get $i) (i32.const 1))) 169 | (local.set $jj (local.get $j) ) 170 | 171 | )(else(if (i32.eq (local.get $id) (i32.const 3)) (then 172 | (local.set $ii (i32.sub (local.get $i) (i32.const 1))) 173 | (local.set $jj (i32.sub (local.get $j) (i32.const 1))) 174 | 175 | )(else(if (i32.eq (local.get $id) (i32.const 4)) (then 176 | (local.set $ii (local.get $i) ) 177 | (local.set $jj (i32.sub (local.get $j) (i32.const 1))) 178 | 179 | )(else(if (i32.eq (local.get $id) (i32.const 5)) (then 180 | (local.set $ii (i32.add (local.get $i) (i32.const 1))) 181 | (local.set $jj (i32.sub (local.get $j) (i32.const 1))) 182 | 183 | )(else(if (i32.eq (local.get $id) (i32.const 6)) (then 184 | (local.set $ii (i32.add (local.get $i) (i32.const 1))) 185 | (local.set $jj (local.get $j) ) 186 | 187 | )(else 188 | (local.set $ii (i32.add (local.get $i) (i32.const 1))) 189 | (local.set $jj (i32.add (local.get $j) (i32.const 1))) 190 | 191 | )))))))))))))) 192 | 193 | (i32.add 194 | (i32.mul (global.get $w) (local.get $ii)) 195 | (local.get $jj) 196 | ) 197 | ) 198 | 199 | ;; get neighbor id from relative position 200 | ;; i0,j0: current position, i,j: neighbor position 201 | (func $neighbor_idx2id (param $i0 i32) (param $j0 i32) 202 | (param $i i32) (param $j i32) (result i32) 203 | (local $di i32) 204 | (local $dj i32) 205 | (local $id i32) 206 | (local.set $di (i32.sub (local.get $i) (local.get $i0))) 207 | (local.set $dj (i32.sub (local.get $j) (local.get $j0))) 208 | 209 | (if (i32.eq (local.get $di) (i32.const -1)) (then 210 | 211 | (if (i32.eq (local.get $dj) (i32.const -1)) (then 212 | (local.set $id (i32.const 3)) 213 | )(else(if (i32.eq (local.get $dj) (i32.const 0)) (then 214 | (local.set $id (i32.const 2)) 215 | )(else 216 | (local.set $id (i32.const 1)) 217 | )))) 218 | 219 | )(else(if (i32.eq (local.get $di) (i32.const 0)) (then 220 | 221 | (if (i32.eq (local.get $dj) (i32.const -1)) (then 222 | (local.set $id (i32.const 4)) 223 | )(else 224 | (local.set $id (i32.const 0)) 225 | )) 226 | 227 | )(else 228 | 229 | (if (i32.eq (local.get $dj) (i32.const -1)) (then 230 | (local.set $id (i32.const 5)) 231 | )(else(if (i32.eq (local.get $dj) (i32.const 0)) (then 232 | (local.set $id (i32.const 6)) 233 | )(else 234 | (local.set $id (i32.const 7)) 235 | )))) 236 | 237 | )))) 238 | 239 | (local.get $id) 240 | ) 241 | 242 | ;; find first non-0 pixel in the neighborhood, clockwise or counter-clockwise as specified 243 | ;; i0,j0: current position 244 | ;; i,j: position of the first pixel to start searching from 245 | ;; offset: index offset from the first pixel (e.g. 1 -> start from the second pixel) 246 | ;; cwccw : -1 for clockwise, +1 for counter-clockwise 247 | ;; returns computed index if found, -1 if not found 248 | (func $rot_non0 (param $i0 i32) (param $j0 i32) 249 | (param $i i32) (param $j i32) (param $offset i32) (param $cwccw i32) 250 | (result i32) 251 | (local $id i32) 252 | (local $k i32) 253 | (local $kk i32) 254 | (local $ij i32) 255 | 256 | (local.set $id (call $neighbor_idx2id (local.get $i0) (local.get $j0) (local.get $i) (local.get $j) )) 257 | 258 | (local.set $k (i32.const 0)) 259 | loop $l0 260 | (local.set $kk 261 | (i32.rem_u 262 | (i32.add 263 | (i32.add 264 | (i32.add (i32.mul (local.get $k) (local.get $cwccw)) (local.get $id) ) 265 | (local.get $offset) 266 | ) 267 | (i32.const 16) 268 | ) 269 | (i32.const 8) 270 | ) 271 | ) 272 | (local.set $ij 273 | (call $neighbor_id2idx (local.get $i0) (local.get $j0) (local.get $kk)) 274 | ) 275 | (if (i32.eqz (call $get_idx (local.get $ij)) ) (then 276 | ;;pass 277 | )(else 278 | (local.get $ij) 279 | return 280 | )) 281 | 282 | (local.set $k (i32.add (local.get $k) (i32.const 1))) 283 | 284 | (br_if $l0 (i32.lt_u (local.get $k) (i32.const 8))) 285 | end 286 | 287 | (i32.const -1) 288 | ) 289 | 290 | ;; main function: find contours in the current image 291 | ;; before calling: 292 | ;; - use setup() for initialization 293 | ;; - use set_ij() for setting pixels of the image 294 | ;; returns the number of contours found 295 | (func $find_contours (result i32) 296 | ;; Topological Structural Analysis of Digitized Binary Images by Border Following. 297 | ;; Suzuki, S. and Abe, K., CVGIP 30 1, pp 32-46 (1985) 298 | 299 | ;; variables from the paper 300 | (local $nbd i32) 301 | (local $lnbd i32) 302 | (local $i1 i32) 303 | (local $j1 i32) 304 | (local $i1j1 i32) ;; = i1*w+j1 305 | (local $i2 i32) 306 | (local $j2 i32) 307 | (local $i3 i32) 308 | (local $j3 i32) 309 | (local $i4 i32) 310 | (local $j4 i32) 311 | (local $i4j4 i32) ;; = i4*w+j4 312 | (local $i i32) 313 | (local $j i32) 314 | 315 | ;; temporary computation results 316 | (local $now i32) ;; value of current pixel 317 | (local $data_ptr i32) ;; pointer at which to write the next vertex 318 | (local $n_vtx i32) ;; number of vertices in current contour 319 | (local $is_hole i32) ;; is current contour a hole 320 | (local $parent i32) ;; parent id of current contour 321 | (local $b0 i32) ;; pointer to header of 'lnbd' contour 322 | (local $b0_is_hole i32) ;; is 'lnbd' contour a hole 323 | (local $b0_parent i32) ;; parent id of 'lnbd' contour 324 | 325 | (local.set $nbd (i32.const 1)) 326 | 327 | (local.set $data_ptr (global.get $offset_contour_data)) 328 | (local.set $n_vtx (i32.const 0)) 329 | 330 | ;; Scan the picture with a TV raster and perform the following steps 331 | ;; for each pixel such that fij # 0. Every time we begin to scan a 332 | ;; new row of the picture, reset LNBD to 1. 333 | (local.set $i (i32.const 0)) 334 | loop $li 335 | (local.set $i (i32.add (local.get $i) (i32.const 1))) 336 | (if (i32.gt_u (local.get $i) (i32.sub (global.get $h) (i32.const 2))) (then 337 | (i32.sub (local.get $nbd) (i32.const 1)) 338 | return 339 | )) 340 | 341 | (local.set $lnbd (i32.const 1)) 342 | (local.set $j (i32.const 0)) 343 | 344 | loop $lj 345 | (local.set $j (i32.add (local.get $j) (i32.const 1))) 346 | (br_if $li (i32.gt_u (local.get $j) (i32.sub (global.get $w) (i32.const 2)))) 347 | 348 | (local.set $i2 (i32.const 0)) 349 | (local.set $j2 (i32.const 0)) 350 | 351 | (local.set $now (call $get_ij (local.get $i) (local.get $j))) 352 | 353 | (br_if $lj (i32.eqz (local.get $now))) 354 | 355 | ;; (a) If fij = 1 and fi, j-1 = 0, then decide that the pixel 356 | ;; (i, j) is the border following starting point of an outer 357 | ;; border, increment NBD, and (i,, j,) + (i, j - 1). 358 | (if (i32.and 359 | (i32.eq (local.get $now) (i32.const 1) ) 360 | (i32.eqz (call $get_ij 361 | (local.get $i) (i32.sub (local.get $j) (i32.const 1)))) 362 | )(then 363 | 364 | (local.set $nbd (i32.add (local.get $nbd) (i32.const 1))) 365 | 366 | (local.set $i2 (local.get $i)) 367 | (local.set $j2 (i32.sub (local.get $j) (i32.const 1))) 368 | 369 | ;; (b) Else if fij 2 1 and fi,j+l = 0, then decide that the 370 | ;; pixel (i, j) is the border following starting point of a 371 | ;; hole border, increment NBD, (iz, j,) * (i, j + l), and 372 | ;; LNBD + fij in casefij > 1. 373 | )(else(if (i32.and 374 | (i32.gt_s (local.get $now) (i32.const 0) ) 375 | (i32.eqz (call $get_ij 376 | (local.get $i) (i32.add (local.get $j) (i32.const 1)))) 377 | )(then 378 | 379 | (local.set $nbd (i32.add (local.get $nbd) (i32.const 1))) 380 | 381 | (local.set $i2 (local.get $i)) 382 | (local.set $j2 (i32.add (local.get $j) (i32.const 1))) 383 | 384 | (if (i32.gt_s (local.get $now) (i32.const 1)) (then 385 | (local.set $lnbd (local.get $now)) 386 | )) 387 | 388 | )(else 389 | ;; (c) Otherwise, go to (4). 390 | (if (i32.ne (local.get $now) (i32.const 1)) (then 391 | (local.set $lnbd (call $abs_i32 (local.get $now))) 392 | )) 393 | (br $lj) 394 | )))) 395 | ;; (2) Depending on the types of the newly found border 396 | ;; and the border with the sequential number LNBD 397 | ;; (i.e., the last border met on the current row), 398 | ;; decide the parent of the current border as shown in Table 1. 399 | ;; TABLE 1 400 | ;; Decision Rule for the Parent Border of the Newly Found Border B 401 | ;; ---------------------------------------------------------------- 402 | ;; Type of border B' 403 | ;; \ with the sequential 404 | ;; \ number LNBD 405 | ;; Type of B \ Outer border Hole border 406 | ;; --------------------------------------------------------------- 407 | ;; Outer border The parent border The border B' 408 | ;; of the border B' 409 | ;; 410 | ;; Hole border The border B' The parent border 411 | ;; of the border B' 412 | ;; ---------------------------------------------------------------- 413 | 414 | (local.set $is_hole (i32.eq (local.get $j2) (i32.add (local.get $j) (i32.const 1)))) 415 | (local.set $parent (i32.const -1)) 416 | (if (i32.gt_s (local.get $lnbd) (i32.const 1)) (then 417 | (local.set $b0 418 | (call $get_nth_contour_info (i32.sub (local.get $lnbd) (i32.const 2))) 419 | ) 420 | (local.set $b0_parent (i32.load16_u (local.get $b0))) 421 | (local.set $b0_is_hole (i32.load8_u (i32.add (local.get $b0) (i32.const 2)))) 422 | 423 | (if (local.get $b0_is_hole) (then 424 | (if (local.get $is_hole) (then 425 | (local.set $parent (local.get $b0_parent)) 426 | )(else 427 | (local.set $parent (i32.sub (local.get $lnbd) (i32.const 2))) 428 | )) 429 | )(else 430 | (if (local.get $is_hole) (then 431 | (local.set $parent (i32.sub (local.get $lnbd) (i32.const 2))) 432 | )(else 433 | (local.set $parent (local.get $b0_parent)) 434 | )) 435 | )) 436 | )) 437 | (call $set_nth_contour_info (i32.sub (local.get $nbd) (i32.const 2)) 438 | (local.get $parent) 439 | (local.get $is_hole) 440 | (local.get $data_ptr) 441 | (i32.const 1) 442 | ) 443 | 444 | (call $write_vertex (local.get $data_ptr) (local.get $i) (local.get $j)) 445 | (local.set $data_ptr (i32.add (local.get $data_ptr) (i32.const 4))) 446 | (local.set $n_vtx (i32.const 1)) 447 | 448 | ;; (3) From the starting point (i, j), follow the detected border: 449 | ;; this is done by the following substeps (3.1) through (3.5). 450 | 451 | ;; (3.1) Starting from (iz, jz), look around clockwise the pixels 452 | ;; in the neigh- borhood of (i, j) and tind a nonzero pixel. 453 | ;; Let (i,, j,) be the first found nonzero pixel. If no nonzero 454 | ;; pixel is found, assign -NBD to fij and go to (4). 455 | 456 | (local.set $i1 (i32.const -1)) 457 | (local.set $j1 (i32.const -1)) 458 | 459 | (local.set $i1j1 (call $rot_non0 460 | (local.get $i) (local.get $j) 461 | (local.get $i2) (local.get $j2) 462 | (i32.const 0) (i32.const -1) 463 | )) 464 | 465 | (if (i32.eq (local.get $i1j1) (i32.const -1)) (then 466 | (local.set $now (i32.sub (i32.const 0) (local.get $nbd))) 467 | (call $set_ij (local.get $i) (local.get $j) (local.get $now)) 468 | 469 | ;; go to (4) 470 | (if (i32.ne (local.get $now) (i32.const 1)) (then 471 | (local.set $lnbd (call $abs_i32 (local.get $now))) 472 | )) 473 | (br $lj) 474 | 475 | )) 476 | (local.set $i1 (i32.div_u (local.get $i1j1) (global.get $w))) 477 | (local.set $j1 (i32.rem_u (local.get $i1j1) (global.get $w))) 478 | 479 | ;; (3.2) &, j,) + (il, j,) ad (is,jd + (4 j). 480 | (local.set $i2 (local.get $i1)) 481 | (local.set $j2 (local.get $j1)) 482 | (local.set $i3 (local.get $i )) 483 | (local.set $j3 (local.get $j )) 484 | 485 | 486 | loop $while 487 | (local.set $i4j4 (call $rot_non0 488 | (local.get $i3) (local.get $j3) 489 | (local.get $i2) (local.get $j2) 490 | (i32.const 1) (i32.const 1) 491 | )) 492 | (local.set $i4 (i32.div_u (local.get $i4j4) (global.get $w))) 493 | (local.set $j4 (i32.rem_u (local.get $i4j4) (global.get $w))) 494 | 495 | 496 | (call $write_vertex (local.get $data_ptr) (local.get $i4) (local.get $j4)) 497 | (local.set $data_ptr (i32.add (local.get $data_ptr) (i32.const 4))) 498 | (local.set $n_vtx (i32.add (local.get $n_vtx) (i32.const 1))) 499 | (call $set_nth_contour_length 500 | (i32.sub (local.get $nbd) (i32.const 2)) 501 | (local.get $n_vtx) 502 | ) 503 | ;; (a) If the pixel (i3, j, + 1) is a O-pixel examined in the 504 | ;; substep (3.3) then fi,, j3 + - NBD. 505 | (if (i32.eqz 506 | (call $get_ij (local.get $i3) (i32.add (local.get $j3) (i32.const 1))) 507 | )(then 508 | (call $set_ij (local.get $i3) (local.get $j3) 509 | (i32.sub (i32.const 0) (local.get $nbd)) 510 | ) 511 | ;; (b) If the pixel (i3, j, + 1) is not a O-pixel examined 512 | ;; in the substep (3.3) and fi,,j, = 1, then fi,,j, + NBD. 513 | )(else(if (i32.eq 514 | (call $get_ij (local.get $i3) (local.get $j3)) 515 | (i32.const 1) 516 | )(then 517 | (call $set_ij (local.get $i3) (local.get $j3) (local.get $nbd)) 518 | )))) 519 | ;; (c) Otherwise, do not changefi,, jj. 520 | 521 | ;; (3.5) If (i4, j,) = (i, j) and (i3, j,) = (iI, j,) 522 | ;; (coming back to the starting point), then go to (4); 523 | (if (i32.and(i32.and(i32.and 524 | (i32.eq (local.get $i4) (local.get $i )) 525 | (i32.eq (local.get $j4) (local.get $j ))) 526 | (i32.eq (local.get $i3) (local.get $i1))) 527 | (i32.eq (local.get $j3) (local.get $j1))) 528 | (then 529 | (if (i32.ne (call $get_ij (local.get $i) (local.get $j)) (i32.const 1)) 530 | (then 531 | (local.set $lnbd (call $abs_i32 (local.get $now))) 532 | )) 533 | ;; otherwise, (i2, j,) + (i3, j,),(i,, j,) + (i4, j,), 534 | ;; and go back to (3.3). 535 | )(else 536 | (local.set $i2 (local.get $i3)) 537 | (local.set $j2 (local.get $j3)) 538 | (local.set $i3 (local.get $i4)) 539 | (local.set $j3 (local.get $j4)) 540 | (br $while) 541 | )) 542 | end 543 | 544 | (br_if $lj (i32.lt_u (local.get $j) (i32.sub (global.get $w) (i32.const 2)))) 545 | end 546 | 547 | (br_if $li (i32.lt_u (local.get $i) (i32.sub (global.get $h) (i32.const 2)))) 548 | end 549 | 550 | ;; return 551 | (i32.sub (local.get $nbd) (i32.const 1)) 552 | ) 553 | 554 | ;; user-facing output-reading 555 | (func $get_nth_contour_parent (param $n i32) (result i32) 556 | (i32.load16_s (call $get_nth_contour_info (local.get $n))) 557 | ) 558 | (func $get_nth_contour_is_hole (param $n i32) (result i32) 559 | (i32.load8_u (i32.add (call $get_nth_contour_info (local.get $n)) (i32.const 2))) 560 | ) 561 | (func $get_nth_contour_offset (param $n i32) (result i32) 562 | (i32.load (i32.add (call $get_nth_contour_info (local.get $n)) (i32.const 4))) 563 | ) 564 | (func $get_nth_contour_length (param $n i32) (result i32) 565 | (i32.load (i32.add (call $get_nth_contour_info (local.get $n)) (i32.const 8))) 566 | ) 567 | (func $get_nth_vertex (param $offset i32) (param $n i32) (result i32) 568 | (i32.load (i32.add 569 | (local.get $offset) 570 | (i32.mul (local.get $n) (i32.const 4)) 571 | )) 572 | ) 573 | 574 | ;; exported API's 575 | (export "get_ij" (func $get_ij )) 576 | (export "set_ij" (func $set_ij )) 577 | (export "setup" (func $setup )) 578 | (export "find_contours" (func $find_contours )) 579 | (export "get_nth_contour_parent" (func $get_nth_contour_parent )) 580 | (export "get_nth_contour_is_hole" (func $get_nth_contour_is_hole)) 581 | (export "get_nth_contour_offset" (func $get_nth_contour_offset )) 582 | (export "get_nth_contour_length" (func $get_nth_contour_length )) 583 | (export "get_nth_vertex" (func $get_nth_vertex )) 584 | (export "mem" (memory $mem )) 585 | 586 | ) 587 | 588 | -------------------------------------------------------------------------------- /wat/random.wat: -------------------------------------------------------------------------------- 1 | ;;========================================================;; 2 | ;; NOISES AND RANDOMNESS IN HANDWRITTEN WEBASSEMBLY ;; 3 | ;; `random.wat` Lingdong Huang 2020 Public Domain ;; 4 | ;;========================================================;; 5 | ;; Uniform, Perlin, Gaussian, Exponential Randomness ;; 6 | ;;--------------------------------------------------------;; 7 | 8 | (module 9 | 10 | (memory $mem 1) ;; 64K 11 | 12 | ;; MEMORY LAYOUT (total 20,992 bytes) 13 | ;; ----------------------------------------- 14 | ;; 4608 bytes | ziggurat lookup tables 15 | ;; -> i32 kn[128], ke[256] 16 | ;; -> f32 wn[128], fn[128],we[256],fe[256] 17 | ;; ----------------------------------------- 18 | ;; 16384 bytes | perlin lookup table 19 | ;; -> f32 [4096] 20 | ;; ----------------------------------------- 21 | 22 | ;; perlin noise constants 23 | (global $PERLIN_YWRAPB i32 (i32.const 4 )) 24 | (global $PERLIN_YWRAP i32 (i32.const 16 )) ;; 1< OOM -> extend heap 327 | (if (i32.eqz (local.get $ptr))(then 328 | ;; compute # of pages from # of bytes, rounding up 329 | (local.set $n_pages 330 | (i32.div_u 331 | (i32.add (local.get $n_bytes) (i32.const 65527) ) 332 | (i32.const 65528) 333 | ) 334 | ) 335 | (call $extend (local.get $n_pages)) 336 | 337 | ;; try again 338 | (local.set $ptr (call $find (local.get $n_bytes)) ) 339 | )) 340 | (local.get $ptr) 341 | ) 342 | 343 | ;; free - free an allocated block given a pointer to it 344 | (func $free (param $ptr i32) 345 | (local $hdr i32) 346 | (local $ftr i32) 347 | (local $size i32) 348 | (local $prev_hdr i32) 349 | (local $prev_ftr i32) 350 | (local $prev_size i32) 351 | (local $prev_free i32) 352 | (local $next_hdr i32) 353 | (local $next_ftr i32) 354 | (local $next_size i32) 355 | (local $next_free i32) 356 | 357 | ;; step I: mark the block as free 358 | 359 | (local.set $hdr (i32.sub (local.get $ptr) (i32.const 4))) 360 | (local.set $size (call $hdr_get_size (local.get $hdr))) 361 | (local.set $ftr (i32.add (i32.add (local.get $hdr) (local.get $size)) (i32.const 4))) 362 | 363 | (call $hdr_set_free (local.get $hdr) (i32.const 1)) 364 | (call $hdr_set_free (local.get $ftr) (i32.const 1)) 365 | 366 | ;; step II: try coalasce 367 | 368 | ;; coalasce with previous block 369 | 370 | ;; check that we're not already the first block 371 | (if (i32.eqz (local.get $hdr)) (then)(else 372 | 373 | ;; read info about previous block 374 | (local.set $prev_ftr (i32.sub (local.get $hdr) (i32.const 4))) 375 | (local.set $prev_size (call $hdr_get_size (local.get $prev_ftr))) 376 | (local.set $prev_hdr 377 | (i32.sub (i32.sub (local.get $prev_ftr) (local.get $prev_size)) (i32.const 4)) 378 | ) 379 | 380 | ;; check if previous block is free -> merge them 381 | (if (i32.eqz (call $hdr_get_free (local.get $prev_ftr))) (then) (else 382 | (local.set $size (i32.add (i32.add (local.get $size) (local.get $prev_size)) (i32.const 8))) 383 | (call $hdr_set_size (local.get $prev_hdr) (local.get $size)) 384 | (call $hdr_set_size (local.get $ftr) (local.get $size)) 385 | 386 | ;; set current header pointer to previous header 387 | (local.set $hdr (local.get $prev_hdr)) 388 | )) 389 | )) 390 | 391 | ;; coalasce with next block 392 | 393 | (local.set $next_hdr (i32.add (local.get $ftr) (i32.const 4))) 394 | 395 | ;; check that we're not already the last block 396 | (if (i32.eq (local.get $next_hdr) (global.get $max_addr)) (then)(else 397 | 398 | ;; read info about next block 399 | (local.set $next_size (call $hdr_get_size (local.get $next_hdr))) 400 | (local.set $next_ftr 401 | (i32.add (i32.add (local.get $next_hdr) (local.get $next_size)) (i32.const 4)) 402 | ) 403 | 404 | ;; check if next block is free -> merge them 405 | (if (i32.eqz (call $hdr_get_free (local.get $next_hdr))) (then) (else 406 | (local.set $size (i32.add (i32.add (local.get $size) (local.get $next_size)) (i32.const 8))) 407 | (call $hdr_set_size (local.get $hdr) (local.get $size)) 408 | (call $hdr_set_size (local.get $next_ftr) (local.get $size)) 409 | )) 410 | 411 | )) 412 | 413 | ) 414 | 415 | ;;========================================================;; 416 | ;; SKELETONIZATION WITH HANDWRITTEN WEBASSEMBLY ;; 417 | ;;========================================================;; 418 | ;; Binary image thinning (skeletonization) in-place. ;; 419 | ;; Implements Zhang-Suen algorithm. ;; 420 | ;; http://agcggs680.pbworks.com/f/Zhan-Suen_algorithm.pdf ;; 421 | ;;--------------------------------------------------------;; 422 | 423 | ;; pixels are stored as 8-bit row-major array in memory 424 | ;; reading a pixel: mem[i=y*w+x] 425 | (func $im_get (param $x i32) (param $y i32) (result i32) 426 | (i32.load8_u (i32.add (global.get $im_ptr) (i32.add 427 | (i32.mul (global.get $W) (local.get $y)) 428 | (local.get $x) 429 | ))) 430 | ) 431 | 432 | ;; writing a pixel: mem[i=y*w+x]=v 433 | (func $im_set (param $x i32) (param $y i32) (param $v i32) 434 | (i32.store8 (i32.add (global.get $im_ptr) (i32.add 435 | (i32.mul (global.get $W) (local.get $y)) 436 | (local.get $x) 437 | )) (local.get $v)) 438 | ) 439 | 440 | ;; one iteration of the thinning algorithm 441 | ;; w: width, h: height, iter: 0=even-subiteration, 1=odd-subiteration 442 | ;; returns 0 if no further thinning possible (finished), 1 otherwise 443 | (func $thinning_zs_iteration (param $iter i32) (result i32) 444 | ;; local variable declarations 445 | ;; iterators 446 | (local $i i32) (local $j i32) 447 | ;; pixel Moore neighborhood 448 | (local $p2 i32) (local $p3 i32) (local $p4 i32) (local $p5 i32) 449 | (local $p6 i32) (local $p7 i32) (local $p8 i32) (local $p9 i32) 450 | ;; temporary computation results 451 | (local $A i32) (local $B i32) 452 | (local $m1 i32) (local $m2 i32) 453 | ;; bools for updating image and determining stop condition 454 | (local $diff i32) 455 | (local $mark i32) 456 | (local $neu i32) 457 | (local $old i32) 458 | 459 | (local.set $diff (i32.const 0)) 460 | 461 | ;; raster scan the image (loop over every pixel) 462 | 463 | ;; for (i = 1; i < h-1; i++) 464 | (local.set $i (i32.const 1)) 465 | loop $loop_i 466 | 467 | ;; for (j = 1; j < w-1; j++) 468 | (local.set $j (i32.const 1)) 469 | loop $loop_j 470 | 471 | ;; pixel's Moore (8-connected) neighborhood: 472 | 473 | ;; p9 p2 p3 474 | ;; p8 p4 475 | ;; p7 p6 p5 476 | 477 | (local.set $p2 (i32.and (call $im_get 478 | (local.get $j) 479 | (i32.sub (local.get $i) (i32.const 1)) 480 | ) (i32.const 1) )) 481 | 482 | (local.set $p3 (i32.and (call $im_get 483 | (i32.add (local.get $j) (i32.const 1)) 484 | (i32.sub (local.get $i) (i32.const 1)) 485 | ) (i32.const 1) )) 486 | 487 | (local.set $p4 (i32.and (call $im_get 488 | (i32.add (local.get $j) (i32.const 1)) 489 | (local.get $i) 490 | ) (i32.const 1) )) 491 | 492 | (local.set $p5 (i32.and (call $im_get 493 | (i32.add (local.get $j) (i32.const 1)) 494 | (i32.add (local.get $i) (i32.const 1)) 495 | ) (i32.const 1) )) 496 | 497 | (local.set $p6 (i32.and (call $im_get 498 | (local.get $j) 499 | (i32.add (local.get $i) (i32.const 1)) 500 | ) (i32.const 1) )) 501 | 502 | (local.set $p7 (i32.and (call $im_get 503 | (i32.sub (local.get $j) (i32.const 1)) 504 | (i32.add (local.get $i) (i32.const 1)) 505 | ) (i32.const 1) )) 506 | 507 | (local.set $p8 (i32.and (call $im_get 508 | (i32.sub (local.get $j) (i32.const 1)) 509 | (local.get $i) 510 | ) (i32.const 1) )) 511 | 512 | (local.set $p9 (i32.and (call $im_get 513 | (i32.sub (local.get $j) (i32.const 1)) 514 | (i32.sub (local.get $i) (i32.const 1)) 515 | ) (i32.const 1) )) 516 | 517 | ;; A is the number of 01 patterns in the ordered set p2,p3,p4,...p8,p9 518 | (local.set $A (i32.add (i32.add( i32.add (i32.add( i32.add( i32.add( i32.add 519 | (i32.and (i32.eqz (local.get $p2)) (i32.eq (local.get $p3) (i32.const 1))) 520 | (i32.and (i32.eqz (local.get $p3)) (i32.eq (local.get $p4) (i32.const 1)))) 521 | (i32.and (i32.eqz (local.get $p4)) (i32.eq (local.get $p5) (i32.const 1)))) 522 | (i32.and (i32.eqz (local.get $p5)) (i32.eq (local.get $p6) (i32.const 1)))) 523 | (i32.and (i32.eqz (local.get $p6)) (i32.eq (local.get $p7) (i32.const 1)))) 524 | (i32.and (i32.eqz (local.get $p7)) (i32.eq (local.get $p8) (i32.const 1)))) 525 | (i32.and (i32.eqz (local.get $p8)) (i32.eq (local.get $p9) (i32.const 1)))) 526 | (i32.and (i32.eqz (local.get $p9)) (i32.eq (local.get $p2) (i32.const 1)))) 527 | ) 528 | ;; B = p2 + p3 + p4 + ... + p8 + p9 529 | (local.set $B (i32.add (i32.add( i32.add 530 | (i32.add (local.get $p2) (local.get $p3)) 531 | (i32.add (local.get $p4) (local.get $p5))) 532 | (i32.add (local.get $p6) (local.get $p7))) 533 | (i32.add (local.get $p8) (local.get $p9))) 534 | ) 535 | 536 | (if (i32.eqz (local.get $iter)) (then 537 | ;; first subiteration, m1 = p2*p4*p6, m2 = p4*p6*p8 538 | (local.set $m1 (i32.mul(i32.mul (local.get $p2) (local.get $p4)) (local.get $p6))) 539 | (local.set $m2 (i32.mul(i32.mul (local.get $p4) (local.get $p6)) (local.get $p8))) 540 | )(else 541 | ;; second subiteration, m1 = p2*p4*p8, m2 = p2*p6*p8 542 | (local.set $m1 (i32.mul(i32.mul (local.get $p2) (local.get $p4)) (local.get $p8))) 543 | (local.set $m2 (i32.mul(i32.mul (local.get $p2) (local.get $p6)) (local.get $p8))) 544 | )) 545 | 546 | ;; the contour point is deleted if it satisfies the following conditions: 547 | ;; A == 1 && 2 <= B <= 6 && m1 == 0 && m2 == 0 548 | (if (i32.and(i32.and(i32.and(i32.and 549 | (i32.eq (local.get $A) (i32.const 1)) 550 | (i32.lt_u (i32.const 1) (local.get $B))) 551 | (i32.lt_u (local.get $B) (i32.const 7))) 552 | (i32.eqz (local.get $m1))) 553 | (i32.eqz (local.get $m2))) 554 | (then 555 | ;; we cannot erase the pixel directly because computation for neighboring pixels 556 | ;; depends on the current state of this pixel. And instead of using 2 matrices, 557 | ;; we do a |= 2 to set the second LSB to denote a to-be-erased pixel 558 | (call $im_set (local.get $j) (local.get $i) 559 | (i32.or 560 | (call $im_get (local.get $j) (local.get $i)) 561 | (i32.const 2) 562 | ) 563 | ) 564 | )(else)) 565 | 566 | ;; increment loopers 567 | (local.set $j (i32.add (local.get $j) (i32.const 1))) 568 | (br_if $loop_j (i32.lt_u (local.get $j) (i32.sub (global.get $W) (i32.const 1))) ) 569 | end 570 | (local.set $i (i32.add (local.get $i) (i32.const 1))) 571 | (br_if $loop_i (i32.lt_u (local.get $i) (i32.sub (global.get $H) (i32.const 1)))) 572 | end 573 | 574 | ;; for (i = 1; i < h; i++) 575 | (local.set $i (i32.const 0)) 576 | loop $loop_i2 577 | 578 | ;; for (j = 0; j < w; j++) 579 | (local.set $j (i32.const 0)) 580 | loop $loop_j2 581 | ;; bit-twiddling to retrive the new image stored in the second LSB 582 | ;; and check if the image has changed 583 | ;; mark = mem[i,j] >> 1 584 | ;; old = mem[i,j] & 1 585 | ;; mem[i,j] = old & (!marker) 586 | (local.set $neu (call $im_get (local.get $j) (local.get $i))) 587 | (local.set $mark (i32.shr_u (local.get $neu) (i32.const 1))) 588 | (local.set $old (i32.and (local.get $neu) (i32.const 1))) 589 | (local.set $neu (i32.and (local.get $old) (i32.eqz (local.get $mark)))) 590 | 591 | (call $im_set (local.get $j) (local.get $i) (local.get $neu)) 592 | 593 | ;; image has changed, tell caller function that we will need more iterations 594 | (if (i32.ne (local.get $neu) (local.get $old)) (then 595 | (local.set $diff (i32.const 1)) 596 | )) 597 | 598 | (local.set $j (i32.add (local.get $j) (i32.const 1))) 599 | (br_if $loop_j2 (i32.lt_u (local.get $j) (global.get $W))) 600 | end 601 | (local.set $i (i32.add (local.get $i) (i32.const 1))) 602 | (br_if $loop_i2 (i32.lt_u (local.get $i) (global.get $H))) 603 | end 604 | 605 | ;; return 606 | (local.get $diff) 607 | ) 608 | 609 | ;; main thinning routine 610 | ;; run thinning iteration until done 611 | ;; w: width, h:height 612 | (func $thinning_zs 613 | (local $diff i32) 614 | (local.set $diff (i32.const 1)) 615 | loop $l0 616 | ;; even subiteration 617 | (local.set $diff (i32.and 618 | (local.get $diff) 619 | (call $thinning_zs_iteration (i32.const 0)) 620 | )) 621 | ;; odd subiteration 622 | (local.set $diff (i32.and 623 | (local.get $diff) 624 | (call $thinning_zs_iteration (i32.const 1)) 625 | )) 626 | ;; no change -> done! 627 | (br_if $l0 (i32.eq (local.get $diff) (i32.const 1))) 628 | end 629 | ) 630 | 631 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 632 | ;; ;; 633 | ;; ;; 634 | ;; DATASTRUCTURE IMPLEMENTATION ;; 635 | ;; ;; 636 | ;; ;; 637 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 638 | 639 | ;; pl_* polyline/polylines 640 | ;; pt_* point/points 641 | 642 | ;; linked lists are used for fast insert/delete/concat 643 | ;; (which are very frequent in this algirthm, while 644 | ;; random access is never encountered in the algorithm) 645 | 646 | ;; ----------------------------------- 647 | 648 | ;; struct readers and writers 649 | ;; WebAssembly does not have support for structs, 650 | ;; so the following getters and setters help read 651 | ;; data of correct sizes at correct offsets 652 | 653 | ;; POLYLINE(S) 654 | ;; 655 | ;; struct pl{ 656 | ;; pt* head; // (i32) first point in polyline 657 | ;; pt* tail; // (i32) last point in polyline 658 | ;; pl* next; // (i32) next polyline 659 | ;; pl* prev; // (i32) previous polyline 660 | ;; int size; // (i32) number of points in polyline 661 | ;; } 662 | (func $pl_get_head (param $q i32) (result i32) 663 | (i32.load (local.get $q)) 664 | ) 665 | (func $pl_set_head (param $q i32) (param $v i32) 666 | (i32.store (local.get $q) (local.get $v)) 667 | ) 668 | (func $pl_get_tail (param $q i32) (result i32) 669 | (i32.load (i32.add (local.get $q) (i32.const 4))) 670 | ) 671 | (func $pl_set_tail (param $q i32) (param $v i32) 672 | (i32.store (i32.add (local.get $q) (i32.const 4)) (local.get $v)) 673 | ) 674 | (func $pl_get_next (param $q i32) (result i32) 675 | (i32.load (i32.add (local.get $q) (i32.const 8))) 676 | ) 677 | (func $pl_set_next (param $q i32) (param $v i32) 678 | (i32.store (i32.add (local.get $q) (i32.const 8)) (local.get $v)) 679 | ) 680 | (func $pl_get_prev (param $q i32) (result i32) 681 | (i32.load (i32.add (local.get $q) (i32.const 12))) 682 | ) 683 | (func $pl_set_prev (param $q i32) (param $v i32) 684 | (i32.store (i32.add (local.get $q) (i32.const 12)) (local.get $v)) 685 | ) 686 | (func $pl_get_size (param $q i32) (result i32) 687 | (i32.load (i32.add (local.get $q) (i32.const 16))) 688 | ) 689 | (func $pl_set_size (param $q i32) (param $v i32) 690 | (i32.store (i32.add (local.get $q) (i32.const 16)) (local.get $v)) 691 | ) 692 | 693 | ;; POINT(S) 694 | ;; 695 | ;; struct pl{ 696 | ;; int x; // (i32) y coordinate 697 | ;; int y; // (i32) x coordinate 698 | ;; pt* next; // (i32) next point 699 | ;; } 700 | (func $pt_get_x (param $q i32) (result i32) 701 | (i32.load (local.get $q)) 702 | ) 703 | (func $pt_set_x (param $q i32) (param $v i32) 704 | (i32.store (local.get $q) (local.get $v)) 705 | ) 706 | (func $pt_get_y (param $q i32) (result i32) 707 | (i32.load (i32.add (local.get $q) (i32.const 4))) 708 | ) 709 | (func $pt_set_y (param $q i32) (param $v i32) 710 | (i32.store (i32.add (local.get $q) (i32.const 4)) (local.get $v)) 711 | ) 712 | (func $pt_get_next (param $q i32) (result i32) 713 | (i32.load (i32.add (local.get $q) (i32.const 8))) 714 | ) 715 | (func $pt_set_next (param $q i32) (param $v i32) 716 | (i32.store (i32.add (local.get $q) (i32.const 8)) (local.get $v)) 717 | ) 718 | 719 | ;; create a new polyline 720 | ;; returns a pointer 721 | (func $pl_new (result i32) 722 | (local $q i32) 723 | (local.set $q (call $malloc (i32.const 20))) 724 | (call $pl_set_head (local.get $q) (i32.const 0)) 725 | (call $pl_set_tail (local.get $q) (i32.const 0)) 726 | (call $pl_set_prev (local.get $q) (i32.const 0)) 727 | (call $pl_set_next (local.get $q) (i32.const 0)) 728 | (call $pl_set_size (local.get $q) (i32.const 0)) 729 | (local.get $q) 730 | ) 731 | 732 | ;; reverse a polyline (in-place, one pass) 733 | (func $pl_rev (param $q i32) 734 | (local $size i32) 735 | (local $it0 i32) 736 | (local $it1 i32) 737 | (local $it2 i32) 738 | (local $i i32) 739 | (local $q_head i32) 740 | 741 | (if (i32.eqz (local.get $q)) (then 742 | return 743 | )) 744 | (local.set $size (call $pl_get_size (local.get $q))) 745 | (if (i32.lt_u (local.get $size) (i32.const 2)) (then 746 | return 747 | )) 748 | (call $pl_set_next (call $pl_get_tail (local.get $q)) (call $pl_get_head (local.get $q)) ) 749 | 750 | (local.set $it0 (call $pl_get_head (local.get $q))) 751 | (local.set $it1 (call $pt_get_next (local.get $it0))) 752 | (local.set $it2 (call $pt_get_next (local.get $it1))) 753 | 754 | (local.set $i (i32.const 0)) 755 | loop $pl_rev_loop0 756 | (call $pt_set_next (local.get $it1) (local.get $it0)) 757 | (local.set $it0 (local.get $it1)) 758 | (local.set $it1 (local.get $it2)) 759 | (local.set $it2 (call $pt_get_next (local.get $it2))) 760 | (local.set $i (i32.add (local.get $i) (i32.const 1))) 761 | (br_if $pl_rev_loop0 (i32.lt_u (local.get $i) (i32.sub (local.get $size) (i32.const 1)))) 762 | end 763 | 764 | (local.set $q_head (call $pl_get_head (local.get $q))) 765 | (call $pl_set_head (local.get $q) (call $pl_get_tail (local.get $q))) 766 | (call $pl_set_tail (local.get $q) (local.get $q_head)) 767 | (call $pl_set_next (call $pl_get_tail (local.get $q)) (i32.const 0)) 768 | ) 769 | 770 | ;; add a point to polyline (at the end) 771 | (func $pl_add_pt (param $q i32) (param $x i32) (param $y i32) 772 | (local $p i32) 773 | (local.set $p (call $malloc (i32.const 12))) 774 | (call $pt_set_x (local.get $p) (local.get $x)) 775 | (call $pt_set_y (local.get $p) (local.get $y)) 776 | (call $pt_set_next (local.get $p) (i32.const 0)) 777 | (if (i32.eqz (call $pl_get_head (local.get $q)))(then 778 | (call $pl_set_head (local.get $q) (local.get $p)) 779 | )(else 780 | (call $pt_set_next (call $pl_get_tail (local.get $q)) (local.get $p)) 781 | )) 782 | (call $pl_set_tail (local.get $q) (local.get $p)) 783 | (call $pl_set_size (local.get $q) (i32.add (call $pl_get_size (local.get $q)) (i32.const 1))) 784 | ) 785 | 786 | ;; combine two polylines into one by concating the second to the end of the first 787 | (func $pl_cat_tail (param $q0 i32) (param $q1 i32) (result i32) 788 | (if (i32.eqz (local.get $q1))(then 789 | (local.get $q0) 790 | return 791 | )) 792 | (if (i32.eqz (local.get $q0))(then 793 | (local.set $q0 (call $pl_new)) 794 | )) 795 | (if (i32.eqz (call $pl_get_head (local.get $q1)))(then 796 | (local.get $q0) 797 | return 798 | )) 799 | (if (i32.eqz (call $pl_get_head (local.get $q0)))(then 800 | (call $pl_set_head (local.get $q0) (call $pl_get_head (local.get $q1))) 801 | (call $pl_set_tail (local.get $q0) (call $pl_get_tail (local.get $q1))) 802 | (local.get $q0) 803 | return 804 | )) 805 | (call $pt_set_next (call $pl_get_tail (local.get $q0)) (call $pl_get_head (local.get $q1))) 806 | (call $pl_set_tail (local.get $q0) (call $pl_get_tail (local.get $q1))) 807 | (call $pl_set_size (local.get $q0) (i32.add 808 | (call $pl_get_size (local.get $q0)) 809 | (call $pl_get_size (local.get $q1)) 810 | )) 811 | (call $pt_set_next (call $pl_get_tail (local.get $q0)) (i32.const 0)) 812 | (local.get $q0) 813 | return 814 | ) 815 | 816 | ;; combine two polylines into one by concating the first to the end of the second 817 | (func $pl_cat_head (param $q0 i32) (param $q1 i32) (result i32) 818 | (if (i32.eqz (local.get $q1))(then 819 | (local.get $q0) 820 | return 821 | )) 822 | (if (i32.eqz (local.get $q0))(then 823 | (local.set $q0 (call $pl_new)) 824 | )) 825 | (if (i32.eqz (call $pl_get_head (local.get $q1)))(then 826 | (local.get $q0) 827 | return 828 | )) 829 | (if (i32.eqz (call $pl_get_head (local.get $q0)))(then 830 | (call $pl_set_head (local.get $q0) (call $pl_get_head (local.get $q1))) 831 | (call $pl_set_tail (local.get $q0) (call $pl_get_tail (local.get $q1))) 832 | (local.get $q0) 833 | return 834 | )) 835 | (call $pt_set_next (call $pl_get_tail (local.get $q1)) (call $pl_get_head (local.get $q0))) 836 | (call $pl_set_head (local.get $q0) (call $pl_get_head (local.get $q1))) 837 | (call $pl_set_size (local.get $q0) (i32.add 838 | (call $pl_get_size (local.get $q0)) 839 | (call $pl_get_size (local.get $q1)) 840 | )) 841 | (call $pt_set_next (call $pl_get_tail (local.get $q0)) (i32.const 0)) 842 | (local.get $q0) 843 | return 844 | ) 845 | 846 | ;; add a polyline to a list of polylines by prepending it to the front 847 | (func $pl_prepend (param $q0 i32) (param $q1 i32) (result i32) 848 | (if (i32.eqz (local.get $q0))(then 849 | (local.get $q1) 850 | return 851 | )) 852 | (call $pl_set_next (local.get $q1) (local.get $q0)) 853 | (call $pl_set_prev (local.get $q0) (local.get $q1)) 854 | (local.get $q1) 855 | return 856 | ) 857 | 858 | ;; destroy a list of polylines, freeing allocated memory 859 | (func $pls_destroy (param $q i32) 860 | (local $it i32) 861 | (local $jt i32) 862 | (local $kt i32) 863 | (local $lt i32) 864 | (if (i32.eqz (local.get $q))(then 865 | return 866 | )) 867 | (local.set $it (local.get $q)) 868 | loop $pls_destroy_loop0 869 | 870 | (if (i32.eqz (local.get $it))(then)(else 871 | (local.set $lt (call $pl_get_next (local.get $it))) 872 | (local.set $jt (call $pl_get_head (local.get $it))) 873 | loop $pls_destroy_loop1 874 | (if (i32.eqz (local.get $jt))(then)(else 875 | (local.set $kt (call $pt_get_next (local.get $jt))) 876 | (call $free (local.get $jt)) 877 | (local.set $jt (local.get $kt)) 878 | (br $pls_destroy_loop1) 879 | )) 880 | end 881 | (call $free (local.get $it)) 882 | (local.set $it (local.get $lt)) 883 | (br $pls_destroy_loop0) 884 | )) 885 | end 886 | ) 887 | 888 | 889 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 890 | ;; ;; 891 | ;; ;; 892 | ;; MAIN ALGORITHM ;; 893 | ;; ;; 894 | ;; ;; 895 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 896 | 897 | ;; check if a region has any white pixel 898 | (func $not_empty (param $x i32) (param $y i32) (param $w i32) (param $h i32) (result i32) 899 | (local $i i32) 900 | (local $j i32) 901 | (local.set $i (local.get $y)) 902 | loop $not_empty_l0 903 | (local.set $j (local.get $x)) 904 | loop $not_empty_l1 905 | (if (call $im_get (local.get $j) (local.get $i))(then 906 | (i32.const 1) 907 | return 908 | )) 909 | (local.set $j (i32.add (local.get $j) (i32.const 1))) 910 | (br_if $not_empty_l1 (i32.lt_u (local.get $j) (i32.add (local.get $x) (local.get $w)) )) 911 | end 912 | (local.set $i (i32.add (local.get $i) (i32.const 1))) 913 | (br_if $not_empty_l0 (i32.lt_u (local.get $i) (i32.add (local.get $y) (local.get $h)) )) 914 | end 915 | (i32.const 0) 916 | ) 917 | 918 | ;; merge ith fragment of second chunk to first chunk 919 | ;; @param c0 fragments from first chunk 920 | ;; @param c1i ith fragment of second chunk 921 | ;; @param sx (x or y) coordinate of the seam 922 | ;; @param isv is vertical, not horizontal? 923 | ;; @param mode 2-bit flag, 924 | ;; MSB = is matching the left (not right) end of the fragment from first chunk 925 | ;; LSB = is matching the right (not left) end of the fragment from second chunk 926 | ;; @return matching successful? 927 | ;; 928 | (func $merge_impl (param $c0 i32) (param $c1i i32) (param $sx i32) 929 | (param $isv i32) (param $mode i32) (result i32) 930 | (local $b0 i32) 931 | (local $b1 i32) 932 | (local $c0j i32) 933 | (local $md i32) 934 | (local $p0 i32) 935 | (local $p1 i32) 936 | (local $it i32) 937 | (local $xory0 i32) 938 | (local $xory1 i32) 939 | (local $xory2 i32) 940 | (local $d i32) 941 | 942 | ;; For each polyline from one submatrix whose either endpoint coincide with 943 | ;; the splitting row or column, find another polyline in the other submatrix 944 | ;; whose endpoint meets it. If the matrix was split horizontally, then the 945 | ;; x-coordinate of the endpoints should differ by exactly 1, and y-coordinate 946 | ;; can differ between 0 to about 4 (depending on the steepness of the stroke 947 | ;; potrayed), The reverse goes for vertical splitting. 948 | 949 | (local.set $b0 (i32.and (i32.shr_u (local.get $mode) (i32.const 1)) (i32.const 1) )) 950 | (local.set $b1 (i32.and (local.get $mode) (i32.const 1) )) 951 | (local.set $c0j (i32.const 0)) 952 | (local.set $md (i32.const 4)) ;; maximum offset to be regarded as continuous 953 | 954 | (if (local.get $b1) (then 955 | (local.set $p1 (call $pl_get_head (local.get $c1i))) 956 | )(else 957 | (local.set $p1 (call $pl_get_tail (local.get $c1i))) 958 | )) 959 | 960 | (if (local.get $isv)(then 961 | (local.set $xory0 (call $pt_get_y (local.get $p1))) 962 | )(else 963 | (local.set $xory0 (call $pt_get_x (local.get $p1))) 964 | )) 965 | 966 | (if (i32.gt_s 967 | (call $abs_i32 (i32.sub (local.get $xory0) (local.get $sx))) 968 | (i32.const 0) 969 | )(then ;; not on the seam, skip 970 | (i32.const 0) 971 | return 972 | )) 973 | 974 | ;; find the best match 975 | (local.set $it (local.get $c0)) 976 | loop $merge_impl_l0 (if (local.get $it)(then 977 | (if (local.get $b0)(then 978 | (local.set $p0 (call $pl_get_head (local.get $it))) 979 | )(else 980 | (local.set $p0 (call $pl_get_tail (local.get $it))) 981 | )) 982 | 983 | (if (local.get $isv)(then 984 | (local.set $xory0 (call $pt_get_y (local.get $p0))) 985 | (local.set $xory1 (call $pt_get_x (local.get $p0))) 986 | (local.set $xory2 (call $pt_get_x (local.get $p1))) 987 | )(else 988 | (local.set $xory0 (call $pt_get_x (local.get $p0))) 989 | (local.set $xory1 (call $pt_get_y (local.get $p0))) 990 | (local.set $xory2 (call $pt_get_y (local.get $p1))) 991 | )) 992 | 993 | (if (i32.gt_s 994 | (call $abs_i32 (i32.sub (local.get $xory0) (local.get $sx))) 995 | (i32.const 1) 996 | )(then ;; not on the seam, skip 997 | (local.set $it (call $pl_get_next (local.get $it))) 998 | (br $merge_impl_l0) 999 | )) 1000 | 1001 | (local.set $d (call $abs_i32 1002 | (i32.sub (local.get $xory1) (local.get $xory2)) 1003 | )) 1004 | (if (i32.lt_u (local.get $d) (local.get $md) )(then 1005 | (local.set $c0j (local.get $it)) 1006 | (local.set $md (local.get $d)) 1007 | )) 1008 | (local.set $it (call $pl_get_next (local.get $it))) 1009 | (br $merge_impl_l0) 1010 | )) end 1011 | 1012 | 1013 | (if (local.get $c0j)(then ;; best match is good enough, merge them 1014 | (if (local.get $b1) (then 1015 | (if (local.get $b0) (then 1016 | (call $pl_rev (local.get $c1i)) 1017 | (local.set $c0j (call $pl_cat_head (local.get $c0j) (local.get $c1i))) 1018 | 1019 | )(else 1020 | (local.set $c0j (call $pl_cat_tail (local.get $c0j) (local.get $c1i))) 1021 | 1022 | )) 1023 | )(else 1024 | (if (local.get $b0) (then 1025 | (local.set $c0j (call $pl_cat_head (local.get $c0j) (local.get $c1i))) 1026 | 1027 | )(else 1028 | (call $pl_rev (local.get $c1i)) 1029 | (local.set $c0j (call $pl_cat_tail (local.get $c0j) (local.get $c1i))) 1030 | 1031 | 1032 | )) 1033 | )) 1034 | (i32.const 1) 1035 | return 1036 | )) 1037 | (i32.const 0) 1038 | ) 1039 | 1040 | ;; merge fragments from two chunks 1041 | ;; @param c0 fragments from first chunk 1042 | ;; @param c1 fragments from second chunk 1043 | ;; @param sx (x or y) coordinate of the seam 1044 | ;; @param dr merge direction, HORIZONTAL(0) or VERTICAL(1)? 1045 | ;; 1046 | (func $merge_frags (param $c0 i32) (param $c1 i32) (param $sx i32) (param $dr i32) (result i32) 1047 | (local $it i32) 1048 | (local $tmp i32) 1049 | (local $goto i32) 1050 | 1051 | 1052 | (if (i32.eqz (local.get $c0))(then 1053 | (local.get $c1) 1054 | return 1055 | )) 1056 | (if (i32.eqz (local.get $c1))(then 1057 | (local.get $c0) 1058 | return 1059 | )) 1060 | 1061 | (local.set $it (local.get $c1)) 1062 | (local.set $goto (i32.const 0)) 1063 | 1064 | 1065 | 1066 | loop $mf_rem (if (local.get $goto) (then 1067 | 1068 | (if (i32.eqz (call $pl_get_prev (local.get $it))) (then 1069 | (local.set $c1 (call $pl_get_next (local.get $it))) 1070 | (if (local.get $c1) (then 1071 | (call $pl_set_prev (local.get $c1) (i32.const 0)) 1072 | )) 1073 | )(else 1074 | (call $pl_set_next 1075 | (call $pl_get_prev (local.get $it)) 1076 | (call $pl_get_next (local.get $it)) 1077 | ) 1078 | (if (call $pl_get_next (local.get $it)) (then 1079 | (call $pl_set_prev 1080 | (call $pl_get_next (local.get $it)) 1081 | (call $pl_get_prev (local.get $it)) 1082 | ) 1083 | )) 1084 | 1085 | )) 1086 | (call $free (local.get $it)) 1087 | ))loop $mf_next 1088 | 1089 | (if (local.get $goto)(then 1090 | (local.set $it (local.get $tmp)) 1091 | )) 1092 | 1093 | 1094 | 1095 | (if (local.get $it) (then 1096 | 1097 | (local.set $goto (i32.const 1)) 1098 | 1099 | (local.set $tmp (call $pl_get_next (local.get $it))) 1100 | 1101 | (if (call $merge_impl 1102 | (local.get $c0) (local.get $it) (local.get $sx) (local.get $dr) (i32.const 1) 1103 | )(then 1104 | (br $mf_rem) 1105 | )) 1106 | 1107 | (if (call $merge_impl 1108 | (local.get $c0) (local.get $it) (local.get $sx) (local.get $dr) (i32.const 3) 1109 | )(then 1110 | (br $mf_rem) 1111 | )) 1112 | 1113 | (if (call $merge_impl 1114 | (local.get $c0) (local.get $it) (local.get $sx) (local.get $dr) (i32.const 0) 1115 | )(then 1116 | (br $mf_rem) 1117 | )) 1118 | 1119 | (if (call $merge_impl 1120 | (local.get $c0) (local.get $it) (local.get $sx) (local.get $dr) (i32.const 2) 1121 | )(then 1122 | (br $mf_rem) 1123 | )) 1124 | 1125 | (br $mf_next) 1126 | )) 1127 | end 1128 | end 1129 | 1130 | (local.set $it (local.get $c1)) 1131 | loop $mf_l2 (if (local.get $it) (then 1132 | (local.set $tmp (call $pl_get_next (local.get $it))) 1133 | (call $pl_set_prev (local.get $it) (i32.const 0)) 1134 | (call $pl_set_next (local.get $it) (i32.const 0)) 1135 | (local.set $c0 (call $pl_prepend (local.get $c0) (local.get $it))) 1136 | (local.set $it (local.get $tmp)) 1137 | (br $mf_l2) 1138 | ))end 1139 | (local.get $c0) 1140 | ) 1141 | 1142 | ;; recursive bottom: turn chunk into polyline fragments; 1143 | ;; look around on 4 edges of the chunk, and identify the "outgoing" pixels; 1144 | ;; add segments connecting these pixels to center of chunk; 1145 | ;; apply heuristics to adjust center of chunk 1146 | ;; 1147 | ;; @param x left of chunk 1148 | ;; @param y top of chunk 1149 | ;; @param w width of chunk 1150 | ;; @param h height of chunk 1151 | ;; @return the polyline fragments 1152 | ;; 1153 | (func $chunk_to_frags (param $x i32) (param $y i32) (param $w i32) (param $h i32) (result i32) 1154 | (local $frags i32) 1155 | (local $on i32) 1156 | (local $li i32) 1157 | (local $lj i32) 1158 | (local $k i32) 1159 | (local $i i32) 1160 | (local $j i32) 1161 | (local $i0 i32) 1162 | (local $j0 i32) 1163 | (local $i1 i32) 1164 | (local $j1 i32) 1165 | (local $f i32) 1166 | (local $pt i32) 1167 | (local $ms i32) 1168 | (local $mi i32) 1169 | (local $mj i32) 1170 | (local $cx i32) 1171 | (local $cy i32) 1172 | (local $s i32) 1173 | (local $fsize i32) 1174 | (local $perim i32) 1175 | (local $it i32) 1176 | 1177 | ;; - Initially set a flag to false, and whenever a 1-pixel is encountered 1178 | ;; whilst the flag is false, set the flag to true, and push the coordinate 1179 | ;; of the 1-pixel to a stack. 1180 | ;; - Whenever a 0-pixel is encountered whilst the flag is true, pop the last 1181 | ;; coordinate from the stack, and push the midpoint between it and the 1182 | ;; current coordinate. Then set the flag to false. 1183 | ;; - After all border pixels are visited, the stack now holds coordinates for 1184 | ;; all the "outgoing" (or "incoming") pixels from this small image section. 1185 | ;; By connecting these coordinates with the center coordinate of the image 1186 | ;; section, an estimated vectorized representation of the skeleton in this 1187 | ;; area if formed by these line segments. We further improve the estimate 1188 | ;; using the following heuristics: 1189 | ;; - If there are exactly 2 outgoing pixels. It is likely that the area holds 1190 | ;; a straight line. We return a single segment connecting these 2 pixels. 1191 | ;; - If there are 3 or more outgoing pixels, it is likely that the area holds 1192 | ;; an intersection, or "crossroad". We do a convolution on the matrix to 1193 | ;; find the 3x3 submatrix that contains the most 1-pixels. Set the center 1194 | ;; of all the segments to the center of the 3x3 submatrix and return. 1195 | ;; - If there are only 1 outgoing pixels, return the segment that connects 1196 | ;; it and the center of the image section. 1197 | 1198 | (local.set $frags (i32.const 0)) 1199 | (local.set $fsize (i32.const 0)) 1200 | (local.set $on (i32.const 0)) ;; flag (to deal with strokes thicker than 1px) 1201 | (local.set $li (i32.const -1)) 1202 | (local.set $lj (i32.const -1)) 1203 | 1204 | ;; center x,y 1205 | (local.set $cx (i32.add (local.get $x) (i32.div_u (local.get $w) (i32.const 2)))) 1206 | (local.set $cy (i32.add (local.get $y) (i32.div_u (local.get $h) (i32.const 2)))) 1207 | 1208 | (local.set $k (i32.const 0)) 1209 | (local.set $perim (i32.sub (i32.add 1210 | (i32.add (local.get $w) (local.get $w)) 1211 | (i32.add (local.get $h) (local.get $h)) 1212 | ) (i32.const 4))) 1213 | 1214 | ;; // walk around the edge clockwise 1215 | loop $ctf_l0 1216 | (if (i32.lt_u (local.get $k) (local.get $w))(then 1217 | (local.set $i (local.get $y)) 1218 | (local.set $j (i32.add (local.get $x) (local.get $k))) 1219 | 1220 | )(else(if (i32.lt_u 1221 | (local.get $k) 1222 | (i32.sub (i32.add (local.get $w) (local.get $h)) (i32.const 1)) 1223 | )(then 1224 | (local.set $i (i32.add 1225 | (i32.sub (i32.add (local.get $y) (local.get $k)) (local.get $w) ) 1226 | (i32.const 1) 1227 | )) 1228 | (local.set $j (i32.sub (i32.add (local.get $x) (local.get $w)) (i32.const 1))) 1229 | )(else(if (i32.lt_u 1230 | (local.get $k) 1231 | (i32.sub (i32.add (i32.add (local.get $w) (local.get $h)) (local.get $w)) (i32.const 2)) 1232 | )(then 1233 | (local.set $i (i32.sub (i32.add (local.get $y) (local.get $h)) (i32.const 1))) 1234 | (local.set $j (i32.sub 1235 | (i32.add (local.get $x) (local.get $w)) 1236 | (i32.add (i32.sub 1237 | (i32.sub (local.get $k) (local.get $w)) 1238 | (local.get $h) 1239 | ) (i32.const 3)) 1240 | )) 1241 | )(else 1242 | (local.set $i (i32.sub 1243 | (i32.add (local.get $y) (local.get $h)) 1244 | (i32.add (i32.sub 1245 | (i32.sub 1246 | (i32.sub (local.get $k) (local.get $w)) 1247 | (local.get $h) 1248 | ) 1249 | (local.get $w) 1250 | ) (i32.const 4)) 1251 | )) 1252 | (local.set $j (local.get $x)) 1253 | )))))) 1254 | 1255 | (if (call $im_get (local.get $j) (local.get $i)) (then ;; found an outgoing pixel 1256 | (if (i32.eqz (local.get $on))(then ;; left side of stroke 1257 | (local.set $on (i32.const 1)) 1258 | (local.set $f (call $pl_new)) 1259 | (call $pl_add_pt (local.get $f) (local.get $j) (local.get $i)) 1260 | (call $pl_add_pt (local.get $f) (local.get $cx) (local.get $cy)) 1261 | (local.set $frags (call $pl_prepend (local.get $frags) (local.get $f))) 1262 | (local.set $fsize (i32.add (local.get $fsize) (i32.const 1))) 1263 | )) 1264 | )(else 1265 | (if (local.get $on) (then ;; right side of stroke, average to get center of stroke 1266 | (local.set $pt (call $pl_get_head (local.get $frags))) 1267 | (call $pt_set_x (local.get $pt) 1268 | (i32.div_u (i32.add (call $pt_get_x (local.get $pt)) (local.get $lj)) (i32.const 2)) 1269 | ) 1270 | (call $pt_set_y (local.get $pt) 1271 | (i32.div_u (i32.add (call $pt_get_y (local.get $pt)) (local.get $li)) (i32.const 2)) 1272 | ) 1273 | (local.set $on (i32.const 0)) 1274 | )) 1275 | )) 1276 | (local.set $li (local.get $i)) 1277 | (local.set $lj (local.get $j)) 1278 | 1279 | (local.set $k (i32.add (local.get $k) (i32.const 1))) 1280 | (br_if $ctf_l0 (i32.lt_u (local.get $k) (local.get $perim))) 1281 | end 1282 | 1283 | 1284 | (if (i32.eq (local.get $fsize) (i32.const 2))(then ;; probably just a line, connect them 1285 | (local.set $f (call $pl_new)) 1286 | (call $pl_add_pt (local.get $f) 1287 | (call $pt_get_x (call $pl_get_head (local.get $frags))) 1288 | (call $pt_get_y (call $pl_get_head (local.get $frags))) 1289 | ) 1290 | (call $pl_add_pt (local.get $f) 1291 | (call $pt_get_x (call $pl_get_head (call $pl_get_next (local.get $frags)))) 1292 | (call $pt_get_y (call $pl_get_head (call $pl_get_next (local.get $frags)))) 1293 | ) 1294 | (call $pls_destroy (local.get $frags)) 1295 | (local.set $frags (local.get $f)) 1296 | 1297 | )(else (if (i32.gt_u (local.get $fsize) (i32.const 2)) (then 1298 | ;; it's a crossroad, guess the intersection 1299 | 1300 | (local.set $ms (i32.const 0)) 1301 | (local.set $mi (i32.const -1)) 1302 | (local.set $mj (i32.const -1)) 1303 | 1304 | ;; use convolution to find brightest blob 1305 | (local.set $i (i32.add (local.get $y) (i32.const 1))) 1306 | loop $ctf_li 1307 | (local.set $j (i32.add (local.get $x) (i32.const 1))) 1308 | loop $ctf_lj 1309 | (local.set $i0 (i32.sub (local.get $i) (i32.const 1))) 1310 | (local.set $i1 (i32.add (local.get $i) (i32.const 1))) 1311 | (local.set $j0 (i32.sub (local.get $j) (i32.const 1))) 1312 | (local.set $j1 (i32.add (local.get $j) (i32.const 1))) 1313 | (local.set $s (i32.add (call $im_get (local.get $j) (local.get $i)) (i32.add 1314 | (i32.add 1315 | (i32.add 1316 | (call $im_get (local.get $j0) (local.get $i0)) 1317 | (call $im_get (local.get $j1) (local.get $i0)) 1318 | ) 1319 | (i32.add 1320 | (call $im_get (local.get $j0) (local.get $i1)) 1321 | (call $im_get (local.get $j1) (local.get $i1)) 1322 | ) 1323 | )(i32.add 1324 | (i32.add 1325 | (call $im_get (local.get $j0) (local.get $i)) 1326 | (call $im_get (local.get $j1) (local.get $i)) 1327 | ) 1328 | (i32.add 1329 | (call $im_get (local.get $j) (local.get $i0)) 1330 | (call $im_get (local.get $j) (local.get $i1)) 1331 | ) 1332 | ) 1333 | ))) 1334 | 1335 | (if (i32.gt_u (local.get $s) (local.get $ms) )(then 1336 | (local.set $mi (local.get $i)) 1337 | (local.set $mj (local.get $j)) 1338 | (local.set $ms (local.get $s)) 1339 | )(else(if (i32.eq (local.get $s) (local.get $ms)) (then 1340 | (if (i32.lt_u 1341 | (i32.add 1342 | (call $abs_i32 (i32.sub (local.get $j) (local.get $cx))) 1343 | (call $abs_i32 (i32.sub (local.get $i) (local.get $cy))) 1344 | ) 1345 | (i32.add 1346 | (call $abs_i32 (i32.sub (local.get $mj) (local.get $cx))) 1347 | (call $abs_i32 (i32.sub (local.get $mi) (local.get $cy))) 1348 | ) 1349 | )(then 1350 | (local.set $mi (local.get $i)) 1351 | (local.set $mj (local.get $j)) 1352 | (local.set $ms (local.get $s)) 1353 | )) 1354 | )))) 1355 | 1356 | (local.set $j (i32.add (local.get $j) (i32.const 1))) 1357 | (br_if $ctf_lj (i32.lt_u (local.get $j) 1358 | (i32.sub (i32.add (local.get $x) (local.get $w)) (i32.const 1)) 1359 | )) 1360 | end 1361 | (local.set $i (i32.add (local.get $i) (i32.const 1))) 1362 | (br_if $ctf_li (i32.lt_u (local.get $i) 1363 | (i32.sub (i32.add (local.get $y) (local.get $h)) (i32.const 1)) 1364 | )) 1365 | end 1366 | 1367 | (if (i32.eq (local.get $mi) (i32.const -1))(then)(else 1368 | (local.set $it (local.get $frags)) 1369 | loop $ctf_l3 (if (local.get $it)(then 1370 | (call $pt_set_x (call $pl_get_tail (local.get $it)) (local.get $mj) ) 1371 | (call $pt_set_y (call $pl_get_tail (local.get $it)) (local.get $mi) ) 1372 | (local.set $it (call $pl_get_next (local.get $it))) 1373 | 1374 | (br $ctf_l3) 1375 | ))end 1376 | )) 1377 | 1378 | )))) 1379 | 1380 | (local.get $frags) 1381 | 1382 | ) 1383 | 1384 | ;; Trace skeleton from thinning result. 1385 | ;; Algorithm: 1386 | ;; 1. if chunk size is small enough, reach recursive bottom and turn it into segments 1387 | ;; 2. attempt to split the chunk into 2 smaller chunks, either horizontall or vertically; 1388 | ;; find the best "seam" to carve along, and avoid possible degenerate cases 1389 | ;; 3. recurse on each chunk, and merge their segments 1390 | ;; 1391 | ;; @param x left of chunk 1392 | ;; @param y top of chunk 1393 | ;; @param w width of chunk 1394 | ;; @param h height of chunk 1395 | ;; @param iter number of iteration left 1396 | ;; @return pointer to polylines 1397 | ;; 1398 | (func $trace_skeleton_impl (param $x i32) (param $y i32) (param $w i32) (param $h i32) (param $iter i32) (result i32) 1399 | 1400 | (local $frags i32) ;; pointer holding all polylines 1401 | 1402 | ;; calculation of column/row of least resistance 1403 | (local $s i32) ;; current score 1404 | (local $ms i32) ;; minimal score 1405 | (local $mi i32) ;; minimal row 1406 | (local $mj i32) ;; minimal column 1407 | 1408 | ;; iterators 1409 | (local $i i32) 1410 | (local $j i32) 1411 | 1412 | ;; center of chunk 1413 | (local $cx i32) 1414 | (local $cy i32) 1415 | 1416 | ;; bounding boxes 1417 | (local $L0 i32) ;; left 1418 | (local $L1 i32) 1419 | (local $L2 i32) 1420 | (local $L3 i32) 1421 | (local $R0 i32) ;; right 1422 | (local $R1 i32) 1423 | (local $R2 i32) 1424 | (local $R3 i32) 1425 | 1426 | ;; seam info 1427 | (local $sx i32) ;; seam position 1428 | (local $dr i32) ;; seam direction 1429 | 1430 | 1431 | (local.set $frags (i32.const 0)) 1432 | (if (i32.lt_s (local.get $iter) (i32.const 1))(then ;; gameover 1433 | (local.get $frags) 1434 | return 1435 | )) 1436 | (if (i32.or 1437 | (i32.gt_u (local.get $w) (global.get $CHUNK_SIZE)) 1438 | (i32.gt_u (local.get $h) (global.get $CHUNK_SIZE)) 1439 | )(then)(else ;; recursive bottom 1440 | (local.get $frags) 1441 | (call $chunk_to_frags (local.get $x) (local.get $y) (local.get $w) (local.get $h)) 1442 | return 1443 | )) 1444 | 1445 | (local.set $cx (i32.add (local.get $x) (i32.div_u (local.get $w) (i32.const 2)))) 1446 | (local.set $cy (i32.add (local.get $y) (i32.div_u (local.get $h) (i32.const 2)))) 1447 | 1448 | ;; number of white pixels on the seam, less the better: 1449 | (local.set $ms (i32.add (global.get $W) (global.get $H) )) 1450 | (local.set $mi (i32.const -1)) ;; horizontal seam candidate 1451 | (local.set $mj (i32.const -1)) ;; vertical seam candidate 1452 | 1453 | ;; try splitting top and bottom 1454 | (if (i32.gt_u (local.get $h) (global.get $CHUNK_SIZE) ) (then 1455 | (local.set $i (i32.add (local.get $y) (i32.const 3))) 1456 | loop $ts_loop_hi 1457 | 1458 | (if (i32.or 1459 | (i32.or 1460 | (call $im_get (local.get $x) (local.get $i) ) 1461 | (call $im_get (local.get $x) (i32.sub (local.get $i) (i32.const 1)) ) 1462 | )(i32.or 1463 | (call $im_get 1464 | (i32.sub (i32.add (local.get $x) (local.get $w)) (i32.const 1)) 1465 | (local.get $i) 1466 | )(call $im_get 1467 | (i32.sub (i32.add (local.get $x) (local.get $w)) (i32.const 1)) 1468 | (i32.sub (local.get $i) (i32.const 1)) 1469 | ) 1470 | ) 1471 | )(then)(else 1472 | (local.set $s (i32.const 0)) 1473 | (local.set $j (local.get $x)) 1474 | loop $ts_loop_hj 1475 | (local.set $s (i32.add (local.get $s) (call $im_get (local.get $j) (local.get $i)))) 1476 | (local.set $s (i32.add (local.get $s) 1477 | (call $im_get (local.get $j) (i32.sub (local.get $i) (i32.const 1))) 1478 | )) 1479 | (local.set $j (i32.add (local.get $j) (i32.const 1))) 1480 | (br_if $ts_loop_hj (i32.lt_u (local.get $j) (i32.add (local.get $x) (local.get $w)))) 1481 | end 1482 | 1483 | (if (i32.lt_u (local.get $s) (local.get $ms)) (then 1484 | (local.set $ms (local.get $s)) 1485 | (local.set $mi (local.get $i)) 1486 | )(else (if (i32.eq (local.get $s) (local.get $ms)) (then 1487 | ;; if there is a draw (very common), we want the seam to be near the middle 1488 | ;; to balance the divide and conquer tree 1489 | (if (i32.lt_u 1490 | (call $abs_i32 (i32.sub (local.get $i) (local.get $cy) )) 1491 | (call $abs_i32 (i32.sub (local.get $mi) (local.get $cy) )) 1492 | )(then 1493 | (local.set $ms (local.get $s)) 1494 | (local.set $mi (local.get $i)) 1495 | )) 1496 | 1497 | )))) 1498 | )) 1499 | (local.set $i (i32.add (local.get $i) (i32.const 1))) 1500 | (br_if $ts_loop_hi (i32.lt_u (local.get $i) 1501 | (i32.sub (i32.add (local.get $y) (local.get $h)) (i32.const 3)) 1502 | )) 1503 | end 1504 | )) 1505 | 1506 | ;; same as above, try splitting left and right 1507 | (if (i32.gt_u (local.get $w) (global.get $CHUNK_SIZE) ) (then 1508 | (local.set $j (i32.add (local.get $x) (i32.const 3))) 1509 | loop $ts_loop_wj 1510 | (if (i32.or 1511 | (i32.or 1512 | (call $im_get (local.get $j) (local.get $y) ) 1513 | (call $im_get (local.get $j) (i32.sub (i32.add (local.get $y) (local.get $h)) (i32.const 1)) ) 1514 | )(i32.or 1515 | (call $im_get 1516 | (i32.sub (local.get $j) (i32.const 1)) 1517 | (local.get $y) 1518 | )(call $im_get 1519 | (i32.sub (local.get $j) (i32.const 1)) 1520 | (i32.sub (i32.add (local.get $y) (local.get $h)) (i32.const 1)) 1521 | ) 1522 | ) 1523 | )(then)(else 1524 | (local.set $s (i32.const 0)) 1525 | (local.set $i (local.get $y)) 1526 | loop $ts_loop_wi 1527 | (local.set $s (i32.add (local.get $s) (call $im_get (local.get $j) (local.get $i)))) 1528 | (local.set $s (i32.add (local.get $s) 1529 | (call $im_get (i32.sub (local.get $j) (i32.const 1)) (local.get $i)) 1530 | )) 1531 | (local.set $i (i32.add (local.get $i) (i32.const 1))) 1532 | (br_if $ts_loop_wi (i32.lt_u (local.get $i) (i32.add (local.get $y) (local.get $h)))) 1533 | end 1534 | (if (i32.lt_u (local.get $s) (local.get $ms)) (then 1535 | (local.set $ms (local.get $s)) 1536 | (local.set $mi (i32.const -1)) ;; horizontal seam is defeated 1537 | (local.set $mj (local.get $j)) 1538 | )(else (if (i32.eq (local.get $s) (local.get $ms)) (then 1539 | 1540 | (if (i32.lt_u 1541 | (call $abs_i32 (i32.sub (local.get $j) (local.get $cx) )) 1542 | (call $abs_i32 (i32.sub (local.get $mj) (local.get $cx) )) 1543 | )(then 1544 | (local.set $ms (local.get $s)) 1545 | (local.set $mi (i32.const -1)) 1546 | (local.set $mj (local.get $j)) 1547 | )) 1548 | 1549 | )))) 1550 | )) 1551 | (local.set $j (i32.add (local.get $j) (i32.const 1))) 1552 | (br_if $ts_loop_wj (i32.lt_u (local.get $j) 1553 | (i32.sub (i32.add (local.get $x) (local.get $w)) (i32.const 3)) 1554 | )) 1555 | end 1556 | )) 1557 | 1558 | (local.set $L0 (i32.const -1)) 1559 | (local.set $R0 (i32.const -1)) 1560 | (local.set $dr (i32.const -1)) 1561 | 1562 | (if (i32.and 1563 | (i32.gt_u (local.get $h) (global.get $CHUNK_SIZE)) 1564 | (i32.gt_s (local.get $mi) (i32.const -1)) 1565 | )(then ;; split top and bottom 1566 | (local.set $L0 (local.get $x)) 1567 | (local.set $L1 (local.get $y)) 1568 | (local.set $L2 (local.get $w)) 1569 | (local.set $L3 (i32.sub (local.get $mi) (local.get $y))) 1570 | (local.set $R0 (local.get $x)) 1571 | (local.set $R1 (local.get $mi)) 1572 | (local.set $R2 (local.get $w)) 1573 | (local.set $R3 (i32.sub (i32.add (local.get $y) (local.get $h)) (local.get $mi))) 1574 | 1575 | (local.set $dr (i32.const 1)) 1576 | (local.set $sx (local.get $mi)) 1577 | 1578 | )(else(if (i32.and 1579 | (i32.gt_u (local.get $w) (global.get $CHUNK_SIZE)) 1580 | (i32.gt_s (local.get $mj) (i32.const -1)) 1581 | )(then ;; split left and right 1582 | (local.set $L0 (local.get $x)) 1583 | (local.set $L1 (local.get $y)) 1584 | (local.set $L2 (i32.sub (local.get $mj) (local.get $x))) 1585 | (local.set $L3 (local.get $h)) 1586 | (local.set $R0 (local.get $mj)) 1587 | (local.set $R1 (local.get $y)) 1588 | (local.set $R2 (i32.sub (i32.add (local.get $x) (local.get $w)) (local.get $mj))) 1589 | (local.set $R3 (local.get $h)) 1590 | 1591 | (local.set $dr (i32.const 0)) 1592 | (local.set $sx (local.get $mj)) 1593 | )))) 1594 | 1595 | 1596 | (if (i32.gt_s (local.get $dr) (i32.const -1))(then 1597 | 1598 | ;; if there are no white pixels, don't waste time 1599 | (if (call $not_empty (local.get $L0) (local.get $L1) (local.get $L2) (local.get $L3) )(then 1600 | 1601 | (local.set $frags (call $trace_skeleton_impl 1602 | (local.get $L0) (local.get $L1) (local.get $L2) (local.get $L3) 1603 | (i32.sub (local.get $iter) (i32.const 1)) 1604 | )) 1605 | )) 1606 | 1607 | (if (call $not_empty (local.get $R0) (local.get $R1) (local.get $R2) (local.get $R3) )(then 1608 | 1609 | (local.set $frags (call $merge_frags (local.get $frags) 1610 | (call $trace_skeleton_impl 1611 | (local.get $R0) (local.get $R1) (local.get $R2) (local.get $R3) 1612 | (i32.sub (local.get $iter) (i32.const 1)) 1613 | ) 1614 | (local.get $sx) 1615 | (local.get $dr) 1616 | )) 1617 | )) 1618 | 1619 | )) 1620 | 1621 | (if (i32.and 1622 | (i32.eq (local.get $mi) (i32.const -1)) 1623 | (i32.eq (local.get $mj) (i32.const -1)) 1624 | )(then ;; splitting failed! do the recursive bottom instead 1625 | 1626 | (call $chunk_to_frags (local.get $x) (local.get $y) (local.get $w) (local.get $h)) 1627 | return 1628 | )) 1629 | 1630 | (local.get $frags) 1631 | ) 1632 | 1633 | ;; user-facing main function that calls the reursive implementation 1634 | ;; returns: pointer to polylines 1635 | (func $trace_skeleton (result i32) 1636 | (call $trace_skeleton_impl (i32.const 0) (i32.const 0) (global.get $W) (global.get $H) (global.get $MAX_ITER)) 1637 | ) 1638 | 1639 | ;; setup (call before trace_skeleton() and im_set()) 1640 | ;; w: width, h: height 1641 | (func $setup (param $w i32) (param $h i32) 1642 | (global.set $W (local.get $w)) 1643 | (global.set $H (local.get $h)) 1644 | (global.set $im_ptr (call $malloc (i32.mul (global.get $W) (global.get $H)))) 1645 | ) 1646 | 1647 | ;; exported API's 1648 | 1649 | ;; global parameters 1650 | (export "MAX_ITER" (global $MAX_ITER)) 1651 | (export "CHUNK_SIZE" (global $CHUNK_SIZE)) 1652 | 1653 | ;; input image I/O 1654 | (export "im_set" (func $im_set)) 1655 | (export "im_get" (func $im_get)) 1656 | 1657 | ;; datastructure I/O 1658 | 1659 | ;; polylines 1660 | (export "pl_new" (func $pl_new)) 1661 | (export "pl_get_head" (func $pl_get_head)) 1662 | (export "pl_get_tail" (func $pl_get_tail)) 1663 | (export "pl_get_next" (func $pl_get_next)) 1664 | (export "pl_get_prev" (func $pl_get_prev)) 1665 | (export "pl_get_size" (func $pl_get_size)) 1666 | (export "pl_set_head" (func $pl_set_head)) 1667 | (export "pl_set_tail" (func $pl_set_tail)) 1668 | (export "pl_set_next" (func $pl_set_next)) 1669 | (export "pl_set_prev" (func $pl_set_prev)) 1670 | (export "pl_set_size" (func $pl_set_size)) 1671 | (export "pl_add_pt" (func $pl_add_pt)) 1672 | (export "pls_destroy" (func $pls_destroy)) 1673 | 1674 | ;; points 1675 | (export "pt_get_x" (func $pt_get_x)) 1676 | (export "pt_get_y" (func $pt_get_y)) 1677 | (export "pt_get_next" (func $pt_get_next)) 1678 | 1679 | ;; main functions 1680 | (export "setup" (func $setup)) 1681 | (export "thinning_zs" (func $thinning_zs)) 1682 | (export "trace_skeleton" (func $trace_skeleton)) 1683 | 1684 | ;; heap 1685 | (export "mem" (memory $mem )) 1686 | 1687 | ) 1688 | ;; MIT License 1689 | ;; 1690 | ;; Copyright (c) 2020 Lingdong Huang 1691 | ;; 1692 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy 1693 | ;; of this software and associated documentation files (the "Software"), to deal 1694 | ;; in the Software without restriction, including without limitation the rights 1695 | ;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 1696 | ;; copies of the Software, and to permit persons to whom the Software is 1697 | ;; furnished to do so, subject to the following conditions: 1698 | ;; 1699 | ;; The above copyright notice and this permission notice shall be included in all 1700 | ;; copies or substantial portions of the Software. 1701 | ;; 1702 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1703 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1704 | ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1705 | ;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1706 | ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1707 | ;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1708 | ;; SOFTWARE. --------------------------------------------------------------------------------