0

I'm trying to find a straightforward answer to this question, but the answers I find are often extremely conveluded, or require logic more complicated than what is required. My question is based on the following example:

Let's say I've got a series of points. They are all very tame and predictable, in that they always take on a mathematical curve pattern... (They always start bottom-left and end top-right)

enter image description here

How would I determine the area under this curve in PHP?

Note that I attempt to replicate this example in Javascript using Canvas but failed miserably (using examples like this)

<?php
    //Example requires Imagick
    $width =  800;
    $height = 200;
    $img = new Imagick();
    $img->newImage( $width, $height, new ImagickPixel( 'transparent' ) );
    $draw = new ImagickDraw();
    $draw->setStrokeColor( new ImagickPixel( 'red' ) );
    $draw->setStrokeWidth(4 );
    $draw->setFillColor( new ImagickPixel( 'transparent' ) );
    $points = array
    ( 
        array( 'x' => 0, 'y' => 200 ), 
        array( 'x' => 100, 'y' => 0 ), 
        array( 'x' => 200, 'y' => 200 ), 
        array( 'x' => 300, 'y' => 0 ),
        array( 'x' => 400, 'y' => 10 ), 
        array( 'x' => 500, 'y' => 0 )
    );
    $draw->bezier($points);
    $img->drawImage( $draw );
    $img->setImageFormat( "png" );
    header( "Content-Type: image/png" );
    echo $img;
?>

I understand this question may take a few iterations for me to ask... I would have posted a JSFiddle to back up this example and make it easier to work with, however I could not convert it for use with js/bezierCurveTo, so if a user could help with that it would also be a very useful substitute

1owk3y
  • 1,115
  • 1
  • 15
  • 30
  • Wouldn't a question regarding mathematics be better suited for the math stack exchange boards? Once you know the formula you can easily translate that to PHP – Zarathuztra Jul 13 '14 at 16:18
  • Actually no, my question is specific to the way the information is being provided, in this case an array of 2d points with a result similar to how ImageMagick would process them into a beizer curve. This question is too specific for a general math board. – 1owk3y Jul 13 '14 at 16:20
  • 1
    Regarding 'Simpsons Method', if you can tell me what any of this garbage means, ill be in your debt: http://en.wikipedia.org/wiki/Simpson's_rule. I'll be honest, I know that 'a' represents my first point, 'f' means function, 'a'/'b' are my limits, 'dx' is my change over x (which doesn't make sense in tis context), b represents my second point... the rest of it might as well be jibberish. Seriously I've done math for years, have I failed for not knowing geometry so specifically? – 1owk3y Jul 13 '14 at 16:28
  • There is no need to use approximate numerical Simpson's method, because exact analytical solution does exist. – MBo Jul 13 '14 at 18:00
  • This has been asked before see http://stackoverflow.com/questions/10039679/how-can-i-calculate-the-area-of-a-bezier-curve – Salix alba Jul 14 '14 at 06:12
  • Slightly confused by your example as you typically need four control points for a bezier curve, yet your example has 6. This might explain why the conversion to javascript is hard. – Salix alba Jul 14 '14 at 06:46
  • "What any of this garbage means"? This garbage is just the math that you fail to understand. – duffymo Jul 14 '14 at 09:04
  • @Salixalba, I read that answer during my search actually. It does not answer my question, as it deals with round beizer shapes which appear to be a lot more complicated to calculate than a 'curve' shape, which you actually proved. Also yes, that was the main part of the conversion I struggled with; no matter how I inputted the points, it never matched the shape from the Imagick result. – 1owk3y Jul 14 '14 at 09:56
  • @duffymo okay granted, I should not have compared the formulas to 'garbage' simply because I failed to understand them. The whole reason I'm asking the question is because I happen to be a programmer without a maths degree, and I know I'm not the only one. As such a highly reputable member of StackOverflow you should know that. There was no need to be spiteful. – 1owk3y Jul 14 '14 at 10:03
  • @1owk3y, you don't need a math degree to understand a little calculus. I learned it in high school. Spiteful? I'm pointing out your lack of respect. – duffymo Jul 14 '14 at 11:49
  • @duffymo to be fair, that's way more than a little calculus. Plus I'm not the only person who has forgotten their high-school math. – 1owk3y Jul 14 '14 at 12:00

2 Answers2

3

You can calculate area under parametric curve using formula

A = Integral[t0..t1] (y(t)*x'(t)*dt)

For cubic Bezier: (I don't sure what kind of Bezier curve you are using)

Area = Integral[0..1](y(t)*x'(t)*dt)=
Integral[0..1](
(P[0].Y*(1-t)^3+3*P[1].Y*t*(1-t)^2+3*P[2].Y*t^2*(1-t)+P[3].Y*t^3)*
(P[0].X*(1-t)^3+3*P[1].X*t*(1-t)^2+3*P[2].X*t^2*(1-t)+P[3].X*t^3)'*
dt)

You have to expand the brackets, differentiate the second line expression, multiply expressions, and integrate the result

Maple work (it is hard to copy text without distortions): enter image description here Note that some expressions are used many times

MBo
  • 77,366
  • 5
  • 53
  • 86
  • I believe it is cubic. Actually it's not even specified in the documentation! I might need to take a closer look later tonight, but there are a couple of symbols here I don't understand. Can I assume that 't' is the number of the current point? How does the 'compliment' work in this context? – 1owk3y Jul 14 '14 at 03:51
  • t is parameter of Bezier curve (it lies in range 0..1) – MBo Jul 14 '14 at 06:26
  • Hey MBo, thanks for your answer. I ended up going with Salix's answer because he went into more depth, added clearer explanation, and provided an interactive fiddle example. It is unfortunate it is mostly an elaboration of your answer which you gave very promptly (to his credit, he gave you credit) – 1owk3y Jul 14 '14 at 09:42
3

MBo's solution will give an exact answer, you can also try to use a numerical solution.

As your curve is always increasing in the x direction we can slice it up in the x-direction and approximate the area of each slice. For example if we have four points on the curve (x0,y0), (x1,y1), (x2,y2), (x3,y3) we find the area of the first slice using

(x1-x0)*(y0+y1)/2

this is the area of a trapezium. Do the same for each pair of points and add them up. If your x coords are evenly spaced this gives the trapezium rule which we can simplify. We can assume that here as we are using Bezier curves.

Things are a bit more tricky if we have Bezier curves as we don't actually know the points on the curve. All is not lost as MBo has given the formula for the points

X(t) = P[0].X*(1-t)^3+3*P[1].X*t*(1-t)^2+3*P[2].X*t^2*(1-t)+P[3].X*t^3
Y(t) = P[0].Y*(1-t)^3+3*P[1].Y*t*(1-t)^2+3*P[2].Y*t^2*(1-t)+P[3].Y*t^3

P[0].X is the x coord of your first control point, P[0].Y is the Y value, etc. In code you need to use

x = P[0].X*(1-t)*(1-t)*(1-t)+3*P[1].X*t*(1-t)*(1-t)+3*P[2].X*t*t*(1-t)+P[3].X*t*t*t;
y = P[0].Y*(1-t)*(1-t)*(1-t)+3*P[1].Y*t*(1-t)*(1-t)+3*P[2].Y*t*t*(1-t)+P[3].Y*t*t*t;

which uses multiplication rather than powers. Use a number of values of t between 0 and 1 to find points on the curve and then find the slices.

I've put a javascript fiddle which puts this all together http://jsfiddle.net/SalixAlba/QQnvm/

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

// The control points
var P = [{X:  13, Y: 224 }, 
     {X: 150, Y: 100 }, 
     {X: 251, Y: 224 }, 
     {X: 341, Y:  96 }, ];

ctx.lineWidth = 6;
ctx.strokeStyle = "#333";
ctx.beginPath();
ctx.moveTo(P[0].X, P[0].Y);
ctx.bezierCurveTo(P[1].X, P[1].Y, P[2].X, P[2].Y, P[3].X, P[3].Y);
ctx.stroke();

// draw the control polygon
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(P[0].X, P[0].Y);
ctx.lineTo(P[1].X, P[1].Y);
ctx.lineTo(P[2].X, P[2].Y);
ctx.lineTo(P[3].X, P[3].Y);
ctx.stroke();

function findarea(n) {
ctx.lineWidth = 3;
ctx.strokeStyle = "#f00";
ctx.beginPath();

var nSteps = n - 1;
var x = [P[0].X];
var y = [P[0].Y];
ctx.moveTo(x[0], y[0]);

var area = 0.0;
for (var i = 1; i <= nSteps; ++i) {
    var t = i / nSteps;
    x[i] = P[0].X*(1-t)*(1-t)*(1-t)+3*P[1].X*t*(1-t)*(1-t)+3*P[2].X*t*t*(1-t)+P[3].X*t*t*t;
    y[i] = P[0].Y*(1-t)*(1-t)*(1-t)+3*P[1].Y*t*(1-t)*(1-t)+3*P[2].Y*t*t*(1-t)+P[3].Y*t*t*t;
    ctx.lineTo(x[i], y[i]);

    area += (x[i] - x[i-1]) * (y[i-1] + y[i]) / 2;

    if(x[i]<x[i-1]) alert("Not strictly increasing in x, area will be incorrect");
}
ctx.stroke();
$("#area").val(area);
}

$("#goBut").click(function () {
    findarea($("#nPts").val());
});
Salix alba
  • 7,536
  • 2
  • 32
  • 38
  • Wow Salix, that's awesome. The working fiddle example is great too. Thanks for taking the time on this very well written answer. I don't suppose you know how to also calculate the intersection for this scenario? (finding an 'x' position given an 'area') – 1owk3y Jul 14 '14 at 09:50
  • For the intersection its best to start a separate question. – Salix alba Jul 14 '14 at 10:48
  • I suppose that's true. Do you mind if I use your fiddle in the question? – 1owk3y Jul 14 '14 at 11:50
  • No problem. Its fine to use the fiddle. – Salix alba Jul 14 '14 at 12:39