I'm bad at drawing curved lines in ActionScript. I'm always too lazy to figure out how to calculate exactly where the control point should go when using curveTo. Because of this, most of my experiments have relied on drawing a lot of short straight line segments to simulate curves, rather than figuring out how to draw the curves properly. For example, my tree experiments are drawn entirely with straight line segments.
While revisiting my grass simulations I decided to bite the bullet and figure out an easy way to work with curves. The result is a couple of "spikes" or isolated test cases that demonstrate the logic behind converting a series of straight lines into a nice looking curve.
The basic concept is to bisect each of the straight lines, then draw a curve between the bisections, using the original points as the curve's control point. Nothing new I'm sure, but it works really well for me, because I can continue to think in straight lines, but draw in curves.
Click to add points. Click and drag points to move them.
I also extended this logic to work with fills, which is handy for drawing for drawing things like grass, seaweed, and tentacle monsters (I'm looking at you Peter Organa). To do this, you need to calculate the average angle for each point based on the next and previous points, then draw your curves along points tangential to that angle. If that doesn't make a lot of sense, the example below should clear it up. It's not perfect (still need to deal with the "flip" point that results in crossed lines), but it's been good enough for some of my recent experiments.
You can download the test files here.

Comments (24)
whoa, this is crazy timing...i was just experimenting with drawing curves! thanks again for the source, im excited to check it out.
Posted by: jon at May 30, 2008 11:14 AMURL: http://grenethumb.com
Thanks for saving the day! We just needed this for our project! Thanks for sharing!
Posted by: Jankees at May 30, 2008 11:40 AMURL: http://blog.6angrymen.com
what can i say? it's beautiful...
Posted by: charon at May 30, 2008 01:41 PMURL:
Great stuff. One quick question though. When I grab the control point at the top left of the "G" and drag it toward the top left corner, there is a point at which the lines cross. Any reason for this behavior?
Posted by: jeremy at May 30, 2008 01:55 PMURL:
I was also experimenting with something like this, trying to figure out how to manage the control points in the curveTo.
So this is very timely. Thanks for sharing.
Posted by: Gilbert Mizrahi at May 30, 2008 03:37 PMURL: http://http:/mylinerider.com
I just recently finished a smooth painter implementation which works essetially like this. I already knew how to go about dividing up the lines, though.
For the smooth painter, though, I couldn't use curveTo because of the way the brush worked, actively sampling the colors below it. So I ended up making a quick point interpolation thing. Same principle as what you made here, though.
I also decided to deprive my self of sleep and play with the curve thing a bit... http://www.box.net/shared/t9mxnbio8w
Posted by: Joseph Sikorski at May 31, 2008 01:30 AMURL:
jeremy, as I mentioned above, the point at which the tangent points flip needs to be fixed. They should flip at the point at which the angle in and out are reversed (180 deg apart), but they currently flip at a fixed rotation. I'm planning to fix this, just haven't had a chance yet. If anyone gets to it before I do, please post back here.
Posted by: Grant Skinner at May 31, 2008 09:32 AMURL: http://gskinner.com/blog/
I'm not exactly sure how to solve the flipping with how you did it, though I'm sure it's trivial. Here's how I wrote the draw function, though. And it automatically flips correctly, too.
function draw():void
{
var g:Graphics = graphics;
g.clear();
var prevMidpta:Point = null;
var prevMidptb:Point = null;
var prevNpa:Point = null;
var prevNpb:Point = null;
var l:Number = pts.length;
for (var i:Number=0; i {
var pt1:Object = pts[i-1];
var pt2:Object = pts[i];
var pt3:Object = pts[i+1];
var n1:Point = getNorm( pt1, pt2 );
var n3:Point = getNorm( pt2, pt3 );
var npa:Point; // normal points, also the control points.
var npb:Point;
if (n1 == null || n3 == null) npa = (n1) ? n1 : n3;
else
{
npa = n1.add( n3 );
npa.normalize( 1 );
}
n1 = getNorm( pt2, pt1 );
n3 = getNorm( pt3, pt2 );
if (n1 == null || n3 == null) npb = (n1) ? n1 : n3;
else
{
npb = n1.add( n3 );
npb.normalize( 1 );
}
pt2.rotation = Math.atan2( npa.x, -npa.y ) / Math.PI * 180;
npa.x = npa.x * 20 + pt2.x;
npa.y = npa.y * 20 + pt2.y;
npb.x = npb.x * 20 + pt2.x;
npb.y = npb.y * 20 + pt2.y;
if (prevNpa)
{
prevMidpta = drawPt( prevNpa, npa, prevMidpta, g, 0x66ffff );
prevMidptb = drawPt( prevNpb, npb, prevMidptb, g, 0x66ffff );
}
prevNpa = npa;
prevNpb = npb;
if (pt1)
{
g.lineStyle(0, 0xFFFFFF, 0.1);
g.moveTo(pt1.x, pt1.y);
g.lineTo(pt2.x, pt2.y);
}
}
g.lineStyle(2, 0x66FFFF, 1);
g.moveTo( prevMidpta.x, prevMidpta.y );
g.lineTo( prevNpa.x, prevNpa.y );
g.moveTo( prevMidptb.x, prevMidptb.y );
g.lineTo( prevNpb.x, prevNpb.y );
}
function getNorm( a:Object, b:Object ):Point
{
if (a == null || b == null)
return null;
var ret:Point = new Point( b.y - a.y, -(b.x - a.x) );
ret.normalize( 1 );
return ret;
}
function dot( a:Object, b:Object ):Number
Posted by: Joseph Sikorski at May 31, 2008 02:29 PM{
return a.x * b.x + a.y * b.y;
}
URL:
Hello Grant!
Posted by: laz9 at June 1, 2008 05:49 AMIn the context of your experiments, a pretty little problem to solve : how would you draw circle segments with curveTo ?
What I was doing is split the circle curve in 4 or 5 arcs, then interpolate point coords in the last incomplete section ... but i never got to a trully accurate solution.
Any ideas ?
URL:
hi,
I desperately search a method/algorithm for drawing an arc of bezier (partial bezier)
ex:
drawBezierArc(pointA:Point,pointB:Point,pointCtrl:Point,distance:Number)
where distance
someone knows an url where I could find some information to help me ?
i know is an integral calculation, but can't find the algorithm :(
the goal is profressive bezier drawing
Posted by: xeo at June 2, 2008 01:59 AMURL:
Oh, wow! Grant Skinner mentioned me in a blog post. My heart is aflutter!
Alright Grant, I'll give my tentacle monster another shot, this time with better math!
Thanks for posting the code!
Posted by: Peter Organa at June 3, 2008 12:46 PMI just finished playing with Flickr/as3flickrlib so this will probably be my next project.
URL: http://blog.organa.ca/
laz9: Quadratic splines can't be used to make a perfect circle. Old truetype fonts (which use them) usually had circles represented by an octogon.
xeo: Does it have to be by distance or will t value suffice?
Posted by: Joseph Sikorski at June 3, 2008 05:48 PMURL:
This reminds me of the "Alt+Drag line" feature to pull/push curves from lines...in the Macromedia Freehand program (RIP). I wanted do something similar with ActionScript. Thanks for posting!
Posted by: Keith H at June 8, 2008 09:26 AMURL: http://keith-hair.com
Good stuff, Grant.
Posted by: hu at June 9, 2008 10:54 AMBy clicking on your demo, I added a point which controls the curve. However my long-time wish list has been a little different: is there anyway to make it so that by clicking, I can add a point where the curve will go through? I don't care which direction it curves to. :)
Thanks for the code again.
URL: http://interactivesection.wordpress.com/
hi! i am not able to open the .fla test files ..i am using flash professional8 in win xp..dunno wats wrong..i cant open it in adobe flash player also..i really need to test this code. can ne1 twll me wats wrong?
Posted by: arthi at June 23, 2008 12:27 AMURL:
hi! i cant seem to open the .fla files of the test files..i am using flash8 in win xp..m neitehr able to open it in flash player. can ne1 tell me wats wrong? i realy need to test this code..
Posted by: rt at June 23, 2008 12:33 AMURL:
You will need Flash CS3 to open the FLA.
Posted by: Grant Skinner at June 23, 2008 08:45 AMURL: http://gskinner.com/blog/
Hi Grant,
I am an old fan of your works. We used your UML designer to generate our class hierarchy for our one of very prestigiuos product and am a regular reader of your blog as well.
I must say this demo is "Speechless" stunning and absolutely fabulous.
I have learned a lot from your blog and dont wanna mis the opportunity to say "Thanks .. Thanks a lot"
God bless you. keep writing
Posted by: anand vardhan at June 24, 2008 05:28 AMAnand
URL: http://www.anandvardhan.com
This is nice work, and can be the starting point for a lot of people with regard to smoothing; yet, what would really be interesting to me, is if you could reverse the construction of the quadratic bezier, knowing just the vertex and two additional points on the line, to find the control point; now if you can do that your math_fu is strong.
I have been trying to perfect my line smoothing code for a while now; although, I have been able to find the the control point for the quadratic bezier/parabola if the outermost points are an equal distance from the vertex at any degree; however, I am finding it very difficult to find any documentation on how to find it if the outermost points of the cage are not equidistant from the vertex.
People have gone so far as to tell me it is impossible; to that I respond that the bezier would be impossible if this were true. I have actually reversed the construction for a certain type of triangle that has a degree greater than 90 for one of its corners.
Your still the god of trees in my book; however, could you also be the god of quadratics?
Posted by: Anthony Pace at July 7, 2008 08:39 PMURL:
Very nice GS, thanks for sharing! I struggled w/ this for a while, and finally found a nice bezier curve-fitting function in the Tweener package.
Posted by: hebchop at July 28, 2008 09:56 AMhttp://labs.zeh.com.br/blog/?p=104#, the closed shape is something new though. very cool.
URL: http://www.jacobmake.com
This is an amazing example and thank you so much for posting the code. As I attempt to fill in the double-lined shape with a solid color, I am getting undesirable results. Would you please offer a code solution to how to fill in this shape? Thank you so much.
Posted by: chris at October 17, 2008 10:22 AMURL:
That is one cool application of action script. Wow.
Posted by: Domek at October 17, 2008 12:09 PMGreat job and thanks for sharing
URL: http://theanimeblizzard.com/
REFACTORED FOR USE WITH FILL AND CLOSED EDGES
preview:
http://labs.review.one-agency.be/labs/curvetest/
ps( movieclips on stage are named points now);
/**
* CurveTests by Grant Skinner. May 30, 2008
* Visit www.gskinner.com/blog for documentation, updates and more free code.
*
* revisioned by Jelger Muylaert. Jan 6 2009
*
* You may distribute and modify this code freely.
*
*/
// for unloading:
function halt():void {
removeEventListener(MouseEvent.MOUSE_DOWN,handlePress);
stage.removeEventListener(MouseEvent.MOUSE_MOVE,doDrag);
stage.removeEventListener(MouseEvent.MOUSE_UP,endDrag);
}
//
var pts:Array = [point1, point2, point3, point4, point5, point6];
var r:Number = point1.width/2;
var curvePoints:Array = [];
var g:Graphics = graphics;
var color:uint = 0x66ffff;
draw();
function draw():void
{
getCurvePointsBottomUp();
drawCurve();
}
function getCurvePointsBottomUp():void
{
g.clear();
var prevMidpta:Point = null;
var prevMidptb:Point = null;
var prevNpa:Point = null;
var prevNpb:Point = null;
var l:Number = pts.length;
var pt1:Object;
var pt2:Object;
var pt3:Object;
var n1:Point;
var n3:Point;
var npa:Point;
var npb:Point;
var scaledR:Number;
curvePoints = [];
for (var i:uint=0; i {
pt1 = pts[i-1];
pt2 = pts[i];
pt3 = pts[i+1];
//pt2.alpha = 0.2;
n1 = getNorm( pt1, pt2 );
n3 = getNorm( pt2, pt3 );
// set normal points, also the control points.
// a = left
// b = right
if (n1 == null || n3 == null) npa = (n1) ? n1 : n3;
else
{
npa = n1.add( n3 );
npa.normalize( 1 );
}
n1 = getNorm( pt2, pt1 );
n3 = getNorm( pt3, pt2 );
if (n1 == null || n3 == null) npb = (n1) ? n1 : n3;
else
{
npb = n1.add( n3 );
npb.normalize( 1 );
}
// rotate handler
pt2.rotation = Math.atan2( npa.x, -npa.y ) / Math.PI * 180;
// set width
if(i == 0) scaledR = r;
else scaledR = r / i;
// set positions of handlerEndpoints
npa.x = npa.x * scaledR + pt2.x;
npa.y = npa.y * scaledR + pt2.y;
npb.x = npb.x * scaledR + pt2.x;
npb.y = npb.y * scaledR + pt2.y;
curvePoints[i] = npa;
curvePoints[(l*2)-i-1] = npb;
}
}
function drawCurve():void
{
var l:int = curvePoints.length;
var pt1:Point;
var pt2:Point;
var mid:Point;
g.lineStyle(2, color, 1);
g.beginFill( 0xCCCCCC, 0.5 );
for( var i:int=0; i {
pt1 = curvePoints[i];
pt2 = curvePoints[i+1];
if(pt2) mid = getMiddlePoint(pt1, pt2);
// begin
if( i == 0){
g.moveTo( pt1.x, pt1.y );
g.lineTo( mid.x, mid.y );
}
// end
else if( i == l-1){
g.lineTo( pt1.x, pt1.y );
}
// curve
else{
g.curveTo( pt1.x, pt1.y, mid.x, mid.y );
}
}
g.endFill();
}
function getMiddlePoint(pt1:Object,pt2:Object):Point
{
var midpt:Point = new Point(pt1.x+(pt2.x-pt1.x)/2,pt1.y+(pt2.y-pt1.y)/2);
return midpt;
}
function getNorm( a:Object, b:Object ):Point
{
if (a == null || b == null) return null;
var ret:Point = new Point( b.y - a.y, -(b.x - a.x) );
ret.normalize( 1 );
return ret;
}
addEventListener(MouseEvent.MOUSE_DOWN,handlePress);
var dragProps:Object;
function handlePress(evt:MouseEvent):void {
if (evt.target is Anchor) {
dragProps = {target:evt.target};
stage.addEventListener(MouseEvent.MOUSE_MOVE,doDrag);
stage.addEventListener(MouseEvent.MOUSE_UP,endDrag);
}
}
function doDrag(evt:MouseEvent):void {
dragProps.target.x = mouseX;
dragProps.target.y = mouseY;
draw();
}
function endDrag(evt:MouseEvent):void {
Posted by: Jelger at January 6, 2009 03:37 PMdragProps = null;
stage.removeEventListener(MouseEvent.MOUSE_MOVE,doDrag);
stage.removeEventListener(MouseEvent.MOUSE_UP,endDrag);
}
URL:
Hi, Grant, I've been looking for a lasso tool example source code for my work but I can't find any so I'm trying to develop one DIY & ur code is really helpful. Also thanks to the code enhanced by Joseph Sikorski & Jelger.
Has any one already developed a lasso tool before? Care to give me some help especially the ant-moving animation & how to find all points inside the lasso curved polygon?
Posted by: scott chu at November 14, 2009 06:56 AMURL: