简体   繁体   中英

Calculate area under Bezier curve

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

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());
});

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM