Php 在Bezier曲线中添加颜色的算法

Php 在Bezier曲线中添加颜色的算法,php,algorithm,gd,bezier,Php,Algorithm,Gd,Bezier,我正在玩GD库一段时间,尤其是贝塞尔曲线 我使用了我修改过一点的工具(认真地eval()…)。我发现这是GD中使用的一种通用算法 现在我想把它带到另一个层次:我想要一些颜色 线条颜色没有问题,但填充颜色更难 我的问题是: 有没有现成的算法?我指的是数学算法或者任何已经在做的语言,这样我就可以把它转换成PHP+GD EDIT2 因此,我尝试使用更硬曲线的@MizardX解决方案: 第一名:50-50 最终排名:50-200 第一管制站:300-225 第二管制站:300-25 这应该表明:

我正在玩GD库一段时间,尤其是贝塞尔曲线

我使用了我修改过一点的工具(认真地
eval()
…)。我发现这是GD中使用的一种通用算法

现在我想把它带到另一个层次:我想要一些颜色

线条颜色没有问题,但填充颜色更难

我的问题是:

有没有现成的算法?我指的是数学算法或者任何已经在做的语言,这样我就可以把它转换成PHP+GD

EDIT2 因此,我尝试使用更硬曲线的@MizardX解决方案:

  • 第一名:50-50
  • 最终排名:50-200
  • 第一管制站:300-225
  • 第二管制站:300-25
这应该表明:

并给出:

编辑 我已经读过@MizardX解决方案。使用
imagefilledpolygon
使其工作

但它并没有像预期的那样起作用。请参见下图以查看问题。 上图是我所期望的(目前没有黑线,只有红色部分)

使用的坐标:

  • 第一点是100-100
  • 最后一点是300-100分
  • 第一个控制点是100-0
  • 最终控制点为300-200


底部是我用这种算法得到的…

将贝塞尔曲线转换为多段线/多边形,然后填充它。如果以足够近的间隔(~1像素)计算贝塞尔多项式,它将与理想的贝塞尔曲线相同

我不知道您对贝塞尔曲线有多熟悉,但这里有一个速成班:

<?php

// Calculate the coordinate of the Bezier curve at $t = 0..1
function Bezier_eval($p1,$p2,$p3,$p4,$t) {
    // lines between successive pairs of points (degree 1)
    $q1  = array((1-$t) * $p1[0] + $t * $p2[0],(1-$t) * $p1[1] + $t * $p2[1]);
    $q2  = array((1-$t) * $p2[0] + $t * $p3[0],(1-$t) * $p2[1] + $t * $p3[1]);
    $q3  = array((1-$t) * $p3[0] + $t * $p4[0],(1-$t) * $p3[1] + $t * $p4[1]);
    // curves between successive pairs of lines. (degree 2)
    $r1  = array((1-$t) * $q1[0] + $t * $q2[0],(1-$t) * $q1[1] + $t * $q2[1]);
    $r2  = array((1-$t) * $q2[0] + $t * $q3[0],(1-$t) * $q2[1] + $t * $q3[1]);
    // final curve between the two 2-degree curves. (degree 3)
    return array((1-$t) * $r1[0] + $t * $r2[0],(1-$t) * $r1[1] + $t * $r2[1]);
}

// Calculate the squared distance between two points
function Point_distance2($p1,$p2) {
    $dx = $p2[0] - $p1[0];
    $dy = $p2[1] - $p1[1];
    return $dx * $dx + $dy * $dy;
}

// Convert the curve to a polyline
function Bezier_convert($p1,$p2,$p3,$p4,$tolerance) {
    $t1 = 0.0;
    $prev = $p1;
    $t2 = 0.1;
    $tol2 = $tolerance * $tolerance;
    $result []= $prev[0];
    $result []= $prev[1];
    while ($t1 < 1.0) {
        if ($t2 > 1.0) {
            $t2 = 1.0;
        }
        $next = Bezier_eval($p1,$p2,$p3,$p4,$t2);
        $dist = Point_distance2($prev,$next);
        while ($dist > $tol2) {
            // Halve the distance until small enough
            $t2 = $t1 + ($t2 - $t1) * 0.5;
            $next = Bezier_eval($p1,$p2,$p3,$p4,$t2);
            $dist = Point_distance2($prev,$next);
        }
        // the image*polygon functions expect a flattened array of coordiantes
        $result []= $next[0];
        $result []= $next[1];
        $t1 = $t2;
        $prev = $next;
        $t2 = $t1 + 0.1;
    }
    return $result;
}

// Draw a Bezier curve on an image
function Bezier_drawfilled($image,$p1,$p2,$p3,$p4,$color) {
    $polygon = Bezier_convert($p1,$p2,$p3,$p4,1.0);
    imagefilledpolygon($image,$polygon,count($polygon)/2,$color);
}

?>

编辑:

我忘了测试例行程序。确实如你所说;它没有给出正确的结果。现在我修复了两个bug:

  • 我无意中重复使用了变量名
    $p1
    $p2
    。我将它们重命名为
    $prev
    $next
  • while
    循环中的符号错误。现在它循环直到距离足够小,而不是足够大

  • 生成沿曲线的连续点列表(p_列表))

    在曲线的两个端点(l1)之间创建一条直线

    然后你会找到线的法线(n1)。使用该法线查找沿该法线(d1)的两个最远点(p_max1和p_max2)之间的距离。将该距离划分为n个离散单位(增量)

    现在将l1沿n1移动增量,并求解交点(从蛮力开始,检查p_列表中所有线段之间的解)。对于l1的每次移动,您应该能够获得两个交点,边界和自交点除外,因为您可能只有一个点。希望四边形例程可以让四边形的两个点位于同一位置(三角形)并毫无怨言地填充,否则在这种情况下需要三角形


    很抱歉,我没有提供伪代码,但是想法很简单。这就像取两个端点,用一把尺子将它们连接起来,然后保持尺子与原始直线平行,从一端开始,用连续的非常接近的铅笔标记填充整个图形。当你创建小铅笔标记(一个精细的矩形)时,你会看到这一点该矩形不太可能使用曲线上的点。即使您强制它使用曲线一侧的点,它与另一侧的点精确匹配也是非常巧合的,因此最好只计算新点。在计算新点时,根据这些点重新生成曲线p_列表可能是一个好主意,这样您可以更快地填充它(当然,如果曲线保持静态,否则就没有任何意义).

    我检查了生成多边形的算法,确保连续参数生成点之间的有界距离,并且似乎对我测试的所有曲线都很有效

    Mathematica中的代码:

    pts={{50,50},{300,225},{300,25},{50,200}};
    f=BezierFunction[pts];
    step=.1; (*initial step*)
    
    While[ (*get the final step - Points no more than .01 appart*)
       Max[
         EuclideanDistance @@@ 
             Partition[Table[f[t],{t,0,1,step}],2,1]] > .01,
       step=step/2]
    
    (*plot it*)
    Graphics@Polygon@Table[f[t],{t,0,1,step}]  
    

    如果点之间不需要相同的参数增量,则可以优化算法(即生成较少的点),这意味着您可以在每个点上选择一个参数增量,以确保到下一个点的距离有界

    随机示例:


    此答案与@MizardX的答案非常相似,但使用不同的方法沿贝塞尔曲线找到适合多边形近似的点

    function split_cubic($p, $t)
    {
        $a_x = $p[0] + ($t * ($p[2] - $p[0]));
        $a_y = $p[1] + ($t * ($p[3] - $p[1]));
        $b_x = $p[2] + ($t * ($p[4] - $p[2]));
        $b_y = $p[3] + ($t * ($p[5] - $p[3]));
        $c_x = $p[4] + ($t * ($p[6] - $p[4]));
        $c_y = $p[5] + ($t * ($p[7] - $p[5]));
        $d_x = $a_x + ($t * ($b_x - $a_x));
        $d_y = $a_y + ($t * ($b_y - $a_y));
        $e_x = $b_x + ($t * ($c_x - $b_x));
        $e_y = $b_y + ($t * ($c_y - $b_y));
        $f_x = $d_x + ($t * ($e_x - $d_x));
        $f_y = $d_y + ($t * ($e_y - $d_y));
    
        return array(
            array($p[0], $p[1], $a_x, $a_y, $d_x, $d_y, $f_x, $f_y),
            array($f_x, $f_y, $e_x, $e_y, $c_x, $c_y, $p[6], $p[7]));
    }
    
    $flatness_sq = 0.25; /* flatness = 0.5 */
    function cubic_ok($p)
    {
        global $flatness_sq;
    
        /* test is essentially:
         * perpendicular distance of control points from line < flatness */
    
        $a_x = $p[6] - $p[0];  $a_y = $p[7] - $p[1];
        $b_x = $p[2] - $p[0];  $b_y = $p[3] - $p[1];
        $c_x = $p[4] - $p[6];  $c_y = $p[5] - $p[7];
        $a_cross_b = ($a_x * $b_y) - ($a_y * $b_x);
        $a_cross_c = ($a_x * $c_y) - ($a_y * $c_x);
        $d_sq = ($a_x * $a_x) + ($a_y * $a_y);
        return max($a_cross_b * $a_cross_b, $a_cross_c * $a_cross_c) < ($flatness_sq * $d_sq);
    }
    
    $max_level = 8;
    function subdivide_cubic($p, $level)
    {
        global $max_level;
    
        if (($level == $max_level) || cubic_ok($p)) {
            return array();
        }
    
        list($q, $r) = split_cubic($p, 0.5);
        $v = subdivide_cubic($q, $level + 1);
        $v[] = $r[0]; /* add a point where we split the cubic */
        $v[] = $r[1];
        $v = array_merge($v, subdivide_cubic($r, $level + 1));
        return $v;
    }
    
    function get_cubic_points($p)
    {
        $v[] = $p[0];
        $v[] = $p[1];
        $v = array_merge($v, subdivide_cubic($p, 0));
        $v[] = $p[6];
        $v[] = $p[7];
        return $v;
    }
    
    function imagefilledcubic($img, $p, $color)
    {
        $v = get_cubic_points($p);
        imagefilledpolygon($img, $v, count($v) / 2, $color);
    }
    
    给出此输出:


    我不知道如何使PHP很好地解决这个问题<代码>图像别名($img,TRUE)似乎不起作用。

    @MizardX:谢谢你的回答。我没有立即看到您的编辑,代码与您现在得到的代码相同(只是我抑制了
    $p1=$p1;
    ,这似乎没有用。)总之,它没有按预期工作(请参阅我的问题更新)第一个点和第一个控制点之间的距离只有50,而终点与第二控制点之间的距离为100;这样你就得到了一个不对称的形状。。。。我只是个傻瓜。。。好的,它似乎可以工作,但如果第一个和最后一个点太近,会使我的服务器崩溃(我知道这不应该发生,但……在我的情况下,它仍然可能发生。)是否有任何解决方案,或者我必须先检查它?您可以减小初始步长,但这只会改变错误的范围。解决此问题的另一种方法是使用细分:@MizardX:很抱歉回答得太晚。在将50改为0以生成曲线时,它做了几乎相同的事情:非等距图形。不过,您的链接真的很棒@贝萨里斯只是想知道。Mathematica编译器是否提供生成C语言代码的功能?如果是这样的话,还需要什么额外的步骤?@David检查这个感谢,你让我发现Mathematica:)PHP不支持
    imagefilledpolygon的抗锯齿
    
    $img = imagecreatetruecolor(256, 256);
    
    imagefilledcubic($img, array(
        50.0, 50.0, /* first point */
        300.0, 225.0, /* first control point */
        300.0, 25.0, /* second control point */
        50.0, 200.0), /* last point */
        imagecolorallocate($img, 255, 255, 255));
    
    imagepng($img, 'out.png');
    
    imagedestroy($img);