This answer is very similar to @MizardX's, but uses a different method to find suitable points along the Bezier for a polygonal approximation.
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);
}
The basic idea is to recursively split the cubic in half until the bits we're left with are almost flat. Everywhere we split the cubic, we stick a polygon point.
split_cubic
splits the cubic in two at parameter $t
. cubic_ok
is the "are we flat enough?" test. subdivide_cubic
is the recursive function. Note that we stick a limit on the recursion depth to avoid nasty cases really screwing us up.
Your self-intersecting test case:
$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);
Gives this output:

I can't figure out how to make PHP nicely anti-alias this; imageantialias($img, TRUE);
didn't seem to work.