您的位置:首页 > 其它

光栅直线生成算法

2007-08-01 10:13 260 查看
前段时间做了一下光栅直线生成算法的研究,并且在VC下实现了DDA算法、Bresenham算法、对称算法、两步算法、及四步算法。这里给个总结,希望和大家交流。

一 总述

主要研究的算法主要有DDA算法、Bresenham算法、对称算法、两步算法、及四步算法,此外还对自适应多步位移码画线算法进行了一定研究。其中,DDA、Bresenham算法都是单步画线算法,其它是多步画线算法。

二 算法介绍

DDA算法和Bresenham算法是经典的画线算法,并且业已证明Bresenham画线算法使用了最小的计算量,是最高效的单步画线算法,这里不再对其进行说明。以下对其余各个算法进行必要的说明。
1 直线的bresenham算法
本算法由bresenham在1965年提出。设直线从起点到终点。直线可表示为方程y=mx+b。其中

b = y1 - m * x1,

m = (y2-y1)/(x2-x1)

我们的讨论先将直线方向限于1a象限在这种情况下,当直线光栅化时,x每次都增加1个单元,即

xi+1=xi+1

而y的相应增加应当小于1。为了光栅化,yi+1只可能选择如下两种位置之一。

图2.1.2

 

图2.1.2 yi+1的位置选择yi-1=yi 或者 yi+1=yi+1

选择的原则是看精确值y与yi及yi+1的距离d1及d2的大小而定。计算式为:

y=m(xi+1)+b (2.1.1)

d1=y-yi (2.1.2)

d2=yi+1-y (2.1.3)

如果d1-d2>0,则yi+1=yi+1,否则yi+1=yi。因此算法的关键在于简便地求出d1-d2的符号。将式、、代入d1-d2,得

d1-d2=2y-2yi-1=2(xi+1)-2yi+2b-1

用dx乘等式两边,并以pi=dx(d1-d2)代入上述等式,得

pi=2xidy-2yidx+2dy+dx(2b-1) (2.1.4)

d1-d2是我们用以判断符号的误差。由于在1a象限,dx总大于0,所以pi仍旧可以用作判断符号的误差。pi-1为:

pi+1=pi+2dy-2dx(yi+1-yi) (2.1.5)

误差的初值p1,可将x1, y1,和b代入式中的xi, yi而得到:
p1=2dy-dx
综述上面的推导,第1象限内的直线bresenham算法思想如下:
1、画点(x1, y2); dx=x2-x1; dy=y2-y1;
计算误差初值p1=2dy-dx; i=1;
2、求直线的下一点位置:
xi+1=xi+1;
if pi>0 则yi+1=yi+1;
否则yi+1=yi;
3、画点;
4、求下一个误差pi+1;
if pi>0 则pi+1=pi+2dy-2dx;
否则pi+1=pi+2dy;
5、i=i+1; if i<dx+1则转2;否则end。
bresenham算法的优点是:
1、不必计算直线之斜率,因此不做除法;
2、不用浮点数,只用整数;
3、只做整数加减法和乘2运算,而乘2运算可以用硬件移位实现。
bresenham算法速度很快,并适于用硬件实现。

由上述算法思想编制的程序如程序2.1.2。这个程序适用于所有8个方向的直线的生成。程序用色彩c画出一条端点为和的直线。其中变量的含义是:p是误差;const1和const2,是误差的逐点变化量;inc是y的单位递变量,值为1或-1;tmp是用作象限变换时的临时变量。程序以判断|dx|>|dy|为分支,并分别将2a, 3a象限的直线和3b, 4b象限的直线变换到1a, 4a和2b, 1b方向去,以求得程序处理的简洁。

void line (x1, y1, x2, y2, c)
int x1, y1, x2, y2, c;

{

int dx;

int dy;

int x;

int y;

int p;

int const1;

int const2;

int inc;

int tmp;

dx=x2-x1;

dy=y2-y1;

if (dx*dy>=0) /*准备x或y的单位递变值。*/

inc=1;

else

inc=-1;

if (abs(dx)>abs(dy)){

if(dx<0){

tmp=x1; /*将2a, 3a象限方向*/

x1=x2; /*的直线变换到1a, 4a*/

x2=tmp;

tmp=y1; /*象限方向去*/

y1=y2;

dx=-dy;

dy=-dy;

}

p=2*dy-dx;

const1=2*dy; /*注意此时误差的*/

const2=2*(dy-dy); /*变化参数取值. */

x=x1;

y=y1;

set_pixel(x, y, c);

while (x<x2){

x++;

if (p<0)

p+=const1;

else{

y+=inc;

p+=const2;

}

set_piexl(x, y, c);

}

}

else {

if (dy<0){

tmp=x1; /* 将3b, 4b象限方向的*/

x1=x2; /*直线变换到2b, 1b */

x2=tmp; /*象限方向去. */

tmp=y1;

y1=y2;

dx=-dy;

dy=-dy;

}

p=2*dx-dy; /*注意此时误差的*/

const1=2*dx; /*变化参数取值. */

const2=2*(dx-dy);

x=x1;

y=y1;

set_pixel (x, y, c);

while (y<y2){

y++;

if(p<0)

p+=const1;

else{

x+=inc;

p+=const2;

set_pixel (x, y, c);

}

}

}

 

2 对称直线生成算法

对称直线生成算法基于这样一个事实:直线以中点位界,其两边是对称的。因而可以利用这个对称性,对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;
}

3 两步算法

两步画线算法每判断一次就生成两个点,这与对称算法相似,不同的是对称算法是从两端向中间进行的,而该算法是始终沿一个方向进行的,一次生成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值就生成两个点,从而提高画线效率,但是要进行较多的判断,所以效率提高并不明显。

4 四步画线算法

四步画线算法类似域两步画线算法,不同的时每判断一次,就生成四个点。如图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。鉴于代码过长,这里不再给出。
5 自适应多步位移码画线算法
与传统的直线算法不同,自适应多步位移码画线算法首先将待绘制直线表达成一串由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算法快。

(一些算法的源程序较长,这里未给出。)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: