实心圆的中点圆算法
Posted
技术标签:
【中文标题】实心圆的中点圆算法【英文标题】:Midpoint circle algorithm for filled circles 【发布时间】:2012-06-04 08:12:11 【问题描述】:Midpoint circle algorithm 可以用来光栅化圆的边框。但是,我希望填充圆圈,而不是多次绘制像素(这非常重要)。
这个答案提供了算法的修改,产生一个实心圆,但一些像素被访问了几次: fast algorithm for drawing filled circles?
问:如何在不多次绘制像素的情况下栅格化一个圆?请注意,RAM 非常有限!
更新:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CircleTest
class Program
static void Main(string[] args)
byte[,] buffer = new byte[50, 50];
circle(buffer, 25, 25, 20);
for (int y = 0; y < 50; ++y)
for (int x = 0; x < 50; ++x)
Console.Write(buffer[y, x].ToString());
Console.WriteLine();
// 'cx' and 'cy' denote the offset of the circle center from the origin.
static void circle(byte[,] buffer, int cx, int cy, int radius)
int error = -radius;
int x = radius;
int y = 0;
// The following while loop may altered to 'while (x > y)' for a
// performance benefit, as long as a call to 'plot4points' follows
// the body of the loop. This allows for the elimination of the
// '(x != y)' test in 'plot8points', providing a further benefit.
//
// For the sake of clarity, this is not shown here.
while (x >= y)
plot8points(buffer, cx, cy, x, y);
error += y;
++y;
error += y;
// The following test may be implemented in assembly language in
// most machines by testing the carry flag after adding 'y' to
// the value of 'error' in the previous step, since 'error'
// nominally has a negative value.
if (error >= 0)
error -= x;
--x;
error -= x;
static void plot8points(byte[,] buffer, int cx, int cy, int x, int y)
plot4points(buffer, cx, cy, x, y);
if (x != y) plot4points(buffer, cx, cy, y, x);
// The '(x != 0 && y != 0)' test in the last line of this function
// may be omitted for a performance benefit if the radius of the
// circle is known to be non-zero.
static void plot4points(byte[,] buffer, int cx, int cy, int x, int y)
#if false // Outlined circle are indeed plotted correctly!
setPixel(buffer, cx + x, cy + y);
if (x != 0) setPixel(buffer, cx - x, cy + y);
if (y != 0) setPixel(buffer, cx + x, cy - y);
if (x != 0 && y != 0) setPixel(buffer, cx - x, cy - y);
#else // But the filled version plots some pixels multiple times...
horizontalLine(buffer, cx - x, cy + y, cx + x);
//if (x != 0) setPixel(buffer, cx - x, cy + y);
//if (y != 0) setPixel(buffer, cx + x, cy - y);
//if (x != 0 && y != 0) setPixel(buffer, cx - x, cy - y);
#endif
static void setPixel(byte[,] buffer, int x, int y)
buffer[y, x]++;
static void horizontalLine(byte[,] buffer, int x0, int y0, int x1)
for (int x = x0; x <= x1; ++x)
setPixel(buffer, x, y0);
以下是相关结果:
00000111111111111111111111111111111111111111110000
00000111111111111111111111111111111111111111110000
00000111111111111111111111111111111111111111110000
00000111111111111111111111111111111111111111110000
00000111111111111111111111111111111111111111110000
00000011111111111111111111111111111111111111100000
00000011111111111111111111111111111111111111100000
00000011111111111111111111111111111111111111100000
00000001111111111111111111111111111111111111000000
00000001111111111111111111111111111111111111000000
00000000111111111111111111111111111111111110000000
00000000111111111111111111111111111111111110000000
00000000011111111111111111111111111111111100000000
00000000001111111111111111111111111111111000000000
00000000000111111111111111111111111111110000000000
00000000000011111111111111111111111111100000000000
00000000000001111111111111111111111111000000000000
00000000000000122222222222222222222210000000000000
00000000000000001222222222222222221000000000000000
00000000000000000012333333333332100000000000000000
00000000000000000000012345432100000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
底部像素被绘制了太多次。我在这里错过了什么?
更新 #2:此解决方案有效:
static void circle(byte[,] buffer, int cx, int cy, int radius)
int error = -radius;
int x = radius;
int y = 0;
while (x >= y)
int lastY = y;
error += y;
++y;
error += y;
plot4points(buffer, cx, cy, x, lastY);
if (error >= 0)
if (x != lastY)
plot4points(buffer, cx, cy, lastY, x);
error -= x;
--x;
error -= x;
static void plot4points(byte[,] buffer, int cx, int cy, int x, int y)
horizontalLine(buffer, cx - x, cy + y, cx + x);
if (y != 0)
horizontalLine(buffer, cx - x, cy - y, cx + x);
【问题讨论】:
答案没有多次访问任何像素。为什么这么说? 我的实现一直在圆的顶部/底部进行多图绘制。也许我只是不明白答案? 【参考方案1】:另一个问题的答案很好。不过,由于它造成了混乱,我将稍微解释一下。
你在 Wikipedia 中看到的算法基本上找到了 1/8 个圆的 x
和 y
(角度 0 到 pi/4
),然后绘制 8 个点作为它的镜像。例如:
(o-y,o+x) x x (o+y,o+x)
(o-x,o+y) x x (o+x,o+y) <-- compute x,y
o
(o-x,o-y) x x (o+x,o-y)
(o-y,o-x) x x (o+y,o-x)
另一种解决方案的建议是,如果您仔细观察这张图片,这将是非常有意义的,而不是绘制 8 个点,而是绘制 4 条水平线:
(o-y,o+x) x---------x (o+y,o+x)
(o-x,o+y) x-----------------x (o+x,o+y) <-- compute x,y
o
(o-x,o-y) x-----------------x (o+x,o-y)
(o-y,o-x) x---------x (o+y,o-x)
现在,如果您计算 (x,y)
中的角度 [0, pi/4]
并为每个计算点绘制这 4 条线,您将绘制许多水平线填充一个圆圈,而没有任何线重叠。
更新
在圆圈底部出现重叠线的原因是 (x,y)
坐标是四舍五入的,因此在这些位置,(x,y)
会自行水平移动。
如果你看看this wikipedia图片:
您会注意到,在圆圈的顶部,一些像素水平对齐。从这些点开始绘制水平线重叠。
如果您不想这样,解决方案很简单。您必须保留之前绘制的x
(因为顶部和底部是原始(x,y)
的镜像,您应该保留表示这些线的y 的之前的x)并且只绘制水平线,如果那样的话值变化。如果不是,说明你们在同一行。
考虑到您将首先遇到最里面的点,只有当新点具有不同的x
时,您才应该为前一个点画线(当然,总是画最后一条线)。或者,您可以从角度 PI/4 开始绘制到 0 而不是 0 到 PI/4,并且您将首先遇到外部点,因此每次看到新的x
时都会绘制线条。
【讨论】:
谢谢。我将使用演示该问题的示例代码更新我的问题。我一定是缺少某种条件。 嗯。所以我需要两个“上一行”变量?一个用于“y”,一个用于“x”(镜像)? 嗯,没那么简单。要绘制一条线,您需要 outer 像素。并且该算法不保证最外层像素首先被访问。因此,如果我跟踪访问过的行,则会丢失一些像素。 @NOPslider,您只需要 1 个,即x
。让我们考虑来自***的同一张图片。你从角度 0 开始,所以你有 (x,0)
并且你开始上升。随着您的上升,对于几个像素,x
保持不变,y
发生变化。无论如何都将绘制 2 条水平线,并且必须检查另外两条(反映在 y = x 线上)是否引入了水平线。如果有,你就画出来。
另外,你是对的,你从内到外访问节点。我会更新答案【参考方案2】:
我想出了一个算法来绘制已经填充的圆圈。 它遍历将绘制圆的像素,仅此而已。 从这里开始,关于绘制像素函数的速度。
Here's a *.gif that demonstrates what the algorithm does !
至于算法,代码如下:
//The center of the circle and its radius.
int x = 100;
int y = 100;
int r = 50;
//This here is sin(45) but i just hard-coded it.
float sinus = 0.70710678118;
//This is the distance on the axis from sin(90) to sin(45).
int range = r/(2*sinus);
for(int i = r ; i >= range ; --i)
int j = sqrt(r*r - i*i);
for(int k = -j ; k <= j ; k++)
//We draw all the 4 sides at the same time.
PutPixel(x-k,y+i);
PutPixel(x-k,y-i);
PutPixel(x+i,y+k);
PutPixel(x-i,y-k);
//To fill the circle we draw the circumscribed square.
range = r*sinus;
for(int i = x - range + 1 ; i < x + range ; i++)
for(int j = y - range + 1 ; j < y + range ; j++)
PutPixel(i,j);
希望这对您有所帮助...一些新用户...抱歉发布了 necro-posting。 ~Shmiggy
【讨论】:
谢谢,但请注意 sqrt() 调用非常昂贵。乘法和除法也是如此。 在 x86 procs 上,乘法、除法和 sqrt 都是单指令。 单条指令不一定意味着高性能。需要考虑时钟周期、精度问题、平台差异......在不需要时使用数学上复杂的算法(例如平方根近似)是没有意义的。【参考方案3】:我需要这样做,这是我想出的代码。此处的可视图像显示了绘制的像素,其中数字是像素遍历的顺序,绿色数字表示使用对称性的列完成反射绘制的像素,如代码所示。
void drawFilledMidpointCircleSinglePixelVisit( int centerX, int centerY, int radius )
int x = radius;
int y = 0;
int radiusError = 1 - x;
while (x >= y) // iterate to the circle diagonal
// use symmetry to draw the two horizontal lines at this Y with a special case to draw
// only one line at the centerY where y == 0
int startX = -x + centerX;
int endX = x + centerX;
drawHorizontalLine( startX, endX, y + centerY );
if (y != 0)
drawHorizontalLine( startX, endX, -y + centerY );
// move Y one line
y++;
// calculate or maintain new x
if (radiusError<0)
radiusError += 2 * y + 1;
else
// we're about to move x over one, this means we completed a column of X values, use
// symmetry to draw those complete columns as horizontal lines at the top and bottom of the circle
// beyond the diagonal of the main loop
if (x >= y)
startX = -y + 1 + centerX;
endX = y - 1 + centerX;
drawHorizontalLine( startX, endX, x + centerY );
drawHorizontalLine( startX, endX, -x + centerY );
x--;
radiusError += 2 * (y - x + 1);
【讨论】:
【参考方案4】:我想对您的更新 #2 发表评论:此解决方案有效:(但我想我首先需要更多的声誉......)解决方案中有一个小错误,巧合的是在绘制小圆圈时。如果将半径设置为 1,则会得到
00000
00000
01110
00100
00000
要解决这个问题,您需要做的就是更改 plot4points 中的条件检查
if (x != 0 && y != 0)
到
if (y != 0)
我已经在大大小小的圆圈上进行了测试,以确保每个像素仍然只分配一次。似乎工作得很好。我认为 x != 0 是不需要的。也节省了一点性能。
【讨论】:
这是有道理的!它实际上可能意味着显着的性能提升。在 GPU 上,分支通常非常昂贵。【参考方案5】:更新 #2
if (error >= 0)
if (x != lastY)
plot4points(buffer, cx, cy, lastY, x);
到
if (error >= 0)
plot4points(buffer, cx, cy, lastY, x);
Circle 和 FillCircle 版本:
Const
Vypln13:Boolean=False; // Fill Object
//Draw a circle at (cx,cy)
Procedure Circle(cx: integer; cy: integer; radius: integer );
Var
error,x,y: integer;
Begin
error := -radius;
x := radius;
y := 0;
while (x >= y) do
Begin
Draw4Pixel(cx,cy, x, y);
if ( Not Vypln13 And ( x <> y) ) Then Draw4Pixel(cx,cy, y, x);
error := error + y;
y := y + 1;
error := error + y;
if (error >= 0) Then
Begin
if ( Vypln13) then Draw4Pixel(cx, cy, y - 1, x);
error := error - x;
x := x - 1;
error := error - x;
End;
End;
End;
Procedure Draw4Pixel(cx,cy,dx,dy: integer);
Begin
if ( (dx = 0) And (dy = 0) ) then
begin
PutPixel (cx , cy , Color13);
exit;
End;
IF Vypln13 Then
Begin
HorizontLine (cx - dx, cx + dx, cy + dy, Color13);
if ( dy = 0 ) then exit;
HorizontLine (cx - dx, cx + dx, cy - dy, Color13);
exit;
end;
PutPixel (cx + dx, cy + dy, Color13);
if ( dx <> 0 ) then
begin
PutPixel (cx - dx, cy + dy, Color13);
if ( dy = 0 ) then exit;
PutPixel (cx + dx, cy - dy, Color13);
End;
PutPixel (cx - dx, cy - dy, Color13);
End;
【讨论】:
以上是关于实心圆的中点圆算法的主要内容,如果未能解决你的问题,请参考以下文章