前段时间做了一下光栅直线生成算法的研究,并且在VC下实现了DDA算法、Bresenham算法、对称算法、两步算法、及四步算法。这里给个总结,希望和大家交流。
主要研究的算法主要有DDA算法、Bresenham算法、对称算法、两步算法、及四步算法,此外还对自适应多步位移码画线算法进行了一定研究。其中,DDA、Bresenham算法都是单步画线算法,其它是多步画线算法。
DDA算法和Bresenham算法是经典的画线算法,并且业已证明Bresenham画线算法使用了最小的计算量,是最高效的单步画线算法,这里不再对其进行说明。以下对其余各个算法进行必要的说明。
对称直线生成算法基于这样一个事实:直线以中点位界,其两边是对称的。因而可以利用这个对称性,对Bresenham算法进行改进,使得每进行一次判断就可以生成相对于直线中点的两个对称点。如此以来,直线就由两端向中间生成。算法用C++语言描述如下:
int x1 = m_start.m_x; // 起点x
int y1 = m_start.m_y; // 起点y
int x2 = m_end.m_x; // 终点x
int y2 = m_end.m_y; // 终点y
int dx = m_end.m_x - m_start.m_x;
int dy = m_end.m_y - m_start.m_y;
int dx2 = dx << 1;
int dy2 = dy << 1;
int e = dy2 - dx; // 决策量
int half = (dx+1) >> 1;
for (int i=0; i<half; i++)
{
pDC->SetPixel(x1, y1, color);
pDC->SetPixel(x2, y2, color);
if (e > 0) // 当e>0时,始端y向加1,末端y向减1
{
y1++;
y2--;
e -= dx2;
}
//始端x向加1,末端x向减1
x1++;
x2--;
e += dy2;
}
两步画线算法每判断一次就生成两个点,这与对称算法相似,不同的是对称算法是从两端向中间进行的,而该算法是始终沿一个方向进行的,一次生成2个连续的点。当斜率k满足条件0≤k<1时,考虑当前点P,如图1所示,直线与AB的交点一定落在AB区间的四等分中之一。
图1 下一待选点位置必在四个等分区间之一
实际上,两个连续点可能出现的形式只有四种情况,即DD、DX、XD和XX。其中X表示沿水平方向移动一个像素,D表示沿对角方向移动一个像素,见图2。
图2 连续两点可能出现的4种形式
决策量e的初值为e=4dy–dx,对于Pattern 1,e的增量为4dy-4dx; 对于Pattern 2和3,e的增量为4dy-2dx;对于Pattern 4, e的增量为4dy。算法用C++语言描述如下:
int x = m_start.m_x; // 起点x
int y = m_start.m_y; // 起点y
int dx = m_end.m_x - m_start.m_x;
int dy = m_end.m_y - m_start.m_y;
int dx2 = dx << 1;
int dy2 = dy << 1;
int dx4 = dx2 << 1;
int dy4 = dy2 << 1;
int inc4 = dy4 - dx4;
int inc2 = dy4 - dx2;
int e = dy4 - dx;
pDC->SetPixel(x, y, color);
for (int i=0; i<dx; i+=2)
{
if (e > dx)
{
// Pattern 4
if (e > dx2)
{
e += inc4;
x++;
y++;
pDC->SetPixel(x, y, color);
x++;
y++;
pDC->SetPixel(x, y, color);
}
// Pattern 3
else
{
e += inc2;
x++;
y++;
pDC->SetPixel(x, y, color);
x++;
pDC->SetPixel(x, y, color);
}
}
else
{
// Pattern 2
if (e > 0)
{
e += inc2;
x++;
pDC->SetPixel(x, y, color);
x++;
y++;
pDC->SetPixel(x, y, color);
}
// Pattern 1
else
{
x++;
pDC->SetPixel(x ,y, color);
x++;
pDC->SetPixel(x, y, color);
e += dy4;
}
}
}
每计算一次e值就生成两个点,从而提高画线效率,但是要进行较多的判断,所以效率提高并不明显。
四步画线算法类似域两步画线算法,不同的时每判断一次,就生成四个点。如图3所示,直线一次前进四个点的情形。
图3 四步画线算法示意图
对于四步画线算法,规的跟两步算法相似的表示方法,共有14种形式,如图4。
图4 连续四点肯能出现的14种形式
图4中,自底向上依次是Pattern 1到Pattern 14,Pattern 1为XXXX,Pattern 2为XXXD,……,Pattern 14为DDDD。
初始时,e=-dx, Pattern 1时e的增量为8dy;Pattern 2到Pattern 4时e的增量为8dy-2dx;Pattern 5到Pattern 8时e的增量为8dy-4dx;Pattern 9到Pattern 13时e的增量为8dy-6dx;Pattern 14时e的增量为8dy-8dx。鉴于代码过长,这里不再给出。
与传统的直线算法不同,自适应多步位移码画线算法首先将待绘制直线表达成一串由0和1组成的位移码,根据位移码的特点,自适应的确定一次生成的像素数目,从而获得更高的绘制效率。这里引入两个引理:
引理1 设有从(x1, y1)到(x2, y2)的直线,0<H=y2-y1<K=x2-x1。那么在该直线的位移码中,相邻两个1之间连续0的数目w为[(K-H)/H]或[K/H]。[A]表示对A取整。
引理2设有从(x1, y1)到(x2, y2)的直线,0<H=y2-y1<K=x2-x1。那么在该直线的位移码中,相邻两个0之间连续1的数目w为[H/(K-H)]或[K/(K-H)]。
证明略。
这里给出自适应多步位移码画线算法的伪代码:
K = x2 – x1;
H = y2 – y1;
T = [(K – 1)/2] – H;
Divide(H, K – H, N, Tchg);
x = x1;
y = y1;
WHILE x<= x2 DO
IF T<0 THEN
y = y +1;
xend = x+N;
WHILE x<xend Do
DrawPixel(x, y);
x = x +1;
END WHILE
T = T+Tchg;
ELSE
T = T – H;
DrawPixel(x, y);
END IF
x = x +1;
END WHILE
从理论而言,该算法根据直线斜率自适应地选择一个步长, 以一次判断生成多个采样点的方式对直线进行绘制,从而大大提供了绘制效率。但是实际上,连续两个1(0)中间0(1)的个数虽然只在两个整数上选择,但是到底在哪一步是哪一个难以判断,所以实现起来并不容易,而且目前尚未有解决方法。
|
Bresenham |
对称 |
两步 |
四步 |
加法 |
1 |
0.5 |
1 |
1 |
比较 |
2 |
1 |
1.5 |
1.3 |
加法和比较 |
3 |
1.5 |
2.5 |
2.3 |
乘法 |
2 |
3 |
4 |
6 |
上表给出平均每步所进行的加法次数和比较次数,乘除次数是整算法总共的次数。DDA算法未在表中给出,它与Bresenham相似,但使用了多次浮点乘除运算和取整运算,效率明显低于其它算法。Bresenham与其它算法相比,加法步骤相当(对称算法除外),但比较次数较多。理论上而言,后面的算法效率较高。但是应该注意到多步算法,如两步和四步算法,在进行循环之前要做很多的初始化工作,而且乘除次数大于Bresenham算法。在进行较短直线绘制时,效率反而不如Bresenham算法快。
(一些算法的源程序较长,这里未给出。)