├── .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 | |  | 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 | |  | 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 | |  | 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 | |  | Generate brownian fractal trees (aka Diffusion-limited aggregation). |
32 | | | [**mazegen.wat**](wat/mazegen.wat) |
33 | |  | 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 | |  | Very baseline 32-bit implicit-free-list first-fit malloc. |
36 | | | [**traceskeleton.wat**](wat/traceskeleton.wat) |
37 | |  | 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 | |  | 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 | |  | 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.
--------------------------------------------------------------------------------