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