Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I calculate the area of a bezier curve?

Tags:

javascript

svg

Given the following path (for example) which describes a SVG cubic bezier curve: "M300,140C300,40,500,40,500,140", and assuming a straight line connecting the end points 300,140 to 500,140 (closing the area under the curve), is it possible to calculate the area so enclosed?

Can anyone suggest a formula (or JavaScript) to accomplish this?

like image 598
MikeW Avatar asked Apr 06 '12 05:04

MikeW


People also ask

How do you calculate a Bezier curve?

These are vector equations. In other words, we can put x and y instead of P to get corresponding coordinates. For instance, the 3-point curve is formed by points (x,y) calculated as: x = (1−t)2x1 + 2(1−t)tx2 + t2x.

How are Bezier curve control points calculated?

To find any point P along a line, use the formula: P = (1-t)P0 + (t)P1 , where t is the percentage along the line the point lies and P0 is the start point and P1 is the end point. Knowing this, we can now solve for the unknown control point.


1 Answers

Convert the path to a polygon of arbitrary precision, and then calculate the area of the polygon.

Interactive Demo: Area of Path via Subdivision (broken)

                      Screenshot of Demo

At its core the above demo uses functions for adaptively subdividing path into a polygon and computing the area of a polygon:

// path:      an SVG <path> element // threshold: a 'close-enough' limit (ignore subdivisions with area less than this) // segments:  (optional) how many segments to subdivisions to create at each level // returns:   a new SVG <polygon> element function pathToPolygonViaSubdivision(path,threshold,segments){   if (!threshold) threshold = 0.0001; // Get really, really close   if (!segments)  segments = 3;       // 2 segments creates 0-area triangles    var points = subdivide( ptWithLength(0), ptWithLength( path.getTotalLength() ) );   for (var i=points.length;i--;) points[i] = [points[i].x,points[i].y];    var doc  = path.ownerDocument;   var poly = doc.createElementNS('http://www.w3.org/2000/svg','polygon');   poly.setAttribute('points',points.join(' '));   return poly;    // Record the distance along the path with the point for later reference   function ptWithLength(d) {     var pt = path.getPointAtLength(d); pt.d = d; return pt;   }    // Create segments evenly spaced between two points on the path.   // If the area of the result is less than the threshold return the endpoints.   // Otherwise, keep the intermediary points and subdivide each consecutive pair.   function subdivide(p1,p2){     var pts=[p1];     for (var i=1,step=(p2.d-p1.d)/segments;i<segments;i++){       pts[i] = ptWithLength(p1.d + step*i);     }     pts.push(p2);     if (polyArea(pts)<=threshold) return [p1,p2];     else {       var result = [];       for (var i=1;i<pts.length;++i){         var mids = subdivide(pts[i-1], pts[i]);         mids.pop(); // We'll get the last point as the start of the next pair         result = result.concat(mids)       }       result.push(p2);       return result;     }   }    // Calculate the area of an polygon represented by an array of points   function polyArea(points){     var p1,p2;     for(var area=0,len=points.length,i=0;i<len;++i){       p1 = points[i];       p2 = points[(i-1+len)%len]; // Previous point, with wraparound       area += (p2.x+p1.x) * (p2.y-p1.y);     }     return Math.abs(area/2);   } } 
// Return the area for an SVG <polygon> or <polyline> // Self-crossing polys reduce the effective 'area' function polyArea(poly){   var area=0,pts=poly.points,len=pts.numberOfItems;   for(var i=0;i<len;++i){     var p1 = pts.getItem(i), p2=pts.getItem((i+-1+len)%len);     area += (p2.x+p1.x) * (p2.y-p1.y);   }   return Math.abs(area/2); } 

Following is the original answer, which uses a different (non-adaptive) technique for converting the <path> to a <polygon>.

Interactive Demo: http://phrogz.net/svg/area_of_path.xhtml (broken)

                  Screenshot of Demo

At its core the above demo uses functions for approximating a path with a polygon and computing the area of a polygon.

// Calculate the area of an SVG polygon/polyline function polyArea(poly){   var area=0,pts=poly.points,len=pts.numberOfItems;   for(var i=0;i<len;++i){     var p1 = pts.getItem(i), p2=pts.getItem((i+len-1)%len);     area += (p2.x+p1.x) * (p2.y-p1.y);   }   return Math.abs(area/2); }  // Create a <polygon> approximation for an SVG <path> function pathToPolygon(path,samples){   if (!samples) samples = 0;   var doc = path.ownerDocument;   var poly = doc.createElementNS('http://www.w3.org/2000/svg','polygon');    // Put all path segments in a queue   for (var segs=[],s=path.pathSegList,i=s.numberOfItems-1;i>=0;--i)     segs[i] = s.getItem(i);   var segments = segs.concat();    var seg,lastSeg,points=[],x,y;   var addSegmentPoint = function(s){     if (s.pathSegType == SVGPathSeg.PATHSEG_CLOSEPATH){            }else{       if (s.pathSegType%2==1 && s.pathSegType>1){         x+=s.x; y+=s.y;       }else{         x=s.x; y=s.y;       }                 var last = points[points.length-1];       if (!last || x!=last[0] || y!=last[1]) points.push([x,y]);     }   };   for (var d=0,len=path.getTotalLength(),step=len/samples;d<=len;d+=step){     var seg = segments[path.getPathSegAtLength(d)];     var pt  = path.getPointAtLength(d);     if (seg != lastSeg){       lastSeg = seg;       while (segs.length && segs[0]!=seg) addSegmentPoint( segs.shift() );     }     var last = points[points.length-1];     if (!last || pt.x!=last[0] || pt.y!=last[1]) points.push([pt.x,pt.y]);   }   for (var i=0,len=segs.length;i<len;++i) addSegmentPoint(segs[i]);   for (var i=0,len=points.length;i<len;++i) points[i] = points[i].join(',');   poly.setAttribute('points',points.join(' '));   return poly; } 
like image 110
Phrogz Avatar answered Oct 04 '22 12:10

Phrogz