计算贝塞尔曲线下的面积

时间:2014-07-13 16:10:53

标签: javascript php math imagemagick geometry

我试图找到这个问题的直截了当的答案,但我发现的答案往往非常引人注目,或者要求逻辑比所要求的更复杂。我的问题基于以下示例:

我们说我得到了一系列积分。它们都非常温顺和可预测,因为它们总是采用数学曲线模式...... (它们总是从左下角开始,右上角结束)

enter image description here

如何在PHP中确定此曲线下的区域?

请注意,我尝试使用Canvas在Javascript中复制此示例但是失败了(使用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;
?>

我理解这个问题可能需要几次迭代才能让我问...我会发布一个JSFiddle来备份这个例子并使其更容易使用,但是我无法将其转换为与js / bezierCurveTo一起使用,所以如果用户可以提供帮助,那么它也是一个非常有用的替代品

2 个答案:

答案 0 :(得分:3)

MBo的解决方案将给出一个确切的答案,您也可以尝试使用数值解决方案。

由于曲线始终在x方向上增加,我们可以在x方向上将其切片并近似每个切片的面积。例如,如果曲线上有四个点(x0,y0),(x1,y1),(x2,y2),(x3,y3),我们使用

找到第一个切片的面积
(x1-x0)*(y0+y1)/2

这是梯形区域。对每对点执行相同操作并将其添加。如果你的x坐标是均匀间隔的,那么就给出了我们可以简化的梯形规则。我们可以假设这里正在使用贝塞尔曲线。

如果我们有Bezier曲线,事情会变得有点棘手,因为我们实际上并不知道曲线上的点。一切都没有丢失,因为MBo已经给出了分数的公式

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是第一个控制点的x坐标,P [0] .Y是Y值等。在代码中你需要使用

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;

使用乘法而不是幂。使用0到1之间的多个t值来查找曲线上的点,然后找到切片。

我已经把一个javascript小提琴放在一起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());
});

答案 1 :(得分:2)

您可以使用公式

计算参数曲线下的面积
A = Integral[t0..t1] (y(t)*x'(t)*dt)

对于立方贝塞尔:(我不确定你使用的是什么样的贝塞尔曲线)

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)

您必须展开括号,区分第二行表达式,乘以表达式并集成结果

Maple工作(很难复制没有扭曲的文本): enter image description here 请注意,有些表达式被多次使用