http://www.emanueleferonato.com/2013/03/04/reduce-the-number-of-points-in-a-polygon-with-the-ramer-douglas-peucker-algorithm/
1 package { 2 import flash.display.Sprite; 3 import flash.display.BitmapData; 4 import flash.display.Bitmap; 5 import flash.geom.Matrix; 6 import flash.geom.Point; 7 public class Main extends Sprite { 8 private var bitmapData:BitmapData=new BitmapData(640,480,true,0x00000000); 9 // tolerance is the amount of alpha for a pixel to be considered solid 10 private var tolerance:Number=0x01; 11 public function Main() { 12 // adding a png image with transparency 13 bitmapData.draw(new Logo(278,429),new Matrix(1,0,0,1,20,40)); 14 var bitmap:Bitmap=new Bitmap(bitmapData); 15 addChild(bitmap); 16 bitmap.alpha=0.5 17 // at the end of this function, marchingVector will contain the points tracing the contour 18 var marchingVector:Vector.<Point>=marchingSquares(bitmapData); 19 marchingVector=RDP(marchingVector,0.50); 20 var canvas:Sprite=new Sprite(); 21 addChild(canvas); 22 canvas.graphics.moveTo(marchingVector[0].x+320,marchingVector[0].y); 23 for (var i:Number=0; i<marchingVector.length; i++) { 24 canvas.graphics.lineStyle(2,0xffffff); 25 canvas.graphics.lineTo(marchingVector[i].x+320,marchingVector[i].y); 26 canvas.graphics.lineStyle(1,0xff0000); 27 canvas.graphics.drawCircle(marchingVector[i].x+320,marchingVector[i].y, 2); 28 } 29 canvas.graphics.lineStyle(2,0xffffff); 30 canvas.graphics.lineTo(marchingVector[0].x+320,marchingVector[0].y); 31 } 32 33 public function RDP(v:Vector.<Point>,epsilon:Number):Vector.<Point> { 34 var firstPoint:Point=v[0]; 35 var lastPoint:Point=v[v.length-1]; 36 if (v.length<3) { 37 return v; 38 } 39 var index:Number=-1; 40 var dist:Number=0; 41 for (var i:Number=1; i<v.length-1; i++) { 42 var cDist:Number=findPerpendicularDistance(v[i],firstPoint,lastPoint); 43 if (cDist>dist) { 44 dist=cDist; 45 index=i; 46 } 47 } 48 if (dist>epsilon) { 49 var l1:Vector.<Point>=v.slice(0,index+1); 50 var l2:Vector.<Point>=v.slice(index); 51 var r1=RDP(l1,epsilon); 52 var r2=RDP(l2,epsilon); 53 var rs:Vector.<Point>=r1.slice(0,r1.length-1).concat(r2); 54 return rs; 55 } 56 else { 57 return new Vector.<Point>(firstPoint,lastPoint); 58 } 59 return null; 60 } 61 62 private function findPerpendicularDistance(p:Point, p1:Point,p2:Point) { 63 var result; 64 var slope; 65 var intercept; 66 if (p1.x==p2.x) { 67 result=Math.abs(p.x-p1.x); 68 } 69 else { 70 slope = (p2.y - p1.y) / (p2.x - p1.x); 71 intercept=p1.y-(slope*p1.x); 72 result = Math.abs(slope * p.x - p.y + intercept) / Math.sqrt(Math.pow(slope, 2) + 1); 73 } 74 return result; 75 } 76 77 public function marchingSquares(bitmapData:BitmapData):Vector.<Point> { 78 var contourVector:Vector.<Point> = new Vector.<Point>(); 79 // this is the canvas we'll use to draw the contour 80 var canvas:Sprite=new Sprite(); 81 addChild(canvas); 82 canvas.graphics.lineStyle(2,0x00ff00); 83 // getting the starting pixel; 84 var startPoint:Point=getStartingPixel(bitmapData); 85 // if we found a starting pixel we can begin 86 if (startPoint!=null) { 87 // moving the graphic pen to the starting pixel 88 canvas.graphics.moveTo(startPoint.x,startPoint.y); 89 // pX and pY are the coordinates of the starting point; 90 var pX:Number=startPoint.x; 91 var pY:Number=startPoint.y; 92 // stepX and stepY can be -1, 0 or 1 and represent the step in pixels to reach 93 // next contour point 94 var stepX:Number; 95 var stepY:Number; 96 // we also need to save the previous step, that's why we use prevX and prevY 97 var prevX:Number; 98 var prevY:Number; 99 // closedLoop will be true once we traced the full contour 100 var closedLoop:Boolean=false; 101 while (!closedLoop) { 102 // the core of the script is getting the 2x2 square value of each pixel 103 var squareValue:Number=getSquareValue(pX,pY); 104 switch (squareValue) { 105 /* going UP with these cases: 106 107 +---+---+ +---+---+ +---+---+ 108 | 1 | | | 1 | | | 1 | | 109 +---+---+ +---+---+ +---+---+ 110 | | | | 4 | | | 4 | 8 | 111 +---+---+ +---+---+ +---+---+ 112 113 */ 114 case 1 : 115 case 5 : 116 case 13 : 117 stepX=0; 118 stepY=-1; 119 break; 120 /* going DOWN with these cases: 121 122 +---+---+ +---+---+ +---+---+ 123 | | | | | 2 | | 1 | 2 | 124 +---+---+ +---+---+ +---+---+ 125 | | 8 | | | 8 | | | 8 | 126 +---+---+ +---+---+ +---+---+ 127 128 */ 129 case 8 : 130 case 10 : 131 case 11 : 132 stepX=0; 133 stepY=1; 134 break; 135 /* going LEFT with these cases: 136 137 +---+---+ +---+---+ +---+---+ 138 | | | | | | | | 2 | 139 +---+---+ +---+---+ +---+---+ 140 | 4 | | | 4 | 8 | | 4 | 8 | 141 +---+---+ +---+---+ +---+---+ 142 143 */ 144 case 4 : 145 case 12 : 146 case 14 : 147 stepX=-1; 148 stepY=0; 149 break; 150 /* going RIGHT with these cases: 151 152 +---+---+ +---+---+ +---+---+ 153 | | 2 | | 1 | 2 | | 1 | 2 | 154 +---+---+ +---+---+ +---+---+ 155 | | | | | | | 4 | | 156 +---+---+ +---+---+ +---+---+ 157 158 */ 159 case 2 : 160 case 3 : 161 case 7 : 162 stepX=1; 163 stepY=0; 164 break; 165 case 6 : 166 /* special saddle point case 1: 167 168 +---+---+ 169 | | 2 | 170 +---+---+ 171 | 4 | | 172 +---+---+ 173 174 going LEFT if coming from UP 175 else going RIGHT 176 177 */ 178 if (prevX==0&&prevY==-1) { 179 stepX=-1; 180 stepY=0; 181 } 182 else { 183 stepX=1; 184 stepY=0; 185 } 186 break; 187 case 9 : 188 /* special saddle point case 2: 189 190 +---+---+ 191 | 1 | | 192 +---+---+ 193 | | 8 | 194 +---+---+ 195 196 going UP if coming from RIGHT 197 else going DOWN 198 199 */ 200 if (prevX==1&&prevY==0) { 201 stepX=0; 202 stepY=-1; 203 } 204 else { 205 stepX=0; 206 stepY=1; 207 } 208 break; 209 } 210 // moving onto next point 211 pX+=stepX; 212 pY+=stepY; 213 // saving contour point 214 contourVector.push(new Point(pX, pY)); 215 prevX=stepX; 216 prevY=stepY; 217 // drawing the line 218 canvas.graphics.lineTo(pX,pY); 219 // if we returned to the first point visited, the loop has finished; 220 if (pX==startPoint.x&&pY==startPoint.y) { 221 closedLoop=true; 222 } 223 } 224 } 225 return contourVector; 226 } 227 228 private function getStartingPixel(bitmapData:BitmapData):Point { 229 // finding the starting pixel is a matter of brute force, we need to scan 230 // the image pixel by pixel until we find a non-transparent pixel 231 var zeroPoint:Point=new Point(0,0); 232 var offsetPoint:Point=new Point(0,0); 233 for (var i:Number=0; i<bitmapData.height; i++) { 234 for (var j:Number=0; j<bitmapData.width; j++) { 235 offsetPoint.x=j; 236 offsetPoint.y=i; 237 if (bitmapData.hitTest(zeroPoint,tolerance,offsetPoint)) { 238 return offsetPoint; 239 } 240 } 241 } 242 return null; 243 } 244 245 private function getSquareValue(pX:Number,pY:Number):Number { 246 /* 247 248 checking the 2x2 pixel grid, assigning these values to each pixel, if not transparent 249 250 +---+---+ 251 | 1 | 2 | 252 +---+---+ 253 | 4 | 8 | <- current pixel (pX,pY) 254 +---+---+ 255 256 */ 257 var squareValue:Number=0; 258 // checking upper left pixel 259 if (getAlphaValue(bitmapData.getPixel32(pX-1,pY-1))>=tolerance) { 260 squareValue+=1; 261 } 262 // checking upper pixel 263 if (getAlphaValue(bitmapData.getPixel32(pX,pY-1))>tolerance) { 264 squareValue+=2; 265 } 266 // checking left pixel 267 if (getAlphaValue(bitmapData.getPixel32(pX-1,pY))>tolerance) { 268 squareValue+=4; 269 } 270 // checking the pixel itself 271 if (getAlphaValue(bitmapData.getPixel32(pX,pY))>tolerance) { 272 squareValue+=8; 273 } 274 return squareValue; 275 } 276 277 private function getAlphaValue(n:Number):Number { 278 // given an ARGB color value, returns the alpha 0 -> 255 279 return n >> 24 & 0xFF; 280 } 281 } 282 }