Reputation: 1202
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)
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
Upvotes: 0
Views: 1755
Reputation: 7824
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());
});
Upvotes: 3
Reputation: 80285
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):
Note that some expressions are used many times
Upvotes: 3