├── LICENSE ├── README.markdown ├── bin └── as3potrace.swc └── src └── com └── powerflasher └── as3potrace ├── POTrace.as ├── POTraceParams.as ├── backend ├── GraphicsDataBackend.as ├── IBackend.as ├── NullBackend.as └── TraceBackend.as └── geom ├── CubicCurve.as ├── Curve.as ├── CurveKind.as ├── Direction.as ├── MonotonInterval.as ├── Opti.as ├── Path.as ├── PointInt.as ├── PrivCurve.as └── SumStruct.as /LICENSE: -------------------------------------------------------------------------------- 1 | as3potrace 2 | Copyright (C) 2001-2010 Peter Selinger (potrace) 3 | Copyright (C) 2009 Wolfgang Nagl (Vectorization) 4 | Copyright (C) 2011 Claus Wahlers (as3potrace) 5 | 6 | This program is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License 8 | as published by the Free Software Foundation; either version 2 9 | of the License, or (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 | 02110-1301, USA. -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | as3potrace is an Actionscript 3 library to trace bitmaps. 2 | 3 | It is a port of the well known C library [potrace](http://potrace.sourceforge.net/ "potrace") by Peter Selinger. To be more exact, it is a AS3 port of [Vectorization](http://www.drawing3d.de/Downloads.aspx "Vectorization"), a C# port of potrace 1.8 by Wolfgang Nagl. 4 | 5 | For more info, please see my [blog post on as3potrace](http://wahlers.com.br/claus/blog/as3-bitmap-tracer-vectorizer-as3potrace/ "blog post on as3potrace"). 6 | 7 | as3potrace is licensed under the GPL (see LICENSE for more info). 8 | -------------------------------------------------------------------------------- /bin/as3potrace.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerflasherBR/as3potrace/098f74a2bd5b43dd9c070ac6b01fae6a8dbdb78d/bin/as3potrace.swc -------------------------------------------------------------------------------- /src/com/powerflasher/as3potrace/POTrace.as: -------------------------------------------------------------------------------- 1 | /* 2 | This program is free software; you can redistribute it and/or modify it 3 | under the terms of the GNU General Public License as published by the 4 | Free Software Foundation; either version 2, or (at your option) any later 5 | version. 6 | 7 | This program is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 10 | Public License for more details. 11 | 12 | You should have received a copy of the GNU General Public License along 13 | with this program; if not, write to the Free Software Foundation, Inc., 14 | 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 15 | 16 | Copyright (C) 2001-2010 Peter Selinger (Original author) 17 | Copyright (C) 2009 Wolfgang Nagl (C# port of Potrace 1.8: "Vectorization") 18 | Copyright (C) 2011 Claus Wahlers (AS3 port of Vectorization: "as3potrace") 19 | 20 | "Potrace" is a trademark of Peter Selinger. "Potrace Professional" and 21 | "Icosasoft" are trademarks of Icosasoft Software Inc. Other trademarks 22 | belong to their respective owners. 23 | 24 | http://potrace.sourceforge.net/ 25 | http://www.drawing3d.de/Downloads.aspx (Vectorization) 26 | https://github.com/PowerflasherBR/as3potrace (as3potrace) 27 | */ 28 | 29 | package com.powerflasher.as3potrace 30 | { 31 | import com.powerflasher.as3potrace.backend.IBackend; 32 | import com.powerflasher.as3potrace.backend.NullBackend; 33 | import com.powerflasher.as3potrace.geom.Curve; 34 | import com.powerflasher.as3potrace.geom.CurveKind; 35 | import com.powerflasher.as3potrace.geom.Direction; 36 | import com.powerflasher.as3potrace.geom.MonotonInterval; 37 | import com.powerflasher.as3potrace.geom.Opti; 38 | import com.powerflasher.as3potrace.geom.Path; 39 | import com.powerflasher.as3potrace.geom.PointInt; 40 | import com.powerflasher.as3potrace.geom.PrivCurve; 41 | import com.powerflasher.as3potrace.geom.SumStruct; 42 | 43 | import flash.display.BitmapData; 44 | import flash.geom.Point; 45 | 46 | public class POTrace 47 | { 48 | protected var bmWidth:uint; 49 | protected var bmHeight:uint; 50 | 51 | protected var _params:POTraceParams; 52 | protected var _backend:IBackend; 53 | 54 | protected static const POTRACE_CORNER:int = 1; 55 | protected static const POTRACE_CURVETO:int = 2; 56 | 57 | protected static const COS179:Number = Math.cos(179 * Math.PI / 180); 58 | 59 | public function POTrace(params:POTraceParams = null, backend:IBackend = null) 60 | { 61 | _params = params || new POTraceParams(); 62 | _backend = backend || new NullBackend(); 63 | } 64 | 65 | public function get params():POTraceParams { 66 | return _params; 67 | } 68 | public function set params(params:POTraceParams):void { 69 | _params = params; 70 | } 71 | 72 | public function get backend():IBackend { 73 | return _backend; 74 | } 75 | public function set backend(backend:IBackend):void { 76 | _backend = backend; 77 | } 78 | 79 | /* 80 | * Main function 81 | * Yields the curve informations related to a given binary bitmap. 82 | * Returns an array of curvepaths. 83 | * Each of this paths is a list of connecting curves. 84 | */ 85 | public function potrace_trace(bitmapData:BitmapData):Array 86 | { 87 | // Make sure there is a 1px white border 88 | var bitmapDataCopy:BitmapData = new BitmapData(bitmapData.width + 2, bitmapData.height + 2, false, 0xffffff); 89 | bitmapDataCopy.threshold(bitmapData, bitmapData.rect, new Point(1, 1), params.thresholdOperator, params.threshold, 0x000000, 0xffffff, false); 90 | 91 | this.bmWidth = bitmapDataCopy.width; 92 | this.bmHeight = bitmapDataCopy.height; 93 | 94 | var i:int; 95 | var j:int; 96 | var k:int; 97 | var pos:uint = 0; 98 | 99 | var bitmapDataVecTmp:Vector. = bitmapDataCopy.getVector(bitmapDataCopy.rect); 100 | var bitmapDataMatrix:Vector.> = new Vector.>(bmHeight); 101 | 102 | for (i = 0; i < bmHeight; i++) { 103 | var row:Vector. = bitmapDataVecTmp.slice(pos, pos + bmWidth); 104 | for (j = 0; j < row.length; j++) { 105 | row[j] &= 0xffffff; 106 | } 107 | bitmapDataMatrix[i] = row; 108 | pos += bmWidth; 109 | } 110 | 111 | var plist:Array = bm_to_pathlist(bitmapDataMatrix); 112 | 113 | process_path(plist); 114 | 115 | var shapes:Array = pathlist_to_curvearrayslist(plist); 116 | 117 | if(backend != null) 118 | { 119 | backend.init(bmWidth, bmHeight); 120 | 121 | for (i = 0; i < shapes.length; i++) { 122 | backend.initShape(); 123 | var shape:Array = shapes[i] as Array; 124 | for (j = 0; j < shape.length; j++) { 125 | backend.initSubShape((j % 2) == 0); 126 | var curves:Array = shape[j] as Array; 127 | if(curves.length > 0) { 128 | var curve:Curve = curves[0] as Curve; 129 | backend.moveTo(curve.a.clone()); 130 | for (k = 0; k < curves.length; k++) { 131 | curve = curves[k] as Curve; 132 | switch(curve.kind) { 133 | case CurveKind.BEZIER: 134 | backend.addBezier( 135 | curve.a.clone(), 136 | curve.cpa.clone(), 137 | curve.cpb.clone(), 138 | curve.b.clone() 139 | ); 140 | break; 141 | case CurveKind.LINE: 142 | backend.addLine( 143 | curve.a.clone(), 144 | curve.b.clone() 145 | ); 146 | break; 147 | } 148 | } 149 | } 150 | backend.exitSubShape(); 151 | } 152 | backend.exitShape(); 153 | } 154 | 155 | backend.exit(); 156 | } 157 | 158 | return shapes; 159 | } 160 | 161 | /* 162 | * Decompose the given bitmap into paths. Returns a list of 163 | * Path objects with the fields len, pt, area filled 164 | */ 165 | private function bm_to_pathlist(bitmapDataMatrix:Vector.>):Array 166 | { 167 | var plist:Array = []; 168 | var pt:PointInt; 169 | while ((pt = find_next(bitmapDataMatrix)) != null) { 170 | get_contour(bitmapDataMatrix, pt, plist); 171 | } 172 | return plist; 173 | } 174 | 175 | /* 176 | * Searches a point such that source[x, y] = true and source[x+1, y] = false. 177 | * If this not exists, null will be returned, else the result is Point(x, y). 178 | */ 179 | private function find_next(bitmapDataMatrix:Vector.>):PointInt 180 | { 181 | var x:int; 182 | var y:int; 183 | for (y = 1; y < bmHeight - 1; y++) { 184 | for (x = 0; x < bmWidth - 1; x++) { 185 | if (bitmapDataMatrix[y][x + 1] == 0) { 186 | // Black found 187 | return new PointInt(x, y); 188 | } 189 | } 190 | } 191 | return null; 192 | } 193 | 194 | private function get_contour(bitmapDataMatrix:Vector.>, pt:PointInt, plists:Array):void 195 | { 196 | var plist:Array = []; 197 | 198 | var path:Path = find_path(bitmapDataMatrix, pt); 199 | 200 | xor_path(bitmapDataMatrix, path); 201 | 202 | // Only area > turdsize is taken 203 | if (path.area > params.turdSize) { 204 | // Path with index 0 is a contour 205 | plist.push(path); 206 | plists.push(plist); 207 | } 208 | 209 | while ((pt = find_next_in_path(bitmapDataMatrix, path)) != null) 210 | { 211 | var hole:Path = find_path(bitmapDataMatrix, pt); 212 | 213 | xor_path(bitmapDataMatrix, hole); 214 | 215 | if (hole.area > params.turdSize) { 216 | // Path with index > 0 is a hole 217 | plist.push(hole); 218 | } 219 | 220 | if ((pt = find_next_in_path(bitmapDataMatrix, hole)) != null) { 221 | get_contour(bitmapDataMatrix, pt, plists); 222 | } 223 | } 224 | } 225 | 226 | /* 227 | * Compute a path in the binary matrix. 228 | * 229 | * Start path at the point (x0,x1), which must be an upper left corner 230 | * of the path. Also compute the area enclosed by the path. Return a 231 | * new path_t object, or NULL on error (note that a legitimate path 232 | * cannot have length 0). 233 | * 234 | * We omit turnpolicies and sign 235 | */ 236 | private function find_path(bitmapDataMatrix:Vector.>, start:PointInt):Path 237 | { 238 | var l:Vector. = new Vector.(); 239 | var p:PointInt = start.clone(); 240 | var dir:uint = Direction.NORTH; 241 | var area:int = 0; 242 | 243 | do 244 | { 245 | l.push(p.clone()); 246 | var _y:int = p.y; 247 | dir = find_next_trace(bitmapDataMatrix, p, dir); 248 | area += p.x * (_y - p.y); 249 | } 250 | while ((p.x != start.x) || (p.y != start.y)); 251 | 252 | if (l.length == 0) { 253 | return null; 254 | } 255 | 256 | var result:Path = new Path(); 257 | result.area = area; 258 | result.pt = new Vector.(l.length); 259 | for (var i:int = 0; i < l.length; i++) { 260 | result.pt[i] = l[i]; 261 | } 262 | 263 | // Shift 1 to be compatible with Potrace 264 | if(result.pt.length > 1) { 265 | result.pt.unshift(result.pt.pop()); 266 | } 267 | 268 | result.monotonIntervals = get_monoton_intervals(result.pt); 269 | 270 | return result; 271 | } 272 | 273 | /* 274 | * Searches a point inside a path such that source[x, y] = true and source[x+1, y] = false. 275 | * If this not exists, null will be returned, else the result is Point(x, y). 276 | */ 277 | private function find_next_in_path(bitmapDataMatrix:Vector.>, path:Path):PointInt 278 | { 279 | if (path.monotonIntervals.length == 0) { 280 | return null; 281 | } 282 | 283 | var i:int = 0; 284 | var n:int = path.pt.length; 285 | 286 | var mis:Vector. = path.monotonIntervals; 287 | var mi:MonotonInterval = mis[0]; 288 | mi.resetCurrentId(n); 289 | 290 | var y:int = path.pt[mi.currentId].y; 291 | 292 | var currentIntervals:Vector. = new Vector.(); 293 | currentIntervals[0] = mi; 294 | 295 | mi.currentId = mi.min(); 296 | 297 | while ((mis.length > i + 1) && (mis[i + 1].minY(path.pt) == y)) 298 | { 299 | mi = mis[i + 1]; 300 | mi.resetCurrentId(n); 301 | currentIntervals.push(mi); 302 | i++; 303 | } 304 | 305 | while (currentIntervals.length > 0) 306 | { 307 | var j:int; 308 | 309 | for (var k:int = 0; k < currentIntervals.length - 1; k++) 310 | { 311 | var x1:int = path.pt[currentIntervals[k].currentId].x + 1; 312 | var x2:int = path.pt[currentIntervals[k + 1].currentId].x; 313 | for (var x:int = x1; x <= x2; x++) { 314 | if (bitmapDataMatrix[y][x] == 0) { 315 | return new PointInt(x - 1, y); 316 | } 317 | } 318 | k++; 319 | } 320 | 321 | y++; 322 | for (j = currentIntervals.length - 1; j >= 0; j--) 323 | { 324 | var m:MonotonInterval = currentIntervals[j]; 325 | if (y > m.maxY(path.pt)) { 326 | currentIntervals.splice(j, 1); 327 | continue; 328 | } 329 | var cid:int = m.currentId; 330 | do 331 | { 332 | cid = m.increasing ? mod(cid + 1, n) : mod(cid - 1, n); 333 | } 334 | while (path.pt[cid].y < y); 335 | m.currentId = cid; 336 | } 337 | 338 | // Add Items of MonotonIntervals with Down.y==y 339 | while ((mis.length > i + 1) && (mis[i + 1].minY(path.pt) == y)) 340 | { 341 | var newInt:MonotonInterval = mis[i + 1]; 342 | // Search the correct x position 343 | j = 0; 344 | var _x:int = path.pt[newInt.min()].x; 345 | while ((currentIntervals.length > j) && (_x > path.pt[currentIntervals[j].currentId].x)) { 346 | j++; 347 | } 348 | currentIntervals.splice(j, 0, newInt); 349 | newInt.resetCurrentId(n); 350 | i++; 351 | } 352 | } 353 | return null; 354 | } 355 | 356 | private function xor_path(bitmapDataMatrix:Vector.>, path:Path):void 357 | { 358 | if (path.monotonIntervals.length == 0) { 359 | return; 360 | } 361 | 362 | var i:int = 0; 363 | var n:int = path.pt.length; 364 | 365 | var mis:Vector. = path.monotonIntervals; 366 | var mi:MonotonInterval = mis[0]; 367 | mi.resetCurrentId(n); 368 | 369 | var y:int = path.pt[mi.currentId].y; 370 | var currentIntervals:Vector. = new Vector.(); 371 | currentIntervals.push(mi); 372 | 373 | mi.currentId = mi.min(); 374 | 375 | while ((mis.length > i + 1) && (mis[i + 1].minY(path.pt) == y)) 376 | { 377 | mi = mis[i + 1]; 378 | mi.resetCurrentId(n); 379 | currentIntervals.push(mi); 380 | i++; 381 | } 382 | 383 | while (currentIntervals.length > 0) 384 | { 385 | var j:int; 386 | 387 | for (var k:int = 0; k < currentIntervals.length - 1; k++) 388 | { 389 | var x1:int = path.pt[currentIntervals[k].currentId].x + 1; 390 | var x2:int = path.pt[currentIntervals[k + 1].currentId].x; 391 | for (var x:int = x1; x <= x2; x++) { 392 | // Invert pixel 393 | bitmapDataMatrix[y][x] ^= 0xffffff; 394 | } 395 | k++; 396 | } 397 | 398 | y++; 399 | for (j = currentIntervals.length - 1; j >= 0; j--) 400 | { 401 | var m:MonotonInterval = currentIntervals[j]; 402 | if (y > m.maxY(path.pt)) { 403 | currentIntervals.splice(j, 1); 404 | continue; 405 | } 406 | var cid:int = m.currentId; 407 | do 408 | { 409 | cid = m.increasing ? mod(cid + 1, n) : mod(cid - 1, n); 410 | } 411 | while (path.pt[cid].y < y); 412 | m.currentId = cid; 413 | } 414 | 415 | // Add Items of MonotonIntervals with Down.y==y 416 | while ((mis.length > i + 1) && (mis[i + 1].minY(path.pt) == y)) 417 | { 418 | var newInt:MonotonInterval = mis[i + 1]; 419 | // Search the correct x position 420 | j = 0; 421 | var _x:int = path.pt[newInt.min()].x; 422 | while ((currentIntervals.length > j) && (_x > path.pt[currentIntervals[j].currentId].x)) { 423 | j++; 424 | } 425 | currentIntervals.splice(j, 0, newInt); 426 | newInt.resetCurrentId(n); 427 | i++; 428 | } 429 | } 430 | } 431 | 432 | private function get_monoton_intervals(pt:Vector.):Vector. 433 | { 434 | var result:Vector. = new Vector.(); 435 | var n:uint = pt.length; 436 | if (n == 0) { 437 | return result; 438 | } 439 | 440 | var intervals:Vector. = new Vector.(); 441 | 442 | // Start with Strong Monoton (Pts[i].y < Pts[i+1].y) or (Pts[i].y > Pts[i+1].y) 443 | var firstStrongMonoton:int = 0; 444 | while (pt[firstStrongMonoton].y == pt[firstStrongMonoton + 1].y) { 445 | firstStrongMonoton++; 446 | } 447 | 448 | var i:int = firstStrongMonoton; 449 | var up:Boolean = (pt[firstStrongMonoton].y < pt[firstStrongMonoton + 1].y); 450 | var interval:MonotonInterval = new MonotonInterval(up, firstStrongMonoton, firstStrongMonoton); 451 | intervals.push(interval); 452 | 453 | do 454 | { 455 | var i1n:int = mod(i + 1, n); 456 | if ((pt[i].y == pt[i1n].y) || (up == (pt[i].y < pt[i1n].y))) { 457 | interval.to = i; 458 | } else { 459 | up = (pt[i].y < pt[i1n].y); 460 | interval = new MonotonInterval(up, i, i); 461 | intervals.push(interval); 462 | } 463 | i = i1n; 464 | } 465 | while (i != firstStrongMonoton); 466 | 467 | if ((intervals.length & 1) == 1) { 468 | var last:MonotonInterval = intervals.pop(); 469 | intervals[0].from = last.from; 470 | } 471 | 472 | while (intervals.length > 0) 473 | { 474 | i = 0; 475 | var m:MonotonInterval = intervals.shift(); 476 | while ((i < result.length) && (pt[m.min()].y > pt[result[i].min()].y)) { 477 | i++; 478 | } 479 | while ((i < result.length) && (pt[m.min()].y == pt[result[i].min()].y) && (pt[m.min()].x > pt[result[i].min()].x)) { 480 | i++; 481 | } 482 | result.splice(i, 0, m); 483 | } 484 | 485 | return result; 486 | } 487 | 488 | private function find_next_trace(bitmapDataMatrix:Vector.>, p:PointInt, dir:uint):uint 489 | { 490 | switch(dir) 491 | { 492 | case Direction.WEST: 493 | if (bitmapDataMatrix[p.y + 1][p.x + 1] == 0) { 494 | dir = Direction.NORTH; 495 | p.y++; 496 | } else { 497 | if (bitmapDataMatrix[p.y][p.x + 1] == 0) { 498 | dir = Direction.WEST; 499 | p.x++; 500 | } else { 501 | dir = Direction.SOUTH; 502 | p.y--; 503 | } 504 | } 505 | break; 506 | 507 | case Direction.SOUTH: 508 | if (bitmapDataMatrix[p.y][p.x + 1] == 0) { 509 | dir = Direction.WEST; 510 | p.x++; 511 | } else { 512 | if (bitmapDataMatrix[p.y][p.x] == 0) { 513 | dir = Direction.SOUTH; 514 | p.y--; 515 | } else { 516 | dir = Direction.EAST; 517 | p.x--; 518 | } 519 | } 520 | break; 521 | 522 | case Direction.EAST: 523 | if (bitmapDataMatrix[p.y][p.x] == 0) { 524 | dir = Direction.SOUTH; 525 | p.y--; 526 | } else { 527 | if (bitmapDataMatrix[p.y + 1][p.x] == 0) { 528 | dir = Direction.EAST; 529 | p.x--; 530 | } else { 531 | dir = Direction.NORTH; 532 | p.y++; 533 | } 534 | } 535 | break; 536 | 537 | case Direction.NORTH: 538 | if (bitmapDataMatrix[p.y + 1][p.x] == 0) { 539 | dir = Direction.EAST; 540 | p.x--; 541 | } else { 542 | if (bitmapDataMatrix[p.y + 1][p.x + 1] == 0) { 543 | dir = Direction.NORTH; 544 | p.y++; 545 | } else { 546 | dir = Direction.WEST; 547 | p.x++; 548 | } 549 | } 550 | break; 551 | } 552 | return dir; 553 | } 554 | 555 | private function process_path(plists:Array):void 556 | { 557 | // call downstream function with each path 558 | for (var j:int = 0; j < plists.length; j++) { 559 | var plist:Array = plists[j] as Array; 560 | for (var i:int = 0; i < plist.length; i++) { 561 | var path:Path = plist[i] as Path; 562 | calc_sums(path); 563 | calc_lon(path); 564 | bestpolygon(path); 565 | adjust_vertices(path); 566 | smooth(path.curves, 1, params.alphaMax); 567 | if (params.curveOptimizing) { 568 | opticurve(path, params.optTolerance); 569 | path.fCurves = path.optimizedCurves; 570 | } else { 571 | path.fCurves = path.curves; 572 | } 573 | path.curves = path.fCurves; 574 | } 575 | } 576 | } 577 | 578 | ///////////////////////////////////////////////////////////////////////// 579 | // PREPARATION 580 | ///////////////////////////////////////////////////////////////////////// 581 | 582 | /* 583 | * Fill in the sum* fields of a path (used for later rapid summing) 584 | */ 585 | private function calc_sums(path:Path):void 586 | { 587 | var n:int = path.pt.length; 588 | 589 | // Origin 590 | var x0:int = path.pt[0].x; 591 | var y0:int = path.pt[0].y; 592 | 593 | path.sums = new Vector.(n + 1); 594 | 595 | var ss:SumStruct = new SumStruct(); 596 | ss.x2 = ss.xy = ss.y2 = ss.x = ss.y = 0; 597 | path.sums[0] = ss; 598 | 599 | for (var i:int = 0; i < n; i++) { 600 | var x:int = path.pt[i].x - x0; 601 | var y:int = path.pt[i].y - y0; 602 | ss = new SumStruct(); 603 | ss.x = path.sums[i].x + x; 604 | ss.y = path.sums[i].y + y; 605 | ss.x2 = path.sums[i].x2 + x * x; 606 | ss.xy = path.sums[i].xy + x * y; 607 | ss.y2 = path.sums[i].y2 + y * y; 608 | path.sums[i + 1] = ss; 609 | } 610 | } 611 | 612 | ///////////////////////////////////////////////////////////////////////// 613 | // STAGE 1 614 | // determine the straight subpaths (Sec. 2.2.1). 615 | ///////////////////////////////////////////////////////////////////////// 616 | 617 | /* 618 | * Fill in the "lon" component of a path object (based on pt/len). 619 | * For each i, lon[i] is the furthest index such that a straight line 620 | * can be drawn from i to lon[i]. 621 | * 622 | * This algorithm depends on the fact that the existence of straight 623 | * subpaths is a triplewise property. I.e., there exists a straight 624 | * line through squares i0,...,in if there exists a straight line 625 | * through i,j,k, for all i0 <= i < j < k <= in. (Proof?) 626 | */ 627 | private function calc_lon(path:Path):void 628 | { 629 | var i:int; 630 | var j:int; 631 | var k:int; 632 | var k1:int; 633 | var a:int; 634 | var b:int; 635 | var c:int; 636 | var d:int; 637 | var dir:int; 638 | var ct:Vector. = new Vector.(4); 639 | var constraint:Vector. = new Vector.(2); 640 | constraint[0] = new PointInt(); 641 | constraint[1] = new PointInt(); 642 | var cur:PointInt = new PointInt(); 643 | var off:PointInt = new PointInt(); 644 | var dk:PointInt = new PointInt(); // direction of k - k1 645 | var pt:Vector. = path.pt; 646 | 647 | var n:int = pt.length; 648 | var pivot:Vector. = new Vector.(n); 649 | var nc:Vector. = new Vector.(n); 650 | 651 | // Initialize the nc data structure. Point from each point to the 652 | // furthest future point to which it is connected by a vertical or 653 | // horizontal segment. We take advantage of the fact that there is 654 | // always a direction change at 0 (due to the path decomposition 655 | // algorithm). But even if this were not so, there is no harm, as 656 | // in practice, correctness does not depend on the word "furthest" 657 | // above. 658 | 659 | k = 0; 660 | for (i = n - 1; i >= 0; i--) 661 | { 662 | if (pt[i].x != pt[k].x && pt[i].y != pt[k].y) { 663 | k = i + 1; // necessarily i < n-1 in this case 664 | } 665 | nc[i] = k; 666 | } 667 | 668 | path.lon = new Vector.(n); 669 | 670 | // Determine pivot points: 671 | // for each i, let pivot[i] be the furthest k such that 672 | // all j with i < j < k lie on a line connecting i,k 673 | 674 | for (i = n - 1; i >= 0; i--) 675 | { 676 | ct[0] = ct[1] = ct[2] = ct[3] = 0; 677 | 678 | // Keep track of "directions" that have occurred 679 | dir = (3 + 3 * (pt[mod(i + 1, n)].x - pt[i].x) + (pt[mod(i + 1, n)].y - pt[i].y)) / 2; 680 | ct[dir % 4]++; 681 | 682 | constraint[0].x = 0; 683 | constraint[0].y = 0; 684 | constraint[1].x = 0; 685 | constraint[1].y = 0; 686 | 687 | // Find the next k such that no straight line from i to k 688 | k = nc[i]; 689 | k1 = i; 690 | 691 | var foundk:Boolean = false; 692 | while (true) 693 | { 694 | dir = (3 + 3 * sign(pt[k].x - pt[k1].x) + sign(pt[k].y - pt[k1].y)) / 2; 695 | ct[dir]++; 696 | 697 | // If all four "directions" have occurred, cut this path 698 | if ((ct[0] >= 1) && (ct[1] >= 1) && (ct[2] >= 1) && (ct[3] >= 1)) { 699 | pivot[i] = k1; 700 | foundk = true; 701 | break; 702 | } 703 | 704 | cur.x = pt[k].x - pt[i].x; 705 | cur.y = pt[k].y - pt[i].y; 706 | 707 | // See if current constraint is violated 708 | if (xprod(constraint[0], cur) < 0 || xprod(constraint[1], cur) > 0) { 709 | break; 710 | } 711 | 712 | if (abs(cur.x) <= 1 && abs(cur.y) <= 1) { 713 | // no constraint 714 | } else { 715 | off.x = cur.x + ((cur.y >= 0 && (cur.y > 0 || cur.x < 0)) ? 1 : -1); 716 | off.y = cur.y + ((cur.x <= 0 && (cur.x < 0 || cur.y < 0)) ? 1 : -1); 717 | if (xprod(constraint[0], off) >= 0) { 718 | constraint[0] = off.clone(); 719 | } 720 | off.x = cur.x + ((cur.y <= 0 && (cur.y < 0 || cur.x < 0)) ? 1 : -1); 721 | off.y = cur.y + ((cur.x >= 0 && (cur.x > 0 || cur.y < 0)) ? 1 : -1); 722 | if (xprod(constraint[1], off) <= 0) { 723 | constraint[1] = off.clone(); 724 | } 725 | } 726 | 727 | k1 = k; 728 | k = nc[k1]; 729 | if (!cyclic(k, i, k1)) { 730 | break; 731 | } 732 | } 733 | 734 | if(foundk) { 735 | continue; 736 | } 737 | 738 | // k1 was the last "corner" satisfying the current constraint, and 739 | // k is the first one violating it. We now need to find the last 740 | // point along k1..k which satisfied the constraint. 741 | dk.x = sign(pt[k].x - pt[k1].x); 742 | dk.y = sign(pt[k].y - pt[k1].y); 743 | cur.x = pt[k1].x - pt[i].x; 744 | cur.y = pt[k1].y - pt[i].y; 745 | 746 | // find largest integer j such that xprod(constraint[0], cur+j*dk) 747 | // >= 0 and xprod(constraint[1], cur+j*dk) <= 0. Use bilinearity 748 | // of xprod. 749 | a = xprod(constraint[0], cur); 750 | b = xprod(constraint[0], dk); 751 | c = xprod(constraint[1], cur); 752 | d = xprod(constraint[1], dk); 753 | 754 | // find largest integer j such that a+j*b >= 0 and c+j*d <= 0. This 755 | // can be solved with integer arithmetic. 756 | j = int.MAX_VALUE; 757 | if (b < 0) { 758 | j = floordiv(a, -b); 759 | } 760 | if (d > 0) { 761 | j = min(j, floordiv(-c, d)); 762 | } 763 | pivot[i] = mod(k1 + j, n); 764 | } 765 | 766 | // Clean up: 767 | // for each i, let lon[i] be the largest k such that 768 | // for all i' with i <= i' < k, i' < k <= pivk[i']. */ 769 | 770 | j = pivot[n - 1]; 771 | path.lon[n - 1] = j; 772 | 773 | for (i = n - 2; i >= 0; i--) { 774 | if (cyclic(i + 1, pivot[i], j)) { 775 | j = pivot[i]; 776 | } 777 | path.lon[i] = j; 778 | } 779 | 780 | for (i = n - 1; cyclic(mod(i + 1, n), j, path.lon[i]); i--) { 781 | path.lon[i] = j; 782 | } 783 | } 784 | 785 | ///////////////////////////////////////////////////////////////////////// 786 | // STAGE 2 787 | // Calculate the optimal polygon (Sec. 2.2.2 - 2.2.4). 788 | ///////////////////////////////////////////////////////////////////////// 789 | 790 | /* 791 | * Auxiliary function: calculate the penalty of an edge from i to j in 792 | * the given path. This needs the "lon" and "sum*" data. 793 | */ 794 | private function penalty3(path:Path, i:int, j:int):Number 795 | { 796 | var n:int = path.pt.length; 797 | 798 | // assume 0 <= i < j <= n 799 | var sums:Vector. = path.sums; 800 | var pt:Vector. = path.pt; 801 | 802 | var r:int = 0; // rotations from i to j 803 | if (j >= n) { 804 | j -= n; 805 | r++; 806 | } 807 | 808 | var x:Number = sums[j + 1].x - sums[i].x + r * sums[n].x; 809 | var y:Number = sums[j + 1].y - sums[i].y + r * sums[n].y; 810 | var x2:Number = sums[j + 1].x2 - sums[i].x2 + r * sums[n].x2; 811 | var xy:Number = sums[j + 1].xy - sums[i].xy + r * sums[n].xy; 812 | var y2:Number = sums[j + 1].y2 - sums[i].y2 + r * sums[n].y2; 813 | var k:Number = j + 1 - i + r * n; 814 | 815 | var px:Number = (pt[i].x + pt[j].x) / 2.0 - pt[0].x; 816 | var py:Number = (pt[i].y + pt[j].y) / 2.0 - pt[0].y; 817 | var ey:Number = (pt[j].x - pt[i].x); 818 | var ex:Number = -(pt[j].y - pt[i].y); 819 | 820 | var a:Number = ((x2 - 2 * x * px) / k + px * px); 821 | var b:Number = ((xy - x * py - y * px) / k + px * py); 822 | var c:Number = ((y2 - 2 * y * py) / k + py * py); 823 | 824 | return Math.sqrt(ex * ex * a + 2 * ex * ey * b + ey * ey * c); 825 | } 826 | 827 | /* 828 | * Find the optimal polygon. 829 | */ 830 | private function bestpolygon(path:Path):void 831 | { 832 | var i:int; 833 | var j:int; 834 | var m:int; 835 | var k:int; 836 | var n:int = path.pt.length; 837 | var pen:Vector. = new Vector.(n + 1); // penalty vector 838 | var prev:Vector. = new Vector.(n + 1); // best path pointer vector 839 | var clip0:Vector. = new Vector.(n); // longest segment pointer, non-cyclic 840 | var clip1:Vector. = new Vector.(n + 1); // backwards segment pointer, non-cyclic 841 | var seg0:Vector. = new Vector.(n + 1); // forward segment bounds, m <= n 842 | var seg1:Vector. = new Vector.(n + 1); // backward segment bounds, m <= n 843 | 844 | var thispen:Number; 845 | var best:Number; 846 | var c:int; 847 | 848 | // Calculate clipped paths 849 | for (i = 0; i < n; i++) { 850 | c = mod(path.lon[mod(i - 1, n)] - 1, n); 851 | if (c == i) { 852 | c = mod(i + 1, n); 853 | } 854 | clip0[i] = (c < i) ? n : c; 855 | } 856 | 857 | // calculate backwards path clipping, non-cyclic. 858 | // j <= clip0[i] iff clip1[j] <= i, for i,j = 0..n 859 | j = 1; 860 | for (i = 0; i < n; i++) { 861 | while (j <= clip0[i]) { 862 | clip1[j] = i; 863 | j++; 864 | } 865 | } 866 | 867 | // calculate seg0[j] = longest path from 0 with j segments 868 | i = 0; 869 | for (j = 0; i < n; j++) { 870 | seg0[j] = i; 871 | i = clip0[i]; 872 | } 873 | seg0[j] = n; 874 | 875 | // calculate seg1[j] = longest path to n with m-j segments 876 | i = n; 877 | m = j; 878 | for (j = m; j > 0; j--) { 879 | seg1[j] = i; 880 | i = clip1[i]; 881 | } 882 | seg1[0] = 0; 883 | 884 | // Now find the shortest path with m segments, based on penalty3 885 | // Note: the outer 2 loops jointly have at most n interations, thus 886 | // the worst-case behavior here is quadratic. In practice, it is 887 | // close to linear since the inner loop tends to be short. 888 | pen[0] = 0; 889 | for (j = 1; j <= m; j++) { 890 | for (i = seg1[j]; i <= seg0[j]; i++) { 891 | best = -1; 892 | for (k = seg0[j - 1]; k >= clip1[i]; k--) { 893 | thispen = penalty3(path, k, i) + pen[k]; 894 | if (best < 0 || thispen < best) { 895 | prev[i] = k; 896 | best = thispen; 897 | } 898 | } 899 | pen[i] = best; 900 | } 901 | } 902 | 903 | // read off shortest path 904 | path.po = new Vector.(m); 905 | for (i = n, j = m - 1; i > 0; j--) { 906 | i = prev[i]; 907 | path.po[j] = i; 908 | } 909 | } 910 | 911 | ///////////////////////////////////////////////////////////////////////// 912 | // STAGE 3 913 | // Vertex adjustment (Sec. 2.3.1). 914 | ///////////////////////////////////////////////////////////////////////// 915 | 916 | /* 917 | * Adjust vertices of optimal polygon: calculate the intersection of 918 | * the two "optimal" line segments, then move it into the unit square 919 | * if it lies outside. 920 | */ 921 | private function adjust_vertices(path:Path):void 922 | { 923 | var pt:Vector. = path.pt; 924 | var po:Vector. = path.po; 925 | 926 | var n:int = pt.length; 927 | var m:int = po.length; 928 | 929 | var x0:int = pt[0].x; 930 | var y0:int = pt[0].y; 931 | 932 | var i:int; 933 | var j:int; 934 | var k:int; 935 | var l:int; 936 | 937 | var d:Number; 938 | var v:Vector. = new Vector.(3); 939 | var q:Vector.>> = new Vector.>>(m); 940 | 941 | var ctr:Vector. = new Vector.(m); 942 | var dir:Vector. = new Vector.(m); 943 | 944 | for (i = 0; i < m; i++) { 945 | q[i] = new Vector.>(3); 946 | for (j = 0; j < 3; j++) { 947 | q[i][j] = new Vector.(3); 948 | } 949 | ctr[i] = new Point(); 950 | dir[i] = new Point(); 951 | } 952 | 953 | var s:Point = new Point(); 954 | 955 | path.curves = new PrivCurve(m); 956 | 957 | // calculate "optimal" point-slope representation for each line segment 958 | for (i = 0; i < m; i++) { 959 | j = po[mod(i + 1, m)]; 960 | j = mod(j - po[i], n) + po[i]; 961 | pointslope(path, po[i], j, ctr[i], dir[i]); 962 | } 963 | 964 | // represent each line segment as a singular quadratic form; 965 | // the distance of a point (x,y) from the line segment will be 966 | // (x,y,1)Q(x,y,1)^t, where Q=q[i] 967 | for (i = 0; i < m; i++) { 968 | d = dir[i].x * dir[i].x + dir[i].y * dir[i].y; 969 | if (d == 0) { 970 | for (j = 0; j < 3; j++) { 971 | for (k = 0; k < 3; k++) { 972 | q[i][j][k] = 0; 973 | } 974 | } 975 | } else { 976 | v[0] = dir[i].y; 977 | v[1] = -dir[i].x; 978 | v[2] = -v[1] * ctr[i].y - v[0] * ctr[i].x; 979 | for (l = 0; l < 3; l++) { 980 | for (k = 0; k < 3; k++) { 981 | q[i][l][k] = v[l] * v[k] / d; 982 | } 983 | } 984 | } 985 | } 986 | 987 | // now calculate the "intersections" of consecutive segments. 988 | // Instead of using the actual intersection, we find the point 989 | // within a given unit square which minimizes the square distance to 990 | // the two lines. 991 | for (i = 0; i < m; i++) 992 | { 993 | var Q:Vector.> = new Vector.>(3); 994 | var w:Point = new Point(); 995 | var dx:Number; 996 | var dy:Number; 997 | var det:Number; 998 | var min:Number; // minimum for minimum of quad. form 999 | var cand:Number; // candidate for minimum of quad. form 1000 | var xmin:Number; // coordinate of minimum 1001 | var ymin:Number; // coordinate of minimum 1002 | var z:int; 1003 | 1004 | for (j = 0; j < 3; j++) { 1005 | Q[j] = new Vector.(3); 1006 | } 1007 | 1008 | // let s be the vertex, in coordinates relative to x0/y0 1009 | s.x = pt[po[i]].x - x0; 1010 | s.y = pt[po[i]].y - y0; 1011 | 1012 | // intersect segments i-1 and i 1013 | j = mod(i - 1, m); 1014 | 1015 | // add quadratic forms 1016 | for (l = 0; l < 3; l++) { 1017 | for (k = 0; k < 3; k++) { 1018 | Q[l][k] = q[j][l][k] + q[i][l][k]; 1019 | } 1020 | } 1021 | 1022 | while (true) 1023 | { 1024 | /* minimize the quadratic form Q on the unit square */ 1025 | /* find intersection */ 1026 | det = Q[0][0] * Q[1][1] - Q[0][1] * Q[1][0]; 1027 | if (det != 0) { 1028 | w.x = (-Q[0][2] * Q[1][1] + Q[1][2] * Q[0][1]) / det; 1029 | w.y = (Q[0][2] * Q[1][0] - Q[1][2] * Q[0][0]) / det; 1030 | break; 1031 | } 1032 | 1033 | // matrix is singular - lines are parallel. Add another, 1034 | // orthogonal axis, through the center of the unit square 1035 | if (Q[0][0] > Q[1][1]) { 1036 | v[0] = -Q[0][1]; 1037 | v[1] = Q[0][0]; 1038 | } else if (Q[1][1] != 0) { 1039 | v[0] = -Q[1][1]; 1040 | v[1] = Q[1][0]; 1041 | } else { 1042 | v[0] = 1; 1043 | v[1] = 0; 1044 | } 1045 | 1046 | d = v[0] * v[0] + v[1] * v[1]; 1047 | v[2] = -v[1] * s.y - v[0] * s.x; 1048 | for (l = 0; l < 3; l++) { 1049 | for (k = 0; k < 3; k++) { 1050 | Q[l][k] += v[l] * v[k] / d; 1051 | } 1052 | } 1053 | } 1054 | 1055 | dx = Math.abs(w.x - s.x); 1056 | dy = Math.abs(w.y - s.y); 1057 | if (dx <= 0.5 && dy <= 0.5) { 1058 | // - 1 because we have a additional border set to the bitmap 1059 | path.curves.vertex[i] = new Point(w.x + x0, w.y + y0); 1060 | continue; 1061 | } 1062 | 1063 | // the minimum was not in the unit square; 1064 | // now minimize quadratic on boundary of square 1065 | min = quadform(Q, s); 1066 | xmin = s.x; 1067 | ymin = s.y; 1068 | 1069 | if (Q[0][0] != 0) { 1070 | for (z = 0; z < 2; z++) { 1071 | // value of the y-coordinate 1072 | w.y = s.y - 0.5 + z; 1073 | w.x = -(Q[0][1] * w.y + Q[0][2]) / Q[0][0]; 1074 | dx = Math.abs(w.x - s.x); 1075 | cand = quadform(Q, w); 1076 | if (dx <= 0.5 && cand < min) { 1077 | min = cand; 1078 | xmin = w.x; 1079 | ymin = w.y; 1080 | } 1081 | } 1082 | } 1083 | 1084 | if (Q[1][1] != 0) { 1085 | for (z = 0; z < 2; z++) 1086 | { 1087 | // value of the x-coordinate 1088 | w.x = s.x - 0.5 + z; 1089 | w.y = -(Q[1][0] * w.x + Q[1][2]) / Q[1][1]; 1090 | dy = Math.abs(w.y - s.y); 1091 | cand = quadform(Q, w); 1092 | if (dy <= 0.5 && cand < min) { 1093 | min = cand; 1094 | xmin = w.x; 1095 | ymin = w.y; 1096 | } 1097 | } 1098 | } 1099 | 1100 | // check four corners 1101 | for (l = 0; l < 2; l++) { 1102 | for (k = 0; k < 2; k++) { 1103 | w.x = s.x - 0.5 + l; 1104 | w.y = s.y - 0.5 + k; 1105 | cand = quadform(Q, w); 1106 | if (cand < min) { 1107 | min = cand; 1108 | xmin = w.x; 1109 | ymin = w.y; 1110 | } 1111 | } 1112 | } 1113 | 1114 | // - 1 because we have a additional border set to the bitmap 1115 | path.curves.vertex[i] = new Point(xmin + x0 - 1, ymin + y0 - 1); 1116 | continue; 1117 | } 1118 | } 1119 | 1120 | ///////////////////////////////////////////////////////////////////////// 1121 | // STAGE 4 1122 | // Smoothing and corner analysis (Sec. 2.3.3). 1123 | ///////////////////////////////////////////////////////////////////////// 1124 | 1125 | private function smooth(curve:PrivCurve, sign:int, alphaMax:Number):void 1126 | { 1127 | var m:int = curve.n; 1128 | 1129 | var i:int; 1130 | var j:int; 1131 | var k:int; 1132 | var dd:Number; 1133 | var denom:Number; 1134 | var alpha:Number; 1135 | 1136 | var p2:Point; 1137 | var p3:Point; 1138 | var p4:Point; 1139 | 1140 | if (sign < 0) { 1141 | /* reverse orientation of negative paths */ 1142 | for (i = 0, j = m - 1; i < j; i++, j--) { 1143 | var tmp:Point = curve.vertex[i]; 1144 | curve.vertex[i] = curve.vertex[j]; 1145 | curve.vertex[j] = tmp; 1146 | } 1147 | } 1148 | 1149 | /* examine each vertex and find its best fit */ 1150 | for (i = 0; i < m; i++) 1151 | { 1152 | j = mod(i + 1, m); 1153 | k = mod(i + 2, m); 1154 | p4 = interval(1 / 2.0, curve.vertex[k], curve.vertex[j]); 1155 | 1156 | denom = ddenom(curve.vertex[i], curve.vertex[k]); 1157 | if (denom != 0) { 1158 | dd = dpara(curve.vertex[i], curve.vertex[j], curve.vertex[k]) / denom; 1159 | dd = Math.abs(dd); 1160 | alpha = (dd > 1) ? (1 - 1.0 / dd) : 0; 1161 | alpha = alpha / 0.75; 1162 | } else { 1163 | alpha = 4 / 3; 1164 | } 1165 | 1166 | // remember "original" value of alpha */ 1167 | curve.alpha0[j] = alpha; 1168 | 1169 | if (alpha > alphaMax) { 1170 | // pointed corner 1171 | curve.tag[j] = POTRACE_CORNER; 1172 | curve.controlPoints[j][1] = curve.vertex[j]; 1173 | curve.controlPoints[j][2] = p4; 1174 | } else { 1175 | if (alpha < 0.55) { 1176 | alpha = 0.55; 1177 | } else if (alpha > 1) { 1178 | alpha = 1; 1179 | } 1180 | p2 = interval(.5 + .5 * alpha, curve.vertex[i], curve.vertex[j]); 1181 | p3 = interval(.5 + .5 * alpha, curve.vertex[k], curve.vertex[j]); 1182 | curve.tag[j] = POTRACE_CURVETO; 1183 | curve.controlPoints[j][0] = p2; 1184 | curve.controlPoints[j][1] = p3; 1185 | curve.controlPoints[j][2] = p4; 1186 | } 1187 | // store the "cropped" value of alpha 1188 | curve.alpha[j] = alpha; 1189 | curve.beta[j] = 0.5; 1190 | } 1191 | } 1192 | 1193 | ///////////////////////////////////////////////////////////////////////// 1194 | // STAGE 5 1195 | // Curve optimization (Sec. 2.4). 1196 | ///////////////////////////////////////////////////////////////////////// 1197 | 1198 | /* 1199 | * Optimize the path p, replacing sequences of Bezier segments by a 1200 | * single segment when possible. 1201 | */ 1202 | private function opticurve(path:Path, optTolerance:Number):void 1203 | { 1204 | var m:int = path.curves.n; 1205 | var pt:Vector. = new Vector.(m); 1206 | var pen:Vector. = new Vector.(m + 1); 1207 | var len:Vector. = new Vector.(m + 1); 1208 | var opt:Vector. = new Vector.(m + 1); 1209 | var convc:Vector. = new Vector.(m); 1210 | var areac:Vector. = new Vector.(m + 1); 1211 | 1212 | var i:int; 1213 | var j:int; 1214 | var area:Number; 1215 | var alpha:Number; 1216 | var p0:Point; 1217 | var i1:int; 1218 | var o:Opti = new Opti(); 1219 | var r:Boolean; 1220 | 1221 | // Pre-calculate convexity: +1 = right turn, -1 = left turn, 0 = corner 1222 | for (i = 0; i < m; i++) { 1223 | if(path.curves.tag[i] == POTRACE_CURVETO) { 1224 | convc[i] = sign(dpara(path.curves.vertex[mod(i - 1, m)], path.curves.vertex[i], path.curves.vertex[mod(i + 1, m)])); 1225 | } else { 1226 | convc[i] = 0; 1227 | } 1228 | } 1229 | 1230 | // Pre-calculate areas 1231 | area = 0; 1232 | areac[0] = 0; 1233 | p0 = path.curves.vertex[0]; 1234 | for (i = 0; i < m; i++) { 1235 | i1 = mod(i + 1, m); 1236 | if (path.curves.tag[i1] == POTRACE_CURVETO) { 1237 | alpha = path.curves.alpha[i1]; 1238 | area += 0.3 * alpha * (4 - alpha) * dpara(path.curves.controlPoints[i][2], path.curves.vertex[i1], path.curves.controlPoints[i1][2]) / 2; 1239 | area += dpara(p0, path.curves.controlPoints[i][2], path.curves.controlPoints[i1][2]) / 2; 1240 | } 1241 | areac[i + 1] = area; 1242 | } 1243 | 1244 | pt[0] = -1; 1245 | pen[0] = 0; 1246 | len[0] = 0; 1247 | 1248 | // Fixme: 1249 | // We always start from a fixed point -- should find the best curve cyclically ### 1250 | 1251 | for (j = 1; j <= m; j++) 1252 | { 1253 | // Calculate best path from 0 to j 1254 | pt[j] = j - 1; 1255 | pen[j] = pen[j - 1]; 1256 | len[j] = len[j - 1] + 1; 1257 | 1258 | for (i = j - 2; i >= 0; i--) { 1259 | r = opti_penalty(path, i, mod(j, m), o, optTolerance, convc, areac); 1260 | if (r) { 1261 | break; 1262 | } 1263 | if (len[j] > len[i] + 1 || (len[j] == len[i] + 1 && pen[j] > pen[i] + o.pen)) { 1264 | pt[j] = i; 1265 | pen[j] = pen[i] + o.pen; 1266 | len[j] = len[i] + 1; 1267 | opt[j] = o.clone(); 1268 | } 1269 | } 1270 | } 1271 | 1272 | var om:int = len[m]; 1273 | 1274 | path.optimizedCurves = new PrivCurve(om); 1275 | 1276 | var s:Vector. = new Vector.(om); 1277 | var t:Vector. = new Vector.(om); 1278 | 1279 | j = m; 1280 | for (i = om - 1; i >= 0; i--) { 1281 | var jm:int = mod(j, m); 1282 | if (pt[j] == j - 1) { 1283 | path.optimizedCurves.tag[i] = path.curves.tag[jm]; 1284 | path.optimizedCurves.controlPoints[i][0] = path.curves.controlPoints[jm][0]; 1285 | path.optimizedCurves.controlPoints[i][1] = path.curves.controlPoints[jm][1]; 1286 | path.optimizedCurves.controlPoints[i][2] = path.curves.controlPoints[jm][2]; 1287 | path.optimizedCurves.vertex[i] = path.curves.vertex[jm]; 1288 | path.optimizedCurves.alpha[i] = path.curves.alpha[jm]; 1289 | path.optimizedCurves.alpha0[i] = path.curves.alpha0[jm]; 1290 | path.optimizedCurves.beta[i] = path.curves.beta[jm]; 1291 | s[i] = t[i] = 1; 1292 | } else { 1293 | path.optimizedCurves.tag[i] = POTRACE_CURVETO; 1294 | path.optimizedCurves.controlPoints[i][0] = opt[j].c[0]; 1295 | path.optimizedCurves.controlPoints[i][1] = opt[j].c[1]; 1296 | path.optimizedCurves.controlPoints[i][2] = path.curves.controlPoints[jm][2]; 1297 | path.optimizedCurves.vertex[i] = interval(opt[j].s, path.curves.controlPoints[jm][2], path.curves.vertex[jm]); 1298 | path.optimizedCurves.alpha[i] = opt[j].alpha; 1299 | path.optimizedCurves.alpha0[i] = opt[j].alpha; 1300 | s[i] = opt[j].s; 1301 | t[i] = opt[j].t; 1302 | } 1303 | j = pt[j]; 1304 | } 1305 | 1306 | /* Calculate beta parameters */ 1307 | for (i = 0; i < om; i++) { 1308 | i1 = mod(i + 1, om); 1309 | path.optimizedCurves.beta[i] = s[i] / (s[i] + t[i1]); 1310 | } 1311 | } 1312 | 1313 | /* 1314 | * Calculate best fit from i+.5 to j+.5. Assume i, areac:Vector.):Boolean 1319 | { 1320 | var m:int = path.curves.n; 1321 | var k:int; 1322 | var k1:int; 1323 | var k2:int; 1324 | var conv:int; 1325 | var i1:int; 1326 | var area:Number; 1327 | var d:Number; 1328 | var d1:Number; 1329 | var d2:Number; 1330 | var pt:Point; 1331 | 1332 | if(i == j) { 1333 | // sanity - a full loop can never be an opticurve 1334 | return true; 1335 | } 1336 | 1337 | k = i; 1338 | i1 = mod(i + 1, m); 1339 | k1 = mod(k + 1, m); 1340 | conv = convc[k1]; 1341 | if (conv == 0) { 1342 | return true; 1343 | } 1344 | d = ddist(path.curves.vertex[i], path.curves.vertex[i1]); 1345 | for (k = k1; k != j; k = k1) { 1346 | k1 = mod(k + 1, m); 1347 | k2 = mod(k + 2, m); 1348 | if (convc[k1] != conv) { 1349 | return true; 1350 | } 1351 | if (sign(cprod(path.curves.vertex[i], path.curves.vertex[i1], path.curves.vertex[k1], path.curves.vertex[k2])) != conv) { 1352 | return true; 1353 | } 1354 | if (iprod1(path.curves.vertex[i], path.curves.vertex[i1], path.curves.vertex[k1], path.curves.vertex[k2]) < d * ddist(path.curves.vertex[k1], path.curves.vertex[k2]) * COS179) { 1355 | return true; 1356 | } 1357 | } 1358 | 1359 | // the curve we're working in: 1360 | var p0:Point = path.curves.controlPoints[mod(i, m)][2]; 1361 | var p1:Point = path.curves.vertex[mod(i + 1, m)]; 1362 | var p2:Point = path.curves.vertex[mod(j, m)]; 1363 | var p3:Point = path.curves.controlPoints[mod(j, m)][2]; 1364 | 1365 | // determine its area 1366 | area = areac[j] - areac[i]; 1367 | area -= dpara(path.curves.vertex[0], path.curves.controlPoints[i][2], path.curves.controlPoints[j][2]) / 2; 1368 | if (i >= j) { 1369 | area += areac[m]; 1370 | } 1371 | 1372 | // find intersection o of p0p1 and p2p3. 1373 | // Let t,s such that o = interval(t, p0, p1) = interval(s, p3, p2). 1374 | // Let A be the area of the triangle (p0, o, p3). 1375 | 1376 | var A1:Number = dpara(p0, p1, p2); 1377 | var A2:Number = dpara(p0, p1, p3); 1378 | var A3:Number = dpara(p0, p2, p3); 1379 | var A4:Number = A1 + A3 - A2; 1380 | 1381 | if (A2 == A1) { 1382 | // this should never happen 1383 | return true; 1384 | } 1385 | 1386 | var t:Number = A3 / (A3 - A4); 1387 | var s:Number = A2 / (A2 - A1); 1388 | var A:Number = A2 * t / 2.0; 1389 | 1390 | if (A == 0) { 1391 | // this should never happen 1392 | return true; 1393 | } 1394 | 1395 | var R:Number = area / A; // relative area 1396 | var alpha:Number = 2 - Math.sqrt(4 - R / 0.3); // overall alpha for p0-o-p3 curve 1397 | 1398 | res.c = new Vector.(2); 1399 | res.c[0] = interval(t * alpha, p0, p1); 1400 | res.c[1] = interval(s * alpha, p3, p2); 1401 | res.alpha = alpha; 1402 | res.t = t; 1403 | res.s = s; 1404 | 1405 | p1 = res.c[0]; 1406 | p2 = res.c[1]; // the proposed curve is now (p0,p1,p2,p3) 1407 | 1408 | res.pen = 0; 1409 | 1410 | // Calculate penalty 1411 | // Check tangency with edges 1412 | for (k = mod(i + 1, m); k != j; k = k1) { 1413 | k1 = mod(k + 1, m); 1414 | t = tangent(p0, p1, p2, p3, path.curves.vertex[k], path.curves.vertex[k1]); 1415 | if (t < -0.5) { 1416 | return true; 1417 | } 1418 | pt = bezier(t, p0, p1, p2, p3); 1419 | d = ddist(path.curves.vertex[k], path.curves.vertex[k1]); 1420 | if (d == 0) { 1421 | // this should never happen 1422 | return true; 1423 | } 1424 | d1 = dpara(path.curves.vertex[k], path.curves.vertex[k1], pt) / d; 1425 | if (Math.abs(d1) > optTolerance) { 1426 | return true; 1427 | } 1428 | if (iprod(path.curves.vertex[k], path.curves.vertex[k1], pt) < 0 || iprod(path.curves.vertex[k1], path.curves.vertex[k], pt) < 0) { 1429 | return true; 1430 | } 1431 | res.pen += d1 * d1; 1432 | } 1433 | 1434 | // Check corners 1435 | for (k = i; k != j; k = k1) { 1436 | k1 = mod(k + 1, m); 1437 | t = tangent(p0, p1, p2, p3, path.curves.controlPoints[k][2], path.curves.controlPoints[k1][2]); 1438 | if (t < -0.5) { 1439 | return true; 1440 | } 1441 | pt = bezier(t, p0, p1, p2, p3); 1442 | d = ddist(path.curves.controlPoints[k][2], path.curves.controlPoints[k1][2]); 1443 | if (d == 0) { 1444 | // this should never happen 1445 | return true; 1446 | } 1447 | d1 = dpara(path.curves.controlPoints[k][2], path.curves.controlPoints[k1][2], pt) / d; 1448 | d2 = dpara(path.curves.controlPoints[k][2], path.curves.controlPoints[k1][2], path.curves.vertex[k1]) / d; 1449 | d2 *= 0.75 * path.curves.alpha[k1]; 1450 | if (d2 < 0) { 1451 | d1 = -d1; 1452 | d2 = -d2; 1453 | } 1454 | if (d1 < d2 - optTolerance) { 1455 | return true; 1456 | } 1457 | if (d1 < d2) { 1458 | res.pen += (d1 - d2) * (d1 - d2); 1459 | } 1460 | } 1461 | 1462 | return false; 1463 | } 1464 | 1465 | private function pathlist_to_curvearrayslist(plists:Array):Array 1466 | { 1467 | var res:Array = []; 1468 | 1469 | /* call downstream function with each path */ 1470 | for (var j:int = 0; j < plists.length; j++) 1471 | { 1472 | var plist:Array = plists[j] as Array; 1473 | var clist:Array = []; 1474 | res.push(clist); 1475 | 1476 | for (var i:int = 0; i < plist.length; i++) 1477 | { 1478 | var p:Path = plist[i] as Path; 1479 | var A:Point = p.curves.controlPoints[p.curves.n - 1][2]; 1480 | var curves:Array = []; 1481 | for (var k:int = 0; k < p.curves.n; k++) 1482 | { 1483 | var C:Point = p.curves.controlPoints[k][0]; 1484 | var D:Point = p.curves.controlPoints[k][1]; 1485 | var E:Point = p.curves.controlPoints[k][2]; 1486 | if (p.curves.tag[k] == POTRACE_CORNER) { 1487 | add_curve(curves, A, A, D, D); 1488 | add_curve(curves, D, D, E, E); 1489 | } else { 1490 | add_curve(curves, A, C, D, E); 1491 | } 1492 | A = E; 1493 | } 1494 | if (curves.length > 0) 1495 | { 1496 | var cl:Curve = curves[curves.length - 1] as Curve; 1497 | var cf:Curve = curves[0] as Curve; 1498 | if ((cl.kind == CurveKind.LINE) && (cf.kind == CurveKind.LINE) 1499 | && iprod(cl.b, cl.a, cf.b) < 0 1500 | && (Math.abs(xprodf( 1501 | new Point(cf.b.x - cf.a.x, cf.b.y - cf.a.y), 1502 | new Point(cl.a.x - cl.a.x, cl.b.y - cl.a.y))) < 0.01)) 1503 | { 1504 | curves[0] = new Curve(CurveKind.LINE, cl.a, cl.a, cl.a, cf.b); 1505 | curves.pop(); 1506 | } 1507 | var curveList:Array = []; 1508 | for (var ci:int = 0; ci < curves.length; ci++) { 1509 | curveList.push(curves[ci]); 1510 | } 1511 | clist.push(curveList); 1512 | } 1513 | } 1514 | } 1515 | return res; 1516 | } 1517 | 1518 | private function add_curve(curves:Array, a:Point, cpa:Point, cpb:Point, b:Point):void 1519 | { 1520 | var kind:int; 1521 | if ((Math.abs(xprodf(new Point(cpa.x - a.x, cpa.y - a.y), new Point(b.x - a.x, b.y - a.y))) < 0.01) && 1522 | (Math.abs(xprodf(new Point(cpb.x - b.x, cpb.y - b.y), new Point(b.x - a.x, b.y - a.y))) < 0.01)) { 1523 | kind = CurveKind.LINE; 1524 | } else { 1525 | kind = CurveKind.BEZIER; 1526 | } 1527 | if ((kind == CurveKind.LINE)) { 1528 | if ((curves.length > 0) && (Curve(curves[curves.length - 1]).kind == CurveKind.LINE)) { 1529 | var c:Curve = curves[curves.length - 1] as Curve; 1530 | if ((Math.abs(xprodf(new Point(c.b.x - c.a.x, c.b.y - c.a.y), new Point(b.x - a.x, b.y - a.y))) < 0.01) && (iprod(c.b, c.a, b) < 0)) { 1531 | curves[curves.length - 1] = new Curve(kind, c.a, c.a, c.a, b); 1532 | } else { 1533 | curves.push(new Curve(CurveKind.LINE, a, cpa, cpb, b)); 1534 | } 1535 | } else { 1536 | curves.push(new Curve(CurveKind.LINE, a, cpa, cpb, b)); 1537 | } 1538 | } else { 1539 | curves.push(new Curve(CurveKind.BEZIER, a, cpa, cpb, b)); 1540 | } 1541 | } 1542 | 1543 | ///////////////////////////////////////////////////////////////////////// 1544 | // AUXILIARY FUNCTIONS 1545 | ///////////////////////////////////////////////////////////////////////// 1546 | 1547 | /* 1548 | * Return a direction that is 90 degrees counterclockwise from p2-p0, 1549 | * but then restricted to one of the major wind directions (n, nw, w, etc) 1550 | */ 1551 | private function dorth_infty(p0:Point, p2:Point):PointInt 1552 | { 1553 | return new PointInt(-sign(p2.y - p0.y), sign(p2.x - p0.x)); 1554 | } 1555 | 1556 | /* 1557 | * Return (p1-p0) x (p2-p0), the area of the parallelogram 1558 | */ 1559 | private function dpara(p0:Point, p1:Point, p2:Point):Number { 1560 | return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y); 1561 | } 1562 | 1563 | /* 1564 | * ddenom/dpara have the property that the square of radius 1 centered 1565 | * at p1 intersects the line p0p2 iff |dpara(p0,p1,p2)| <= ddenom(p0,p2) 1566 | */ 1567 | private function ddenom(p0:Point, p2:Point):Number 1568 | { 1569 | var r:PointInt = dorth_infty(p0, p2); 1570 | return r.y * (p2.x - p0.x) - r.x * (p2.y - p0.y); 1571 | } 1572 | 1573 | /* 1574 | * Return true if a <= b < c < a, in a cyclic sense (mod n) 1575 | */ 1576 | private function cyclic(a:int, b:int, c:int):Boolean 1577 | { 1578 | if (a <= c) { 1579 | return (a <= b && b < c); 1580 | } else { 1581 | return (a <= b || b < c); 1582 | } 1583 | } 1584 | 1585 | /* 1586 | * Determine the center and slope of the line i..j. Assume i < j. 1587 | * Needs "sum" components of p to be set. 1588 | */ 1589 | private function pointslope(path:Path, i:int, j:int, ctr:Point, dir:Point):void 1590 | { 1591 | // assume i < j 1592 | var n:int = path.pt.length; 1593 | var sums:Vector. = path.sums; 1594 | var l:Number; 1595 | var r:int = 0; // rotations from i to j 1596 | 1597 | while (j >= n) { 1598 | j -= n; 1599 | r++; 1600 | } 1601 | while (i >= n) { 1602 | i -= n; 1603 | r--; 1604 | } 1605 | while (j < 0) { 1606 | j += n; 1607 | r--; 1608 | } 1609 | while (i < 0) { 1610 | i += n; 1611 | r++; 1612 | } 1613 | 1614 | var x:Number = sums[j + 1].x - sums[i].x + r * sums[n].x; 1615 | var y:Number = sums[j + 1].y - sums[i].y + r * sums[n].y; 1616 | var x2:Number = sums[j + 1].x2 - sums[i].x2 + r * sums[n].x2; 1617 | var xy:Number = sums[j + 1].xy - sums[i].xy + r * sums[n].xy; 1618 | var y2:Number = sums[j + 1].y2 - sums[i].y2 + r * sums[n].y2; 1619 | var k:Number = j + 1 - i + r * n; 1620 | 1621 | ctr.x = x / k; 1622 | ctr.y = y / k; 1623 | 1624 | var a:Number = (x2 - x * x / k) / k; 1625 | var b:Number = (xy - x * y / k) / k; 1626 | var c:Number = (y2 - y * y / k) / k; 1627 | 1628 | var lambda2:Number = (a + c + Math.sqrt((a - c) * (a - c) + 4 * b * b)) / 2; // larger e.value 1629 | 1630 | // now find e.vector for lambda2 1631 | a -= lambda2; 1632 | c -= lambda2; 1633 | 1634 | if (Math.abs(a) >= Math.abs(c)) { 1635 | l = Math.sqrt(a * a + b * b); 1636 | if (l != 0) { 1637 | dir.x = -b / l; 1638 | dir.y = a / l; 1639 | } 1640 | } else { 1641 | l = Math.sqrt(c * c + b * b); 1642 | if (l != 0) { 1643 | dir.x = -c / l; 1644 | dir.y = b / l; 1645 | } 1646 | } 1647 | if (l == 0) { 1648 | // sometimes this can happen when k=4: 1649 | // the two eigenvalues coincide 1650 | dir.x = dir.y = 0; 1651 | } 1652 | } 1653 | 1654 | /* 1655 | * Apply quadratic form Q to vector w = (w.x, w.y) 1656 | */ 1657 | private function quadform(Q:Vector.>, w:Point):Number 1658 | { 1659 | var sum:Number = 0; 1660 | var v:Vector. = new Vector.(3); 1661 | v[0] = w.x; 1662 | v[1] = w.y; 1663 | v[2] = 1; 1664 | for (var i:int = 0; i < 3; i++) { 1665 | for (var j:int = 0; j < 3; j++) { 1666 | sum += v[i] * Q[i][j] * v[j]; 1667 | } 1668 | } 1669 | return sum; 1670 | } 1671 | 1672 | /* 1673 | * Calculate point of a bezier curve 1674 | */ 1675 | private function bezier(t:Number, p0:Point, p1:Point, p2:Point, p3:Point):Point 1676 | { 1677 | var s:Number = 1 - t; 1678 | var res:Point = new Point(); 1679 | 1680 | // Note: a good optimizing compiler (such as gcc-3) reduces the 1681 | // following to 16 multiplications, using common subexpression 1682 | // elimination. 1683 | 1684 | // Note [cw]: Flash: fudeu! ;) 1685 | 1686 | res.x = s * s * s * p0.x + 3 * (s * s * t) * p1.x + 3 * (t * t * s) * p2.x + t * t * t * p3.x; 1687 | res.y = s * s * s * p0.y + 3 * (s * s * t) * p1.y + 3 * (t * t * s) * p2.y + t * t * t * p3.y; 1688 | 1689 | return res; 1690 | } 1691 | 1692 | /* 1693 | * Calculate the point t in [0..1] on the (convex) bezier curve 1694 | * (p0,p1,p2,p3) which is tangent to q1-q0. Return -1.0 if there is no 1695 | * solution in [0..1]. 1696 | */ 1697 | private function tangent(p0:Point, p1:Point, p2:Point, p3:Point, q0:Point, q1:Point):Number 1698 | { 1699 | // (1-t)^2 A + 2(1-t)t B + t^2 C = 0 1700 | var A:Number = cprod(p0, p1, q0, q1); 1701 | var B:Number = cprod(p1, p2, q0, q1); 1702 | var C:Number = cprod(p2, p3, q0, q1); 1703 | 1704 | // a t^2 + b t + c = 0 1705 | var a:Number = A - 2 * B + C; 1706 | var b:Number = -2 * A + 2 * B; 1707 | var c:Number = A; 1708 | 1709 | var d:Number = b * b - 4 * a * c; 1710 | 1711 | if (a == 0 || d < 0) { 1712 | return -1; 1713 | } 1714 | 1715 | var s:Number = Math.sqrt(d); 1716 | 1717 | var r1:Number = (-b + s) / (2 * a); 1718 | var r2:Number = (-b - s) / (2 * a); 1719 | 1720 | if (r1 >= 0 && r1 <= 1) { 1721 | return r1; 1722 | } else if (r2 >= 0 && r2 <= 1) { 1723 | return r2; 1724 | } else { 1725 | return -1; 1726 | } 1727 | } 1728 | 1729 | /* 1730 | * Calculate distance between two points 1731 | */ 1732 | private function ddist(p:Point, q:Point):Number 1733 | { 1734 | return Math.sqrt((p.x - q.x) * (p.x - q.x) + (p.y - q.y) * (p.y - q.y)); 1735 | } 1736 | 1737 | /* 1738 | * Calculate p1 x p2 1739 | * (Integer version) 1740 | */ 1741 | private function xprod(p1:PointInt, p2:PointInt):int 1742 | { 1743 | return p1.x * p2.y - p1.y * p2.x; 1744 | } 1745 | 1746 | /* 1747 | * Calculate p1 x p2 1748 | * (Floating point version) 1749 | */ 1750 | private function xprodf(p1:Point, p2:Point):int 1751 | { 1752 | return p1.x * p2.y - p1.y * p2.x; 1753 | } 1754 | 1755 | /* 1756 | * Calculate (p1 - p0) x (p3 - p2) 1757 | */ 1758 | private function cprod(p0:Point, p1:Point, p2:Point, p3:Point):Number 1759 | { 1760 | return (p1.x - p0.x) * (p3.y - p2.y) - (p3.x - p2.x) * (p1.y - p0.y); 1761 | } 1762 | 1763 | /* 1764 | * Calculate (p1 - p0) * (p2 - p0) 1765 | */ 1766 | private function iprod(p0:Point, p1:Point, p2:Point):Number 1767 | { 1768 | return (p1.x - p0.x) * (p2.x - p0.x) + (p1.y - p0.y) * (p2.y - p0.y); 1769 | } 1770 | 1771 | /* 1772 | * Calculate (p1 - p0) * (p3 - p2) 1773 | */ 1774 | private function iprod1(p0:Point, p1:Point, p2:Point, p3:Point):Number 1775 | { 1776 | return (p1.x - p0.x) * (p3.x - p2.x) + (p1.y - p0.y) * (p3.y - p2.y); 1777 | } 1778 | 1779 | private function interval(lambda:Number, a:Point, b:Point):Point 1780 | { 1781 | return new Point(a.x + lambda * (b.x - a.x), a.y + lambda * (b.y - a.y)); 1782 | } 1783 | 1784 | private function abs(a:int):int 1785 | { 1786 | return (a > 0) ? a : -a; 1787 | } 1788 | 1789 | private function floordiv(a:int, n:int):int 1790 | { 1791 | return (a >= 0) ? a / n : -1 - (-1 - a) / n; 1792 | } 1793 | 1794 | private function min(a:int, b:int):int 1795 | { 1796 | return (a < b) ? a : b; 1797 | } 1798 | 1799 | private function mod(a:int, n:int):int 1800 | { 1801 | return (a >= n) ? a % n : ((a >= 0) ? a : n - 1 - (-1 - a) % n); 1802 | } 1803 | 1804 | private function sign(x:int):int 1805 | { 1806 | return (x > 0) ? 1 : ((x < 0) ? -1 : 0); 1807 | } 1808 | } 1809 | } 1810 | -------------------------------------------------------------------------------- /src/com/powerflasher/as3potrace/POTraceParams.as: -------------------------------------------------------------------------------- 1 | package com.powerflasher.as3potrace 2 | { 3 | public class POTraceParams 4 | { 5 | // For a detailed description of all parameters, see: 6 | // http://potrace.sourceforge.net/potracelib.pdf 7 | 8 | // Note that differing from the original, the 9 | // curveOptimizing parameter is set to false here. 10 | 11 | public function POTraceParams(threshold:uint = 0x888888, thresholdOperator:String = "<=", turdSize:int = 2, alphaMax:Number = 1, curveOptimizing:Boolean = false, optTolerance:Number = 0.2) 12 | { 13 | this.threshold = threshold; 14 | this.thresholdOperator = thresholdOperator; 15 | this.turdSize = turdSize; 16 | this.alphaMax = alphaMax; 17 | this.curveOptimizing = curveOptimizing; 18 | this.optTolerance = optTolerance; 19 | } 20 | 21 | // Color value for threshold filter applied to bitmap before processing. 22 | // Defaults to 0x888888 23 | public var threshold:uint; 24 | 25 | // The operator to use when the threshold is applied (ex. "<=", ">", etc). 26 | // Defaults to "<=" 27 | public var thresholdOperator:String; 28 | 29 | // Area of largest path to be ignored (in pixels) 30 | // Defaults to 2 31 | public var turdSize:int; 32 | 33 | // Corner threshold, controls the smoothness of the curve. 34 | // The useful range of this parameter is from 0 (polygon) to 1.3333 (no corners). 35 | // Defaults to 1 36 | public var alphaMax:Number; 37 | 38 | // Whether to optimize curves or not. 39 | // Replace sequences of Bezier segments by a single segment when possible. 40 | // Defaults to false 41 | public var curveOptimizing:Boolean; 42 | 43 | // Curve optimizing tolerance 44 | // Larger values tend to decrease the number of segments, at the expense of less accuracy. 45 | // The useful range is from 0 to infinty, although in practice one would hardly choose values greater than 1 or so. 46 | // Defaults to 0.2 47 | public var optTolerance:Number; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/com/powerflasher/as3potrace/backend/GraphicsDataBackend.as: -------------------------------------------------------------------------------- 1 | package com.powerflasher.as3potrace.backend 2 | { 3 | import com.powerflasher.as3potrace.geom.CubicCurve; 4 | 5 | import flash.display.GraphicsPath; 6 | import flash.display.IGraphicsData; 7 | import flash.geom.Point; 8 | 9 | public class GraphicsDataBackend implements IBackend 10 | { 11 | protected var gd:Vector.; 12 | protected var gp:GraphicsPath; 13 | 14 | public function GraphicsDataBackend(gd:Vector.) 15 | { 16 | this.gd = gd; 17 | this.gp = new GraphicsPath(); 18 | } 19 | 20 | public function init(width:int, height:int):void {} 21 | public function initShape():void {} 22 | public function initSubShape(positive:Boolean):void {} 23 | 24 | public function moveTo(a:Point):void 25 | { 26 | gp.moveTo(a.x, a.y); 27 | } 28 | 29 | public function addBezier(a:Point, cpa:Point, cpb:Point, b:Point):void 30 | { 31 | var cubic:CubicCurve = new CubicCurve(); 32 | cubic.drawBezierPts(a, cpa, cpb, b); 33 | for (var i:int = 0; i < cubic.result.length; i++) { 34 | var quad:Vector. = cubic.result[i]; 35 | gp.curveTo(quad[1].x, quad[1].y, quad[2].x, quad[2].y); 36 | } 37 | } 38 | 39 | public function addLine(a:Point, b:Point):void 40 | { 41 | gp.lineTo(b.x, b.y); 42 | } 43 | 44 | public function exitSubShape():void {} 45 | public function exitShape():void {} 46 | 47 | public function exit():void 48 | { 49 | gd.push(gp); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/com/powerflasher/as3potrace/backend/IBackend.as: -------------------------------------------------------------------------------- 1 | package com.powerflasher.as3potrace.backend 2 | { 3 | import flash.geom.Point; 4 | 5 | public interface IBackend 6 | { 7 | function init(width:int, height:int):void; 8 | function initShape():void; 9 | function initSubShape(positive:Boolean):void; 10 | function moveTo(a:Point):void; 11 | function addBezier(a:Point, cpa:Point, cpb:Point, b:Point):void; 12 | function addLine(a:Point, b:Point):void; 13 | function exitSubShape():void; 14 | function exitShape():void; 15 | function exit():void; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/com/powerflasher/as3potrace/backend/NullBackend.as: -------------------------------------------------------------------------------- 1 | package com.powerflasher.as3potrace.backend 2 | { 3 | import flash.geom.Point; 4 | 5 | public class NullBackend implements IBackend 6 | { 7 | public function init(width:int, height:int):void 8 | { 9 | } 10 | 11 | public function initShape():void 12 | { 13 | } 14 | 15 | public function initSubShape(positive:Boolean):void 16 | { 17 | } 18 | 19 | public function moveTo(a:Point):void 20 | { 21 | } 22 | 23 | public function addBezier(a:Point, cpa:Point, cpb:Point, b:Point):void 24 | { 25 | } 26 | 27 | public function addLine(a:Point, b:Point):void 28 | { 29 | } 30 | 31 | public function exitSubShape():void 32 | { 33 | } 34 | 35 | public function exitShape():void 36 | { 37 | } 38 | 39 | public function exit():void 40 | { 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/com/powerflasher/as3potrace/backend/TraceBackend.as: -------------------------------------------------------------------------------- 1 | package com.powerflasher.as3potrace.backend 2 | { 3 | import com.powerflasher.as3potrace.backend.IBackend; 4 | 5 | import flash.geom.Point; 6 | 7 | public class TraceBackend implements IBackend 8 | { 9 | public function init(width:int, height:int):void 10 | { 11 | trace("Canvas width:" + width + ", height:" + height); 12 | } 13 | 14 | public function initShape():void 15 | { 16 | trace(" Shape"); 17 | } 18 | 19 | public function initSubShape(positive:Boolean):void 20 | { 21 | trace(" SubShape positive:" + positive); 22 | } 23 | 24 | public function moveTo(a:Point):void 25 | { 26 | trace(" MoveTo a:" + a); 27 | } 28 | 29 | public function addBezier(a:Point, cpa:Point, cpb:Point, b:Point):void 30 | { 31 | trace(" Bezier a:" + a + ", cpa:" + cpa + ", cpb:" + cpb + ", b:" + b); 32 | } 33 | 34 | public function addLine(a:Point, b:Point):void 35 | { 36 | trace(" Line a:" + a + ", b:" + b); 37 | } 38 | 39 | public function exitSubShape():void 40 | { 41 | } 42 | 43 | public function exitShape():void 44 | { 45 | } 46 | 47 | public function exit():void 48 | { 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/com/powerflasher/as3potrace/geom/CubicCurve.as: -------------------------------------------------------------------------------- 1 | package com.powerflasher.as3potrace.geom { import flash.geom.Point; /** * Class for drawing cubic bezier curves. * Adapted from bezier_draw_cubic.as and drawing_api_core_extensions.as by Alex Uhlmann. * Approved by Robert Penner. * @author Robert Penner, Alex Uhlmann * @version 0.4 ALPHA * -note that line 92 this.mc.moveTo (p1.x, p1.y); is commented out. * -ported to AS3 * * Adapted for use with as3potrace by Claus Wahlers */ public class CubicCurve { private var xPen:Number; private var yPen:Number; private var _result:Vector.>; public function CubicCurve() { } private function intersect2Lines(p1:Point, p2:Point, p3:Point, p4:Point):Point { var x1:Number = p1.x; var y1:Number = p1.y; var x4:Number = p4.x; var y4:Number = p4.y; var dx1:Number = p2.x - x1; var dx2:Number = p3.x - x4; if (dx1 == 0 && dx2 == 0) { return null; } var m1:Number = (p2.y - y1) / dx1; var m2:Number = (p3.y - y4) / dx2; if (dx1 == 0) { // infinity return new Point(x1, m2 * (x1 - x4) + y4); } else if (dx2 == 0) { // infinity return new Point(x4, m1 * (x4 - x1) + y1); } var xInt:Number = (-m2 * x4 + y4 + m1 * x1 - y1) / (m1 - m2); var yInt:Number = m1 * (xInt - x1) + y1; return new Point(xInt, yInt); } private function midLine(a:Point, b:Point):Point { return new Point((a.x + b.x) / 2, (a.y + b.y) / 2); } private function bezierSplit(p0:Point, p1:Point, p2:Point, p3:Point):Vector. { var m:Function = this.midLine; var p01:Point = m(p0, p1); var p12:Point = m(p1, p2); var p23:Point = m(p2, p3); var p02:Point = m(p01, p12); var p13:Point = m(p12, p23); var p03:Point = m(p02, p13); var ret:Vector. = new Vector.(8); ret[0] = p0.clone(); ret[1] = p01; ret[2] = p02; ret[3] = p03; ret[4] = p03; ret[5] = p13; ret[6] = p23; ret[7] = p3.clone(); return ret; } private function cBez(a:Point, b:Point, c:Point, d:Point, k:Number):void { // find intersection between bezier arms var s:Point = intersect2Lines(a, b, c, d); if(s == null) { return; } // find distance between the midpoints var dx:Number = (a.x + d.x + s.x * 4 - (b.x + c.x) * 3) * .125; var dy:Number = (a.y + d.y + s.y * 4 - (b.y + c.y) * 3) * .125; // split curve if the quadratic isn't close enough if (dx * dx + dy * dy > k) { // split the curve var halves:Vector. = bezierSplit(a, b, c, d); // recursive call to subdivide curve cBez(a, halves[1], halves[2], halves[3], k); cBez(halves[4], halves[5], halves[6], d, k); } else { // end recursion by drawing quadratic bezier var bezier:Vector. = new Vector.(3); bezier[0] = new Point(xPen, yPen); bezier[1] = s.clone(); bezier[2] = d.clone(); result.push(bezier); xPen = d.x; yPen = d.y; } } public function drawBezierPts(p1:Point, p2:Point, p3:Point, p4:Point, tolerance:Number = -1):void { if (tolerance <= 0) { tolerance = 5; } xPen = p1.x; yPen = p1.y; _result = new Vector.>(); cBez(p1, p2, p3, p4, tolerance * tolerance); } public function drawBezier(x1:Number, y1:Number, x2:Number, y2:Number, x3:Number, y3:Number, x4:Number, y4:Number, tolerance:Number = -1):void { drawBezierPts( new Point(x1, y1), new Point(x2, y2), new Point(x3, y3), new Point(x4, y4), tolerance ); } public function curveToCubicPts(p1:Point, p2:Point, p3:Point, tolerance:Number = -1):void { if (tolerance <= 0) { tolerance = 5; } _result = new Vector.>(); cBez(new Point(xPen, yPen), p1, p2, p3, tolerance * tolerance); } public function curveToCubic(x1:Number, y1:Number, x2:Number, y2:Number, x3:Number, y3:Number, tolerance:Number = -1):void { curveToCubicPts( new Point(x1, y1), new Point(x2, y2), new Point(x3, y3), tolerance); } public function get result():Vector.> { return _result; } } } -------------------------------------------------------------------------------- /src/com/powerflasher/as3potrace/geom/Curve.as: -------------------------------------------------------------------------------- 1 | package com.powerflasher.as3potrace.geom 2 | { 3 | import flash.geom.Point; 4 | 5 | public class Curve 6 | { 7 | // Bezier or Line 8 | public var kind:int; 9 | 10 | // Startpoint 11 | public var a:Point; 12 | 13 | // ControlPoint 14 | public var cpa:Point; 15 | 16 | // ControlPoint 17 | public var cpb:Point; 18 | 19 | // Endpoint 20 | public var b:Point; 21 | 22 | public function Curve(kind:int, a:Point, cpa:Point, cpb:Point, b:Point) 23 | { 24 | this.kind = kind; 25 | this.a = a; 26 | this.cpa = cpa; 27 | this.cpb = cpb; 28 | this.b = b; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/com/powerflasher/as3potrace/geom/CurveKind.as: -------------------------------------------------------------------------------- 1 | package com.powerflasher.as3potrace.geom 2 | { 3 | public class CurveKind 4 | { 5 | public static const LINE:int = 0; 6 | public static const BEZIER:int = 1; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/com/powerflasher/as3potrace/geom/Direction.as: -------------------------------------------------------------------------------- 1 | package com.powerflasher.as3potrace.geom 2 | { 3 | public class Direction 4 | { 5 | public static const NORTH:uint = 0; 6 | public static const EAST:uint = 1; 7 | public static const SOUTH:uint = 2; 8 | public static const WEST:uint = 3; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/com/powerflasher/as3potrace/geom/MonotonInterval.as: -------------------------------------------------------------------------------- 1 | package com.powerflasher.as3potrace.geom 2 | { 3 | public class MonotonInterval 4 | { 5 | public var increasing:Boolean; 6 | public var from:int; 7 | public var to:int; 8 | 9 | public var currentId:int; 10 | 11 | public function MonotonInterval(increasing:Boolean, from:int, to:int) 12 | { 13 | this.increasing = increasing; 14 | this.from = from; 15 | this.to = to; 16 | } 17 | 18 | public function resetCurrentId(modulo:int):void 19 | { 20 | if(!increasing) { 21 | currentId = mod(min() + 1, modulo); 22 | } else { 23 | currentId = min(); 24 | } 25 | } 26 | 27 | public function min():int 28 | { 29 | return increasing ? from : to; 30 | } 31 | 32 | public function max():int 33 | { 34 | return increasing ? to : from; 35 | } 36 | 37 | public function minY(pts:Vector.):int 38 | { 39 | return pts[min()].y; 40 | } 41 | 42 | public function maxY(pts:Vector.):int 43 | { 44 | return pts[max()].y; 45 | } 46 | 47 | private function mod(a:int, n:int):int 48 | { 49 | return (a >= n) ? a % n : ((a >= 0) ? a : n - 1 - (-1 - a) % n); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/com/powerflasher/as3potrace/geom/Opti.as: -------------------------------------------------------------------------------- 1 | package com.powerflasher.as3potrace.geom 2 | { 3 | import flash.geom.Point; 4 | 5 | public class Opti 6 | { 7 | public var pen:Number; 8 | public var c:Vector.; 9 | public var t:Number; 10 | public var s:Number; 11 | public var alpha:Number; 12 | 13 | public function clone():Opti 14 | { 15 | var o:Opti = new Opti(); 16 | o.pen = pen; 17 | o.c = new Vector.(2); 18 | o.c[0] = c[0].clone(); 19 | o.c[1] = c[1].clone(); 20 | o.t = t; 21 | o.s = s; 22 | o.alpha = alpha; 23 | return o; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/com/powerflasher/as3potrace/geom/Path.as: -------------------------------------------------------------------------------- 1 | package com.powerflasher.as3potrace.geom 2 | { 3 | public class Path 4 | { 5 | public var area:int; 6 | public var monotonIntervals:Vector.; 7 | public var pt:Vector.; 8 | public var lon:Vector.; 9 | public var sums:Vector.; 10 | public var po:Vector.; 11 | public var curves:PrivCurve; 12 | public var optimizedCurves:PrivCurve; 13 | public var fCurves:PrivCurve; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/com/powerflasher/as3potrace/geom/PointInt.as: -------------------------------------------------------------------------------- 1 | package com.powerflasher.as3potrace.geom 2 | { 3 | public class PointInt 4 | { 5 | public var x:int; 6 | public var y:int; 7 | 8 | public function PointInt(x:int = 0, y:int = 0) 9 | { 10 | this.x = x; 11 | this.y = y; 12 | } 13 | 14 | public function clone():PointInt 15 | { 16 | return new PointInt(x, y); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/com/powerflasher/as3potrace/geom/PrivCurve.as: -------------------------------------------------------------------------------- 1 | package com.powerflasher.as3potrace.geom 2 | { 3 | import flash.geom.Point; 4 | 5 | public class PrivCurve 6 | { 7 | public var n:int; 8 | public var tag:Vector.; 9 | public var controlPoints:Vector.>; 10 | public var vertex:Vector.; 11 | public var alpha:Vector.; 12 | public var alpha0:Vector.; 13 | public var beta:Vector.; 14 | 15 | public function PrivCurve(count:int) 16 | { 17 | // Number of segments 18 | n = count; 19 | 20 | // tag[n] = POTRACE_CORNER or POTRACE_CURVETO 21 | tag = new Vector.(n); 22 | 23 | // c[n][i]: control points. 24 | // c[n][0] is unused for tag[n] = POTRACE_CORNER 25 | controlPoints = new Vector.>(n); 26 | for (var i:int = 0; i < n; i++) { 27 | controlPoints[i] = new Vector.(3); 28 | } 29 | 30 | // for POTRACE_CORNER, this equals c[1]. 31 | vertex = new Vector.(n); 32 | 33 | // only for POTRACE_CURVETO 34 | alpha = new Vector.(n); 35 | 36 | // for debug output only 37 | // "uncropped" alpha parameter 38 | alpha0 = new Vector.(n); 39 | 40 | beta = new Vector.(n); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/com/powerflasher/as3potrace/geom/SumStruct.as: -------------------------------------------------------------------------------- 1 | package com.powerflasher.as3potrace.geom 2 | { 3 | public class SumStruct 4 | { 5 | public var x:int; 6 | public var y:int; 7 | public var x2:int; 8 | public var xy:int; 9 | public var y2:int; 10 | } 11 | } 12 | --------------------------------------------------------------------------------