常用直线生成算法
最近看到灿兄的一篇博客,讲的是“DDA算法和Bresenham算法”,里面有些部分不是很详细,对算法的描述灿兄很不错,但是对于有人说DDA和Bresenham直线算法的效率说是一样的,下面就对这些算法的效率问题浅谈一二,文笔不是很好,大家见谅。
1:数值微分法(DDA)
DDA(Digital Differential Analyzer)方法是使用δx活δy的一个线段扫描转换算法。在一个坐标轴上以单位间隔对线段采样,从而决定另一个坐标轴上最靠近线段路径的对应的整数数值。
先考虑斜率为正,从左端点到右端点进行处理的线段。若斜率k<=1,则在单位间隔(δx=1)取样并计算每个顺序的y值:
k=δy/δx。其中δx=x1-x0,δy=y1-y0,(x0,y0)和(x1,y1)分别是直线的端点坐标。
然后从直线的起点开始,确定最佳逼近直线的y个坐标。假定端点坐标均为整数,让x从起点到终点,每步递增1,计算对应的y坐标,y=kx+b,并取像素(x,round(y))。这种方式简单易懂,但是运算效率较低,因为每计算一次,都需要一个浮点数乘法和一个四舍五入运算,由于:
yk+1=kx+1+b = k(x1+δx)+b = kx1+b+kδx = y1+kδx
因此,当δx=1时,则yk+1=y1+δk,即当x每递增1,y递增δk。
上面的分析仅仅适用于abs(k)<=1的情况,在这种情况下,x每增加1,y最多增加1,所以在迭代过程中的每一步,只需要确定一个像素即可;而当斜率abs(k)>1时,可以把x和y的坐标交换,也就是说y每增加1,x相应的增加1/k。若斜率为0或者无穷大的情况下,比较简单,不再赘述。
优缺点:DDA算法是利用光栅的特性消除了直线方程的乘法,而在x和y方向使用合适的增量来逐步沿着直线的路径推出各像素位置,但该算法中的y何k(斜率)必须用浮点数表示,而且每一步运算都必须对y进行四舍五入取整,在增量连续迭代中取整的误差积累会使长线段所计算的像素位置偏离实际位置,并且取整运算与浮点数运算比较耗时。
2:中点画线法
为了方便说明,假定直线斜率在区间(0,1),其他情况可以类似推导。若直线在x方向增加1个单位,y方向增量只能在0~1之间,假设x坐标为xp的各像素点中,与直线最近的为(xp,yp),那么下个点只能在该点的正右方(xp+1,yp)或者右上方(xp+1,yp+1)两者之一,到底选择那一个,只要看这两个点离直线轨迹更近就是那个点。如果用M表示这两点的中点,即M=(xp+1,yp+0.5)。又假设Q是直线与x=xp+1的交点。显然,若M在Q点上方,则(xp+1,yp)离直线近,反之,(xp+1,yp+1)离直线近。
优缺点:该算法不用浮点数的计算,只包含整数变量,适合硬件实现,代码如下:
3:Bresenham算法
Bresenham算法是使用最广泛的直线生成算法,为了方便讨论,假设斜率在区间(0,1)中,其他情况类似。与中点算法类似,Bresenham算法也是通过每列像素上确定与理想直线最近的像素来进行处理的。原理是,过各行各列像素中心构造一组虚拟网格线,按照直线从起点到终点的顺序计算直线与各垂直网格线的交点,然后确定该列像素中与此交点最近的像素。下面说明推导过程。
设直线方程为yi+1=yi+k(xi+1-xi)+k,假设列坐标像素已经确定为xi,行坐标为yi;那么下一个点的列坐标为xi+1,而行坐标要么为yi,要么递增1为yi+1。是否递增1取决于误差项d的值。误差项d初始值为0,x坐标每增加1,d的值相应的递增直线的斜率k,一旦d>=1,就将它减去1,这样保证d只能在0~1之间。当d>=0.5时,直线与垂线x=xi+1交点最接近于当前像素(xi,yi)的右上方像素(xi+1,yi+1);而当d<0.5时,更接近于右方像素(xi+1,yi)。为了方便计算,令e=d-0.5,e的初值为-0.5,增量为k。这样就可以将d和0.5的比较转换为e和0的比较。
优缺点:Bresenham算法在计算直线的斜率的与误差的时候用到浮点数和除法运算,可以改进使用整数来避免除法,由于算法中只用到的是误差项的符号,所以可以试用2*e*dx做判别。这样就彻底将浮点数运算和除法运算去除,算法效率比较高。