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

标签 php imagick bezier

我尝试用 PHP 中的 Imagick 创建一些贝塞尔曲线。 到目前为止,它有效,我唯一的问题是如何在其他点(不是 0,0)开始贝塞尔曲线并连接起点和终点? 感谢任何帮助:)

这是我正在使用的代码:

$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

最佳答案

虽然我 have opened an issue 的功能似乎确实存在问题。 ,假设您想要一个平滑的轮廓,我认为这个函数不会做您希望它做的事情。

基本上,该函数中没有提供足够的信息来生成平滑的轮廓。

特别是:

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

我认为你需要做的是:

  • 计算您正在绘制的每个点的切向量。
  • 按每个点的“值”(即距图表中心的距离)进行缩放。
  • 还可以根据您想要的图表的圆角程度来缩放它。
  • 使用它作为使用 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;
}

关于PHP Imagick bezier连接起点和终点并且不从pos 0,0开始,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27296766/

相关文章:

linux - 使用 Imagick 裁剪两个透明的 png 图像会产生不同的结果

javascript - 图像未正确裁剪或调整大小

c++ - 三次方贝塞尔曲线到二次方

animation - 您可以在动画时围绕贝塞尔曲线路径 "bend"SVG 吗?

php - New Relic warning : the Xdebug extension prevents the New Relic agent from gathering errors. 不会记录任何错误

php - mySQL 从匹配或不存在的两个表中选择

php - 使用PHP的“Notice: Undefined variable”,“Notice: Undefined index”和“Notice: Undefined offset”

php - 如果MySql行(字段)有任何文本输出

php - 将 CSS3 rotateY 转换为 PHP Imagick::distortImage

ios - 计算 Cox De Boor 算法的结向量的正确方法是什么?