在SO上找到了以下简单的绘制实心圆的算法:
for(int y=-radius; y<=radius; y++)
for(int x=-radius; x<=radius; x++)
if(x*x+y*y <= radius*radius)
setpixel(origin.x+x, origin.y+y);
有没有同样简单的算法来绘制填充椭圆?
在SO上找到了以下简单的绘制实心圆的算法:
for(int y=-radius; y<=radius; y++)
for(int x=-radius; x<=radius; x++)
if(x*x+y*y <= radius*radius)
setpixel(origin.x+x, origin.y+y);
有没有同样简单的算法来绘制填充椭圆?
更简单,没有double
和除法(但要小心整数溢出):
for(int y=-height; y<=height; y++) {
for(int x=-width; x<=width; x++) {
if(x*x*height*height+y*y*width*width <= height*height*width*width)
setpixel(origin.x+x, origin.y+y);
}
}
我们可以利用两个事实来进行优化:
int hh = height * height;
int ww = width * width;
int hhww = hh * ww;
int x0 = width;
int dx = 0;
// do the horizontal diameter
for (int x = -width; x <= width; x++)
setpixel(origin.x + x, origin.y);
// now do both halves at the same time, away from the diameter
for (int y = 1; y <= height; y++)
{
int x1 = x0 - (dx - 1); // try slopes of dx - 1 or more
for ( ; x1 > 0; x1--)
if (x1*x1*hh + y*y*ww <= hhww)
break;
dx = x0 - x1; // current approximation of the slope
x0 = x1;
for (int x = -x0; x <= x0; x++)
{
setpixel(origin.x + x, origin.y - y);
setpixel(origin.x + x, origin.y + y);
}
}
这种方法有效是因为每一条扫描线都比前一条短,至少比前一条短了同样多。由于将坐标舍入为整数像素,这不是完全准确的——扫描线长度可能比预期短一个像素。
换句话说,从最长的扫描线(即水平直径)开始,每条扫描线相对于前一条缩短的距离,代码中表示为dx
,最多减少1个像素,保持不变或增加。第一个内部循环找到当前扫描线比前一条扫描线短的准确数量,从dx-1
开始递减,直到我们刚好落在椭圆内部。
| x1 dx x0
|###### |<-->|
current scan line --> |########### |<>|previous dx
previous scan line --> |################ |
two scan lines ago --> |###################
|#####################
|######################
|######################
+------------------------
为了比较椭圆内部测试的数量,每个星号代表在朴素版本中测试的一对坐标:
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
...在改进版本中:
*
**
****
***
***
***
**
**
椭圆(关于原点)是一个沿着x或y轴线性拉伸的圆。因此,您可以像这样修改循环:
for(int y=-height; y<=height; y++) {
for(int x=-width; x<=width; x++) {
double dx = (double)x / (double)width;
double dy = (double)y / (double)height;
if(dx*dx+dy*dy <= 1)
setpixel(origin.x+x, origin.y+y);
}
}
setpixel()
,整个算法本身就不太可能高效。另外,任何优化编译器都会将dy
的计算提升出x
循环。 - Greg Hewgillwidth*height
,则可以进行整数运算。它简化为 x*height*x*height+y*width*y*width <= width*height*width*height
。 - Mark Ransomx*x+y*y <= radius*radius
with
Axx*x*x + 2*Axy*x*y + Ayy*y*y < radius*radius
(x'/a)^2 + (y'/b)^2 = 1
x' = C*x + S*y
y' = -S*x + C*y
1/a2 0
0 1/b2
T(Rx) A'' Rx = 1
T(x) T(R) A'' R x =1
T(x) A x = 1
其中,A 是使你的倾斜绘图扫描线算法工作的必需品,其值为:
A = T(R) A'' R =
C2/a2+S2/b2 SC(1/a2-1/b2)
SC/(1/a2-1/b2) S2/a2 + C2/b2