自从我上一篇文章谈到在 PHP 中用像素画一条线以来,我一直在研究画圆。事实证明,画圆有几种不同的方法,所以我将在这里介绍几个选项。
首先,PHP 中有一些内置函数可用于绘制圆弧、圆或椭圆作为填充形状或线。PHP 中调用的内置函数imageellipse()可用于绘制圆。这是 GD 库的一部分。
<?php // 生成图像。 $im = imagecreatetruecolor(255, 255); // 创造一种色彩。 $white = imagecolorallocate($im, 255, 255, 255); // 在图像中间画一个圆。 imageellipse($im, 128, 128, 200, 200, $white); // 将图像保存到文件中。 imagepng($im, 'circle.png'); // 销毁图像处理程序。 imagedestroy($im);
此代码生成以下图像。
该imageellipse()函数需要相当多的参数,但可根据提供的参数用于绘制圆或椭圆。要绘制实心圆,您可以使用该 imagefilledellipse()函数,该函数采用相同的参数但绘制实心圆。
如何在不使用内置 PHP 函数的情况下绘制圆?最简单的方法是通过从中心点绘制一系列线来绘制实心圆。对于圆中的每个度数,我们使用以下公式:
x = radius * cos(angle) y = radius * sin(angle)
我们在这里做的是计算圆心点所对的点。这是从最远点开始的直线连接在一起时特定点的角度。所以本质上,这是一条从圆心到外边缘的线。
有了这个,我们需要做的就是遍历一个圆(即 360)中的所有角度,并从中心点到由上述方程计算出的坐标画一条线。
<?php // 生成图像。 $im = imagecreatetruecolor(255, 255); // 创造一种色彩。 $white = imagecolorallocate($im, 255, 255, 255); // 将线条粗细设置为 4。 imagesetthickness($im, 4); // 设置圆的中心点。 $centerX = 128; $centerY = 128; // 设置圆的半径。 $radius = 100; // 每个循环将增加的角度。 $theta = 0; while ($theta <= 360) { // 计算新的 x,y 坐标。 $x = $centerX + $radius * cos($theta); $y = $centerY + $radius * sin($theta); // 增加θ。 $theta += 1; // 画一条从中心点到 x,y 坐标的线。 imageline($im, $centerX, $centerY, $x, $y, $white); } // 将图像保存到文件中。 imagepng($im, 'circle-filled.png'); // 销毁图像处理程序。 imagedestroy($im);
请注意,此代码还包括对 的调用imagesetthickness(),我们将其增加到 4,这是为了创建一个完全填充的圆。如果没有这个调用,圆的外边缘就不能正确地啮合在一起,圆也没有完全填满。这也可以通过增加 $theta 时增加步数来排序,但这也意味着增加我们需要运行循环的次数。增加画线的粗细是解决这个问题的简单方法。
此代码产生以下输出。
要绘制一个未填充的圆,我们只需imageline()调用imagesetpixel(). 这实质上是在投影线的末端绘制一个像素。
imagesetpixel($im, $x, $y, $white);
通过该更改,创建了以下圆圈。
如果这看起来有点笨拙,那是因为许多点没有连接。这种 cos/sin 方法适用于小圆圈,但圆圈越大,点之间的距离就越远,因此我们需要增加循环的迭代次数来缩小这个差距。
绘制圆的更好算法是 Bresenham 的圆绘制算法。这与 Bresenham 的画线算法有关,但在这里我们遍历圆周上的每个点,并决定应该绘制哪个像素以最好地拟合圆。
第一步是将圆分成 8 段,每段 45 度。因为圆是对称的,所以我们需要做的就是计算出单个线段,然后我们可以将该线段转换为其他 7 个线段。这意味着我们不必一直绕着圆圈循环,只需通过其中一个段即可。
使用圆心的坐标,我们可以计算出每个线段中的第一个点。
centerX + x, centerY + y centerX + y, centerY + x centerX - y, centerY + x centerX - x, centerY + y centerX - x, centerY - y centerX - y, centerY - x centerX + y, centerY - x centerX + x, centerY - y
然后我们可以开始计算第一段的每个点所在的位置。第一个决策是根据公式 $decision = 3 - (2 * $radius) 计算出来的。
在何处绘制下一个像素的决定使用以下方法计算:
如果决策小于 0,则下一个像素将在 (x+1, y) 处绘制。然后我们根据公式 $decision = $decision + (4 * $x) + 6 计算出决策。我们还增加了 x。
如果决定大于 0,则下一个像素将在 (x+1, y-1) 处绘制。然后我们根据公式 $decision + 4 * ($x - $y) + 10 计算出决策。我们也增加 x 并减少 y。
一旦 x 和 y 收敛,我们就可以停止,因为我们将计算出圆中的所有点。这里的数学是从圆的周长方程推导出来的,即 x 2 + y 2 - r 2。我发现这个页面很好地介绍了这个数学(虽然它确实需要一段时间才能理解发生了什么)。
以下代码将所有这些付诸实践。
<?php // 生成图像。 $im = imagecreatetruecolor(255, 255); // 创造一种色彩。 $white = imagecolorallocate($im, 255, 255, 255); // 设置圆的中心点。 $centerX = 128; $centerY = 128; $radius = 100; $x = 0; $y = $radius; // 计算初始决策 $decision = 3 - (2 * $radius); while ($x <= $y) { // 在圆的 8 个部分中的每一个中放置一个像素。 imagesetpixel($im, $centerX + $x, $centerY + $y, $white); imagesetpixel($im, $centerX + $y, $centerY + $x, $white); imagesetpixel($im, $centerX - $y, $centerY + $x, $white); imagesetpixel($im, $centerX - $x, $centerY + $y, $white); imagesetpixel($im, $centerX - $x, $centerY - $y, $white); imagesetpixel($im, $centerX - $y, $centerY - $x, $white); imagesetpixel($im, $centerX + $y, $centerY - $x, $white); imagesetpixel($im, $centerX + $x, $centerY - $y, $white); // x 的增量值。 $x++; if ($decision < 0) { // 下一个像素将在 (x + 1, y) 处绘制。 $decision = $decision + (4 * $x) + 6; } else { // 下一个像素将在 (x + 1, y - 1) 处绘制。 $decision = $decision + 4 * ($x - $y) + 10; // 减少 y 的值。 $y--; } } // 将图像保存到文件中。 imagepng($im, 'circle.png'); // 销毁图像处理程序。 imagedestroy($im);
这就是代码产生的结果。
如您所见,这是一个更加精致的圆圈,不包含任何间隙或其他错误。这个算法可以改进,我什至见过只使用位移的例子,这使得它更有效率。