PHP Imagick bezier连接起点和终点,不要从pos 0,0开始

时间:2014-12-04 14:41:41

标签: php imagick bezier

我试图在PHP中使用Imagick创建一些bezier。 直到它到目前为止,我唯一的问题是如何在其他点(不是0,0)启动bezier并连接起点和终点? 任何帮助表示赞赏:)

这是我使用的代码:

$image = new Imagick();
$image->newImage(500, 500, 'none', 'png');

$bezier1 = new ImagickDraw();
$bezier1->setFillColor('#B42AAF');
$bezier1->setStrokeColor('black');
$bezier1->setStrokeWidth(1);

$bezier2 = new ImagickDraw();
$bezier2->setFillColor('FB9407');
$bezier2->setStrokeColor('black');
$bezier2->setStrokeWidth(1);

$coordinates_1 = Array
(
    [0] => Array
        (
            [x] => 250
            [y] => 46
        )

    [1] => Array
        (
            [x] => 394
            [y] => 166
        )

    [2] => Array
        (
            [x] => 316
            [y] => 288
        )

    [3] => Array
        (
            [x] => 250
            [y] => 324
        )

    [4] => Array
        (
            [x] => 163
            [y] => 299
        )

    [5] => Array
        (
            [x] => 163
            [y] => 200
        )
)

$coordinates_2 = Array
(
    [0] => Array
        (
            [x] => 250
            [y] => 123
        )

    [1] => Array
        (
            [x] => 437
            [y] => 141
        )

    [2] => Array
        (
            [x] => 410
            [y] => 342
        )

    [3] => Array
        (
            [x] => 250
            [y] => 405
        )

    [4] => Array
        (
            [x] => 169
            [y] => 296
        )

    [5] => Array
        (
            [x] => 101
            [y] => 164
        )
)

$bezier1->pathStart();
$bezier2->pathStart();

for($i = 0; $i < count($coordinates_1); $i++)
{
    $bezier1->pathCurveToQuadraticBezierSmoothAbsolute($coordinates_1[$i]['x'], $coordinates_1[$i]['y']);
    $bezier2->pathCurveToQuadraticBezierSmoothAbsolute($coordinates_2[$i]['x'], $coordinates_2[$i]['y']);
}

$bezier1->pathClose();
$bezier2->pathClose();

$image->drawImage($bezier1);
$image->drawImage($bezier2);

header('Content-Type: image/png');
echo $image;

1e结果用于显示点(带多边形):

Polygon

2e结果是贝塞尔问题(左上角0,0):

Bezier

3e结果是使用pathMoveToAbsolute尝试将“铅笔”移动到起始位置。哪个更难失败:(

failed bezier http://downloads.gdwebs.nl/failed-bezier.png

2 个答案:

答案 0 :(得分:1)

虽然I have opened an issue的功能似乎确实存在问题,但我认为这个功能不会像你希望的那样做,假设你想要一个平滑的轮廓。

基本上,在该功能中没有足够的信息来产生平滑的轮廓。

特别是:

  • 绘制的第一条线将始终以完全直线开始,因为pathCurveToQuadraticBezierSmoothAbsolute假设前两个Bezier控制点与第一条线共同发生。
  • 至少在您绘制的最后一个坐标和连接位回到第一个点之间会存在不连续性。我认为每个点之间也可能存在不连续性,因为没有足够的信息来绘制平滑的曲线。

我认为你需要做的是:

  • 计算您正在绘制的每个点的切线向量。
  • 按每个点的“值”(也就是距图表中心的距离)进行缩放。
  • 同样根据您想要图表的舍入程度进行缩放。
  • 使用它作为使用DrawPathCurveToAbsolute绘制贝塞尔曲线的基础。

下面是一个非常粗略的代码示例。输入值只是极坐标系中的半径,并且它们之间的角度相等。它有两种绘制形状的方法,或者使用如上所述的贝塞尔曲线,或者通过对角度周围的值进行插值(产生一个非常“圆形”的图形,然后使用直线版本插值...代码可能比描述更有意义。

无论如何,产生的一些示例图像是:

贝塞尔

enter image description here

曲线插值

enter image description here

内插曲线

enter image description here

<?php


define('debug', false);


$image = new Imagick();
$image->newImage(500, 500, 'white', 'png');


function interpolate($fraction, $value1, $value2) {
    return ((1 - $fraction) * $value1) + ($fraction * $value2);
}

function interpolateArray($fraction, $curvedPosition, $linearPosition) {

    $result = [];

    for ($i=0 ; $i<count($curvedPosition) ; $i++) {
        $result[$i] = interpolate($fraction, $curvedPosition[$i], $linearPosition[$i]);
    }

    return $result;
}


class SpiderChart {

    /**
     * @var \ImagickDraw
     */
    private $draw;

    private $chartWidth = 500;
    private $chartHeight = 500;

    private $segments = 100;

    function __construct() {
        $draw = new ImagickDraw();
        $draw->setFillColor('#B42AAF');
        $draw->setStrokeColor('black');
        $draw->setStrokeWidth(1);

        $this->draw = $draw;
    }

    /**
     * @return ImagickDraw
     */
    public function getDraw() {
        return $this->draw;
    }

    function drawChartBackground() {
        $this->draw->line(
            25, 
            $this->chartHeight / 2,
            $this->chartWidth - 25,
            $this->chartHeight / 2
        );

        $this->draw->line(
            $this->chartWidth / 2,
            25,
            $this->chartWidth / 2,
            $this->chartHeight - 25
        );

        $this->draw->setFillColor('none');

        $this->draw->circle(
            $this->chartWidth / 2,
            $this->chartHeight / 2,
            $this->chartWidth / 2,
            $this->chartHeight / 2 + 200 
        );

        $this->draw->circle(
            $this->chartWidth / 2,
            $this->chartHeight / 2,
            $this->chartWidth / 2,
            $this->chartHeight / 2 + 100
        );
    }


    public function getInterpolatedPosition($p, $i, $points) {
        $angleBetweenPoints = 2 * M_PI / count($points);
        $fraction = $i / $this->segments;
        $angle = ($p + ($fraction)) * $angleBetweenPoints;
        $firstValue = $points[$p];
        $secondValue = $points[($p + 1) % count($points)];
        $averageValue = interpolate($fraction, $firstValue, $secondValue);

        $positionX = sin($angle) * $averageValue ;
        $positionY = -cos($angle) * $averageValue ;

        if (debug) {
            echo "angle $angle positionX $positionX, positionY $positionY \n";
        }

        return [$positionX, $positionY];
    }

    public function getLinearPosition($p, $i, $points) {
        $angleBetweenPoints = 2 * M_PI / count($points);
        $fraction = $i / $this->segments;
        $startAngle = $p * $angleBetweenPoints;
        $endAngle = ($p + 1) * $angleBetweenPoints;

        $startPositionX = sin($startAngle) * $points[$p];
        $startPositionY = -cos($startAngle) * $points[$p];

        $endPositionX = sin($endAngle) * $points[($p + 1)];
        $endPositionY = -cos($endAngle) * $points[($p + 1) % count($points)];

        return [
            interpolate($fraction, $startPositionX, $endPositionX),
            interpolate($fraction, $startPositionY, $endPositionY),
        ];
    }


    public function drawBlendedChart($points, $curveLinearBlend) {
        $this->draw->setFillColor('#B42AAF9F');
        $this->draw->translate(
            $this->chartWidth / 2,
            $this->chartHeight / 2
        );

        $this->draw->pathStart();

        list($nextPositionX, $nextPositionY) = $this->getInterpolatedPosition(0, 0, $points);

        $this->draw->pathMoveToAbsolute(
            $nextPositionX,
            $nextPositionY
        );

        for ($p=0 ; $p<count($points) ; $p++) {
            for ($i = 0; $i < $this->segments; $i++) {
                $curvedPosition = $this->getInterpolatedPosition($p, $i, $points);
                $linearPosition = $this->getLinearPosition($p, $i, $points);

                list($nextPositionX,$nextPositionY) = interpolateArray(
                    $curveLinearBlend,
                    $curvedPosition,
                    $linearPosition
                );

                $this->draw->pathLineToAbsolute(
                    $nextPositionX,
                    $nextPositionY
                );
            }
        }

        $this->draw->pathClose();
        $this->draw->pathFinish();
    }


    /**
     * @param $points
     * @return array
     */
    private function getPointTangents($points) {
        $angleBetweenPoints = 2 * M_PI / count($points);
        $tangents = [];

        for ($i=0; $i<count($points) ; $i++) {
            $angle = ($i * $angleBetweenPoints) + M_PI_2;

            $unitX = sin($angle);
            $unitY = -cos($angle);
            $tangents[] = [$unitX, $unitY];
        }

        return $tangents;
    }

    /**
     * @param $points
     * @return array
     */
    private function getPointPositions($points) {
        $angleBetweenPoints = 2 * M_PI / count($points);
        $positions = [];

        for ($i=0; $i<count($points) ; $i++) {
            $angle = ($i * $angleBetweenPoints);

            $positions[] = [
                sin($angle) * $points[$i],
                -cos($angle) * $points[$i] 
            ];
        }

        return $positions;
    }

    /**
     * @param $position
     * @param $tangent
     * @param $direction - which sign the control point should use to multiple the unit vector 
     * @return array
     */
    private function getControlPoint($position, $tangent, $direction, $roundness) {

        //TODO - this scale needs to be done properly. The factors should be
        // i) the value of the current point.
        // ii) The curviness desired - done
        // iii) The cosine exterior angle.


        // top-tip - the interior angles sum to 180, so exterior is (180 - 180/n)
        // for an n-sided polygon

        $scale = 60 * $roundness;

        $resultX = $position[0] + $direction * $tangent[0] * $scale;
        $resultY = $position[1] + $direction * $tangent[1] * $scale;

        return [$resultX, $resultY];
    }


    function drawBezierChart($points, $roundness) {

        //Calculate the tangent vector for each point that you're drawing.
        $tangents = $this->getPointTangents($points);
        $positions = $this->getPointPositions($points);

        $numberOfPoints = count($points);

        $this->draw->setFillColor('#B42AAF9F');   

        $this->draw->translate(
            $this->chartWidth / 2,
            $this->chartHeight / 2
        );

        $this->draw->pathStart();
        $this->draw->pathMoveToAbsolute($positions[0][0], $positions[0][4]);


        //Scale that by the 'value' of each point aka the distance from the chart's centre.

        //Also scale it by how rounded you want the chart.

        for ($i=0 ; $i<$numberOfPoints ; $i++) {
            list($nextPositionX, $nextPositionY) = $positions[($i + 1) % $numberOfPoints];

            list($controlPoint1X, $controlPoint1Y) = 
                $this->getControlPoint(
                    $positions[$i],
                    $tangents[$i],
                    1,
                    $roundness
                );

            list($controlPoint2X, $controlPoint2Y) =
                $this->getControlPoint(
                    $positions[($i + 1) % $numberOfPoints],
                    $tangents[($i + 1) % $numberOfPoints],
                    -1,
                    $roundness
                );

            $this->draw->pathCurveToAbsolute(
                $controlPoint1X, $controlPoint1Y,
                $controlPoint2X, $controlPoint2Y,
                $nextPositionX, $nextPositionY
            );
        }

        $this->draw->pathClose();
        $this->draw->pathFinish();
    }
}

function getValues() {

    $coordinates_1 = [
        145,
        80,
        125,
        165,
        145
    ];

    return $coordinates_1;
}

$spiderChart = new SpiderChart();

$spiderChart->drawChartBackground();

$points = getValues();

//$spiderChart->drawBlendedChart($points, 0.2);

$spiderChart->drawBezierChart($points, 0.5);


$image->drawImage($spiderChart->getDraw());

if (!debug) {
    header('Content-Type: image/png');
    echo $image;
}

答案 1 :(得分:1)

图片的原因:

http://downloads.gdwebs.nl/failed-bezier.png

看起来很疯狂,当你使用pathCurveToQuadraticBezierSmoothAbsolute作为路径上的第一个元素时,有一个bug in ImageMagick

应该在ImageMagick的下一个版本中修复它。