您已经提到了 Bresenhams 的圆算法。这是一个很好的起点:您可以从中心像素开始,然后画出越来越大的 Bresenham 圆。
问题在于 Bresenham 圆算法会在一种莫尔效应中错过对角线附近的像素。在另一个问题中,我有adopted the Bresenham algorithm for drawing between an inner and outer circle。以该算法为基础,循环画圆的策略奏效了。
由于 Bresenham 算法只能将像素放置在离散的整数坐标上,因此访问像素的顺序不会严格按照距离递增的顺序。但距离始终在您正在绘制的当前圆的一个像素内。
下面是一个实现。那是在 C 中,但它只使用标量,因此适应 C# 应该不难。 setPixel 是您在迭代时对每个像素所做的操作。
void xLinePos(int x1, int x2, int y)
{
x1++;
while (x1 <= x2) setPixel(x1++, y);
}
void yLinePos(int x, int y1, int y2)
{
y1++;
while (y1 <= y2) setPixel(x, y1++);
}
void xLineNeg(int x1, int x2, int y)
{
x1--;
while (x1 >= x2) setPixel(x1--, y);
}
void yLineNeg(int x, int y1, int y2)
{
y1--;
while (y1 >= y2) setPixel(x, y1--);
}
void circle2(int xc, int yc, int inner, int outer)
{
int xo = outer;
int xi = inner;
int y = 0;
int erro = 1 - xo;
int erri = 1 - xi;
int patch = 0;
while (xo >= y) {
if (xi < y) {
xi = y;
patch = 1;
}
xLinePos(xc + xi, xc + xo, yc + y);
yLineNeg(xc + y, yc - xi, yc - xo);
xLineNeg(xc - xi, xc - xo, yc - y);
yLinePos(xc - y, yc + xi, yc + xo);
if (y) {
yLinePos(xc + y, yc + xi, yc + xo);
xLinePos(xc + xi, xc + xo, yc - y);
yLineNeg(xc - y, yc - xi, yc - xo);
xLineNeg(xc - xi, xc - xo, yc + y);
}
y++;
if (erro < 0) {
erro += 2 * y + 1;
} else {
xo--;
erro += 2 * (y - xo + 1);
}
if (y > inner) {
xi = y;
} else {
if (erri < 0) {
erri += 2 * y + 1;
} else {
xi--;
erri += 2 * (y - xi + 1);
}
}
}
if (patch) {
y--;
setPixel(xc + y, yc + y);
setPixel(xc + y, yc - y);
setPixel(xc - y, yc - y);
setPixel(xc - y, yc + y);
}
}
/*
* Scan pixels in circle in order of increasing distance
* from centre
*/
void scan(int xc, int yc, int r)
{
int i;
setPixel(xc, yc);
for (i = 0; i < r; i++) {
circle2(xc, yc, i, i + 1);
}
}
此代码通过跳过交替八分圆上的重合像素来处理不访问两个八分圆中的像素。 (编辑:原始代码中仍然存在错误,但现在已通过“补丁”变量修复。)
还有改进的空间:内圈基本上就是上一次迭代的外圈,所以计算两次也没有意义;您可以保留前一个圆的外部点的数组。
xLinePos 函数也有点太复杂了。在该函数中绘制的像素永远不会超过两个,通常只有一个。
如果搜索顺序的粗糙度困扰您,您可以在程序开始时运行一次更精确的算法,计算所有圆的遍历顺序,直到合理的最大半径。然后,您可以保留该数据并将其用于迭代所有半径较小的圆。