├── .gitattributes ├── LICENSE.txt ├── README.md ├── .gitignore ├── MarchingSquaresOpt.js ├── marchingSquaresTest.html ├── MarchingSquaresOld.js └── MarchingSquares.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Sakri Rosenstrom 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Optimized implementation of the [Marching Squares](http://users.polytech.unice.fr/~lingrand/MarchingCubes/algo.html) algorithm in Javascript 2 | 3 |  4 | 5 | Computation times for 20 iterations: 6 | 7 | | | old | new | optimized | 8 | |------------|------|-----|-----| 9 | | time in ms | 3876 | 443 | 20 | 10 | | time in % | 873 | 100 | **4** | 11 | 12 | The optimized version is approximately 217 times faster than the "old version" and **24 times faster** than the "new version" implemented by [sakri](https://github.com/sakri). 13 | 14 | [ES2015](http://www.ecma-international.org/ecma-262/6.0/) [features](https://kangax.github.io/compat-table/es6/) like TypedArrays, const and local scoping are used to gain this speed up. 15 | 16 | Test it yourself with this online [benchmark / demo](http://htmlpreview.github.io/?https://github.com/mamrehn/MarchingSquaresJS/blob/master/marchingSquaresTest.html). 17 | 18 | ## Original README 19 | 20 | Marching squares outlines blobs of non-transparent pixels inside a bitmap. 21 | 22 | This is a straight forward port from this excellent implementation by Phill Spiess: 23 | 24 | http://devblog.phillipspiess.com/2010/02/23/better-know-an-algorithm-1-marching-squares/ 25 | 26 | This is a Javascript implementation designed to be used with Html5 Canvas. 27 | 28 | Check out the code in marchingSquaresTest.html for usage example. 29 | 30 | http://htmlpreview.github.io/?https://github.com/sakri/MarchingSquaresJS/blob/master/marchingSquaresTest.html 31 | 32 | 33 | Here's a few codepen demos using it: 34 | 35 | A visualization of the algo as it executes 36 | http://codepen.io/sakri/full/aIirl 37 | 38 | Text Edge Flare 39 | http://codepen.io/sakri/full/vIKJp 40 | 41 | Ghost text 42 | http://codepen.io/sakri/full/zbqti 43 | 44 | He-Man effect 45 | http://codepen.io/sakri/full/wsiLC 46 | 47 | 48 | Here's a demo that tackles "blobs with holes" as in getting the outline for characters such as "O" "8" "&" etc. 49 | http://codepen.io/sakri/pen/LCrDe 50 | 51 | If there is interest, I can include the required code in this repo (floodFill, basic threshold etc.) 52 | 53 | 54 | 55 | Have a good day! 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Webstorm 3 | ################# 4 | 5 | .idea/ 6 | .idea/workspace.xml 7 | 8 | ################# 9 | ## Eclipse 10 | ################# 11 | 12 | *.pydevproject 13 | .project 14 | .metadata 15 | bin/ 16 | tmp/ 17 | *.tmp 18 | *.bak 19 | *.swp 20 | *~.nib 21 | local.properties 22 | .classpath 23 | .settings/ 24 | .loadpath 25 | 26 | # External tool builders 27 | .externalToolBuilders/ 28 | 29 | # Locally stored "Eclipse launch configurations" 30 | *.launch 31 | 32 | # CDT-specific 33 | .cproject 34 | 35 | # PDT-specific 36 | .buildpath 37 | 38 | 39 | ################# 40 | ## Visual Studio 41 | ################# 42 | 43 | ## Ignore Visual Studio temporary files, build results, and 44 | ## files generated by popular Visual Studio add-ons. 45 | 46 | # User-specific files 47 | *.suo 48 | *.user 49 | *.sln.docstates 50 | 51 | # Build results 52 | 53 | [Dd]ebug/ 54 | [Rr]elease/ 55 | x64/ 56 | build/ 57 | [Bb]in/ 58 | [Oo]bj/ 59 | 60 | # MSTest test Results 61 | [Tt]est[Rr]esult*/ 62 | [Bb]uild[Ll]og.* 63 | 64 | *_i.c 65 | *_p.c 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.pch 70 | *.pdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.log 86 | *.scc 87 | 88 | # Visual C++ cache files 89 | ipch/ 90 | *.aps 91 | *.ncb 92 | *.opensdf 93 | *.sdf 94 | *.cachefile 95 | 96 | # Visual Studio profiler 97 | *.psess 98 | *.vsp 99 | *.vspx 100 | 101 | # Guidance Automation Toolkit 102 | *.gpState 103 | 104 | # ReSharper is a .NET coding add-in 105 | _ReSharper*/ 106 | *.[Rr]e[Ss]harper 107 | 108 | # TeamCity is a build add-in 109 | _TeamCity* 110 | 111 | # DotCover is a Code Coverage Tool 112 | *.dotCover 113 | 114 | # NCrunch 115 | *.ncrunch* 116 | .*crunch*.local.xml 117 | 118 | # Installshield output folder 119 | [Ee]xpress/ 120 | 121 | # DocProject is a documentation generator add-in 122 | DocProject/buildhelp/ 123 | DocProject/Help/*.HxT 124 | DocProject/Help/*.HxC 125 | DocProject/Help/*.hhc 126 | DocProject/Help/*.hhk 127 | DocProject/Help/*.hhp 128 | DocProject/Help/Html2 129 | DocProject/Help/html 130 | 131 | # Click-Once directory 132 | publish/ 133 | 134 | # Publish Web Output 135 | *.Publish.xml 136 | *.pubxml 137 | 138 | # NuGet Packages Directory 139 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 140 | #packages/ 141 | 142 | # Windows Azure Build Output 143 | csx 144 | *.build.csdef 145 | 146 | # Windows Store app package directory 147 | AppPackages/ 148 | 149 | # Others 150 | sql/ 151 | *.Cache 152 | ClientBin/ 153 | [Ss]tyle[Cc]op.* 154 | ~$* 155 | *~ 156 | *.dbmdl 157 | *.[Pp]ublish.xml 158 | *.pfx 159 | *.publishsettings 160 | 161 | # RIA/Silverlight projects 162 | Generated_Code/ 163 | 164 | # Backup & report files from converting an old project file to a newer 165 | # Visual Studio version. Backup files are not needed, because we have git ;-) 166 | _UpgradeReport_Files/ 167 | Backup*/ 168 | UpgradeLog*.XML 169 | UpgradeLog*.htm 170 | 171 | # SQL Server files 172 | App_Data/*.mdf 173 | App_Data/*.ldf 174 | 175 | ############# 176 | ## Windows detritus 177 | ############# 178 | 179 | # Windows image file caches 180 | Thumbs.db 181 | ehthumbs.db 182 | 183 | # Folder config file 184 | Desktop.ini 185 | 186 | # Recycle Bin used on file shares 187 | $RECYCLE.BIN/ 188 | 189 | # Mac crap 190 | .DS_Store 191 | 192 | 193 | ############# 194 | ## Python 195 | ############# 196 | 197 | *.py[co] 198 | 199 | # Packages 200 | *.egg 201 | *.egg-info 202 | dist/ 203 | build/ 204 | eggs/ 205 | parts/ 206 | var/ 207 | sdist/ 208 | develop-eggs/ 209 | .installed.cfg 210 | 211 | # Installer logs 212 | pip-log.txt 213 | 214 | # Unit test / coverage reports 215 | .coverage 216 | .tox 217 | 218 | #Translations 219 | *.mo 220 | 221 | #Mr Developer 222 | .mr.developer.cfg 223 | -------------------------------------------------------------------------------- /MarchingSquaresOpt.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by @sakri on 25-3-14. 3 | * Edited and optimized by @mamrehn on 08-09-16 4 | * 5 | * Javascript port of : 6 | * http://devblog.phillipspiess.com/2010/02/23/better-know-an-algorithm-1-marching-squares/ 7 | * returns an Array of x and y positions defining the perimeter of a blob of non-transparent pixels on a canvas 8 | * 9 | */ 10 | (function (window){ 11 | 12 | const MarchingSquaresOpt = {}; 13 | 14 | MarchingSquaresOpt.getBlobOutlinePoints = function(source_array, width, height=0){ 15 | // Note: object should not be on the border of the array, since there is 16 | // no padding of 1 pixel to handle points which touch edges 17 | 18 | if (source_array instanceof HTMLCanvasElement){ 19 | width = source_array.width; 20 | height = source_array.height; 21 | const data4 = source_array.getContext('2d').getImageData(0, 0, width, height).data, // Uint8ClampedArray 22 | len = width * height, 23 | data = new Uint8Array(len); 24 | for (let i = 0; i < len; ++i){ 25 | data[i] = data4[i << 2]; 26 | } 27 | source_array = data; 28 | } else if (0 == height){ 29 | height = (source_array.length / width)|0; 30 | } 31 | 32 | // find the starting point 33 | const startingPoint = MarchingSquaresOpt.getFirstNonTransparentPixelTopDown(source_array, width, height); 34 | if (null === startingPoint){ 35 | console.log('[Warning] Marching Squares could not find an object in the given array'); 36 | return []; 37 | } 38 | 39 | // return list of w and h positions 40 | return MarchingSquaresOpt.walkPerimeter(source_array, width, height, startingPoint.w, startingPoint.h); 41 | }; 42 | 43 | MarchingSquaresOpt.getFirstNonTransparentPixelTopDown = function(source_array, width, height){ 44 | let idx; 45 | for(let h = 0|0; h < height; ++h){ 46 | idx = (h * width)|0; 47 | for(let w = 0|0; w < width; ++w){ 48 | if(source_array[idx] > 0){ 49 | return {w : w, h : h}; 50 | } 51 | ++idx; 52 | } 53 | } 54 | return null; 55 | }; 56 | 57 | MarchingSquaresOpt.walkPerimeter = function(source_array, width, height, start_w, start_h){ 58 | 59 | width = width|0; 60 | height = height|0; 61 | 62 | // Set up our return list 63 | const point_list = [], 64 | up = 1|0, left = 2|0, down = 3|0, right = 4|0, 65 | step_func = MarchingSquaresOpt.step; 66 | 67 | let idx = 0|0, // Note: initialize it with an integer, so the JS interpreter optimizes for this type. 68 | 69 | // our current x and y positions, initialized 70 | // to the init values passed in 71 | w = start_w, 72 | h = start_h, 73 | 74 | // the main while loop, continues stepping until 75 | // we return to our initial points 76 | next_step; 77 | do { 78 | // evaluate our state, and set up our next direction 79 | idx = (h - 1) * width + (w - 1); 80 | next_step = step_func(idx, source_array, width); 81 | 82 | // if our current point is within our image 83 | // add it to the list of points 84 | if (w >= 0 && w < width && h >= 0 && h < height){ 85 | point_list.push(w - 1, h); 86 | } 87 | 88 | switch (next_step){ 89 | case up: --h; break; 90 | case left: --w; break; 91 | case down: ++h; break; 92 | case right: ++w; break; 93 | default: 94 | break; 95 | } 96 | 97 | } while (w != start_w || h != start_h); 98 | 99 | point_list.push(w, h); 100 | 101 | return point_list; 102 | }; 103 | 104 | // determines and sets the state of the 4 pixels that 105 | // represent our current state, and sets our current and 106 | // previous directions 107 | 108 | MarchingSquaresOpt.step = function(idx, source_array, width){ 109 | //console.log('Sakri.MarchingSquaresOpt.step()'); 110 | // Scan our 4 pixel area 111 | //Sakri.imageData = Sakri.MarchingSquaresOpt.sourceContext.getImageData(x-1, y-1, 2, 2).data; 112 | 113 | const up_left = 0 < source_array[idx + 1], 114 | up_right = 0 < source_array[idx + 2], 115 | down_left = 0 < source_array[idx + width + 1], 116 | down_right = 0 < source_array[idx + width + 2], 117 | none = 0|0, up = 1|0, left = 2|0, down = 3|0, right = 4|0; 118 | 119 | // Determine which state we are in 120 | let state = 0|0; 121 | 122 | if (up_left){state |= 1;} 123 | if (up_right){state |= 2;} 124 | if (down_left){state |= 4;} 125 | if (down_right){state |= 8;} 126 | 127 | // State now contains a number between 0 and 15 128 | // representing our state. 129 | // In binary, it looks like 0000-1111 (in binary) 130 | 131 | // An example. Let's say the top two pixels are filled, 132 | // and the bottom two are empty. 133 | // Stepping through the if statements above with a state 134 | // of 0b0000 initially produces: 135 | // Upper Left == true ==> 0b0001 136 | // Upper Right == true ==> 0b0011 137 | // The others are false, so 0b0011 is our state 138 | // (That's 3 in decimal.) 139 | 140 | // Looking at the chart above, we see that state 141 | // corresponds to a move right, so in our switch statement 142 | // below, we add a case for 3, and assign Right as the 143 | // direction of the next step. We repeat this process 144 | // for all 16 states. 145 | 146 | // So we can use a switch statement to determine our 147 | // next direction based on 148 | switch (state){ 149 | case 1: MarchingSquaresOpt.next_step = up; break; 150 | case 2: MarchingSquaresOpt.next_step = right; break; 151 | case 3: MarchingSquaresOpt.next_step = right; break; 152 | case 4: MarchingSquaresOpt.next_step = left; break; 153 | case 5: MarchingSquaresOpt.next_step = up; break; 154 | case 6: 155 | if (MarchingSquaresOpt.next_step == up){ // info from previous_step 156 | MarchingSquaresOpt.next_step = left; 157 | } else { 158 | MarchingSquaresOpt.next_step = right; 159 | } 160 | break; 161 | case 7: MarchingSquaresOpt.next_step = right; break; 162 | case 8: MarchingSquaresOpt.next_step = down; break; 163 | case 9: 164 | if (MarchingSquaresOpt.next_step == right){ // info from previous_step 165 | MarchingSquaresOpt.next_step = up; 166 | } else { 167 | MarchingSquaresOpt.next_step = down; 168 | } 169 | break; 170 | case 10: MarchingSquaresOpt.next_step = down; break; 171 | case 11: MarchingSquaresOpt.next_step = down; break; 172 | case 12: MarchingSquaresOpt.next_step = left; break; 173 | case 13: MarchingSquaresOpt.next_step = up; break; 174 | case 14: MarchingSquaresOpt.next_step = left; break; 175 | default: 176 | MarchingSquaresOpt.next_step = none; // this should never happen 177 | break; 178 | } 179 | return MarchingSquaresOpt.next_step; 180 | }; 181 | 182 | window.MarchingSquaresOpt = MarchingSquaresOpt; 183 | 184 | }(window)); 185 | -------------------------------------------------------------------------------- /marchingSquaresTest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |192 | 193 |
194 | 195 |