views:

677

answers:

4

Hi all

I am trying to make a 'brush' tool in AS3 (pure, not Flex) which simulates handwriting, making strokes to be smooth instead of cornered. Then, the trace must be reduced to cubic bezier curves which can be dragged and deformed, affecting the previously drawn path (like the illustrator's pen tool).

I'm tracking the mouse movement to get a set of points to draw the path. As far as I know, I need to do a B-Spline path using that set of points. Then I should reduce it to cubic bezier curves (adding the the 'pen tool' functionality to the path).

I have already developed the pen tool, using an algorithm which reduces Cubic Beziers to Quadratic Beziers (and then using the Flash curveTo function). But I have no idea how to create a B-Spline (or another simplification), an then reduce it to Bezier curves.

Do you know any way to accomplish this?

+3  A: 

The jhotdraw is an opensource project in Java for drawing. It converts free hand drawings into cubic bezier curves. The source is available - download and translate. Don't get scared at the size of the project : you need only a couple of classes namely:

org.jhotdraw.geom.Bezier
org.jhotdraw.geom.BezierPath
org.jhotdraw.geom.Geom

While translating start by changing all the collection declarations to Arrays (use vectors if you are targeting only FP10 users). I've some regexes that you might find useful in the conversion - I can post them if you want.


Here is a list of regexes that you might find useful. In each pair, paste the first one into search text area and second one into replace area, check the regex check box and use Find and Replace buttons. Don't use Replace All - none of these are guaranteed to be foolproof.

Replace all int/double name declarations with var name:Number

\b(double|int)\s+(\w+)\b

var $2:Number

Replace all Point2D.Double name declarations with var name:Point

\bPoint2D\.Double\s+(\w+)\b

var $1:Point

Replace all int/double name declarations in function signatures with name:Number

\(([^)]*)\b(?:double|int)\s+(\w+)\b([^)]*?)\)

($1$2:Number$3)

Replace all Point2D.Double name declarations in function signatures with name:Point

\(([^)]*)\b(?:Point2D\.Double)\s+(\w+)\b([^)]*?)\) 

($1$2:Point$3)

Before changing method signatures, make sure all methods are static:

(public|private)\s+(?!static)

Replace method signatures to AS format

(public|private)\s+static\s+(\w+)\s+(\w+)\s*\(([^)]*)\)

$1 static function $3($4):$2

Replace ArrayList.get(index) with array[index] //Warning: fails for list.get(list.size() - 1)

(\w+)\.get\(([^)]+)\)

$1[$2]

//avoid the () failure 

(\w+)\.get\(([^)]*(?:\([^)]*\))[^)]*)\)

$1[$2]

Replace ArrayList.set(index, element) with array[index] = element //Warning: fails for list.set(i, list.size())

(\w+)\.set\(([^,]+)\s*,\s*([^)]+)\)

$1[$2] = $3


/*the above regex successfully made the following replacement*/

cleaned.set(cleaned.size() - 1, digitizedPoints[digitizedPoints.size() - 1])

cleaned[cleaned.size() - 1] = digitizedPoints[digitizedPoints.size() - 1]

Replace arraylist.add(object) with array.push(object)

//would fail if object contains ')'
//add(index, object) should be done with splice

(\w+)\.add\(([^)]+)\)

$1.push($2)

//too many failures - fail safe version - 
//still fails for nested parenthesis  list.add(new Point(a.first(), a.last())) 
//- only three such cases - the effort to match parenthesis wouldn't be worth it
//works for list.add(new Point(3, 4)) - there were many similar cases

(\w+)\.add\(([^)]*(?:\([^)]*\))[^)]*)\)

$1.push($2)

Replace method signatures to AS format (non static methods)

(public|private)\s+(?!function)(\w+)\s+(\w+)\s*\(([^)]*)\)

$1 function $3($4):$2

Replace all int/double/point/boolean name declarations in function signatures with name:type

\(([^)]*)\b(\w+)\s+(\w+)\b([^)]*?)\)

($1$3:$2$4)

Replace all variable declarations in its own line with an = to AS format

^(\s+)(\w+)\s+(\w+)\s*=\s*(.+?)\s*;(\s*)$

$1var $3:$2 = $4;$5

change placing of braces.

^(\t)(\s*)([^\n]+)\{\s*(\n)\s+

$1$2$3$4$1$2{$4$1$2

change } else into } \n else

^([ \t]+)}[ \t]*else\b([^\n]*)(\n)

$1}$3$1else$2$3

Replace 4 variable declarations in a single line to AS in different lines

^(\t+)(\w+)\s+(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*;[ \t]*(\n)

$1var $3:$2;$7$1var $4:$2;$7$1var $5:$2;$7$1var $6:$2;$7

Replace array declarations

^(\s+)\w+\[\]\s*(\w+)\b

$1 var $2:Array

Remove () casting - AS compiler doesn't like them

(?:\(\w+\)\s*)([^ ,*+;/)><=\-])

$1

Replace max etc into Math.max - AS doesn't have static imports

(?<!Math\.)\b(max|min|abs|sqrt|PI|cos|sin|atan2)\(

Math.$1(
Amarghosh
I've checked the library and it looks awesome. The sample draw application does exactly what I want. Thanks for the reference.
yizzreel
Here are the regexes I was talking about.
Amarghosh
What an awesome work!! I have already began to port the Java code to AS3, it doesn't appear to be a difficult task, but this will speed up the process tremendously. Thanks a lot.
yizzreel
Make sure you post the port up onto github or googlecode, I was looking for EXACTLY this, earlier today. :)
secoif
A: 

Cool

Although translation from Java to AS3 should be pretty straightforward (both are very similar), those regexes could be very handy, thanks!

Besides that, what I really really need is to extract, from a bunch of Array points, only the Bezier points and their relative influence/anchor points, for example:

from Points:

[P0, P1, P2,... Pn]

I need a set of cubic Bezier points (where AP is anchor point):

[ B0[AP0,AP1], B1[AP0,AP1], ... Bn[AP0,AP1] ]

The number of Bezier points are logically less than the original number of points collected from the mouse movement. And, since I already have a class in AS3 which renders cubic bezier curves using the standard curveTo AS3 function (far better and faster than drawing point by point), I only need that transformation, from Points to Bezier points. Is that possible with those classes? can they return a set of bezier points of they have their own drawn system directly?.

Anyway, I'm going to check those files to see if I can use them.

Thanks a lot for the answer.

yizzreel
A: 

I used this function one time.


    public function multicurve(g: Graphics, args: Array, closed: Boolean): void {   
      var mid: Array = args.slice(); //make dublicate
      var i: uint;
      var point: Point;
      var nextPoint: Point;
      var numPoints: uint = mid.length;

      if (numPoints == 2) {
       g.moveTo(mid[0].x, mid[0].y);
       g.lineTo(mid[1].x, mid[1].y);
       return;
      }

      var Xpoint: Array = new Array();
      var Ypoint: Array = new Array();
      for (i = 1; i < numPoints - 2; i++) {
       point = mid[i];
       nextPoint = mid[i+1];
       Xpoint[i] = 0.5*(nextPoint.x + point.x);
       Ypoint[i] = 0.5*(nextPoint.y + point.y);
      }
      if (closed) {
       Xpoint[0] = 0.5*(mid[1].x + mid[0].x);
       Ypoint[0] = 0.5*(mid[1].y + mid[0].y);
       Xpoint[i] = 0.5*(mid[i+1].x + mid[i].x);
       Ypoint[i] = 0.5*(mid[i+1].y + mid[i].y);
       Xpoint[i+1] = 0.5*(mid[i+1].x + mid[0].x);
       Ypoint[i+1] = 0.5*(mid[i+1].y + mid[0].y);
       mid.push(new Point(mid[0].x, mid[0].y));
       Xpoint[i+2] = Xpoint[0];
       Ypoint[i+2] = Ypoint[0];
      } else {
       Xpoint[0] = mid[0].x;
       Ypoint[0] = mid[0].y;
       Xpoint[i] = mid[i+1].x;
       Ypoint[i] = mid[i+1].y;
       mid.pop();
       numPoints--;
      }
      g.moveTo(Xpoint[0], Ypoint[0]);
      for (i = 1; i < numPoints; i++) {
       point = mid[i];
       g.curveTo(point.x, point.y, Xpoint[i], Ypoint[i]);
      }
      if (closed) {
       g.curveTo(mid[0].x, mid[0].y, Xpoint[i], Ypoint[i]);
      }
     }


Pavel Alexeev
A: 

not sure if you specifically need beziers, but this catmull-rom spline tool is pretty great: http://www.motiondraw.com/md/as%5Fsamples/t/CatmullRomSpline/tween.html

ericsco