直线算法

DDA画线算法

一.算法介绍

DDA是一种增量算法,也就是说通过对前一个点在X和Y轴方向上加上一个增量,从而得到一个新点得坐标。这个算法要求先算出直线的斜率,然后从起点开始,确定最佳逼近于直线

的y坐标。假设起点的坐标为整数,直线方程为y=kx+b,k的取值在0到1之间,x每递增1,y相应地递增k。因为像素的坐标是整数,所以y需要进行取整处理。对新坐标行四舍五入得到

整型y值,确定一个要渲染得像素点。从而画出一条直线。

 

二.算法解析:

当 | k | <= 1时:

已知过端点 P0(x0, y0),P1(x1, y1) 的直线段 L(P0, P1);直线斜率为 k = (y1 - y0) / (x1 - x0)。于是 yi+1 = kxi+1 + b。

1.x每递增1,y相应地递增k,每次计算点的坐标,我只需要x+1,y+k就行了,避免了乘除,而只有加减。

           ↓

2.通过增量得到下一个点后,可知此坐标肯定会与实际像素点有偏差(实际的直线并不是和像素点完全重合的)。所以采取四舍五入得方式来平衡差值,但是

C/C++中取整是直接舍去小数值,所以采取加上0.5再取整,即int(y+0.5)。从而达到一个四舍五入的目的。

 

当 | k | > 1时:

若仍然使 k = (y1 - y0) / (x1 - x0),则会导致斜率大于1。从而在x递增1的时候y可能会出现递增大于1的数,使得像素点不相邻,直线不连续,出现断点的情况。

此时应令k = (x1 - x0) / (y1 - y0),然后执行y+1,x+k 。最后再对x四舍五入int(x+0.5)。

 

三、代码

// 数值微分法,伪代码。通过此代码可理解算法的基本思路

当 | k | <= 1时:

当 | k | > 1时:

综上可得:

 

中点画线算法

      图1                    图2

引用这2个图是因为实际像素为图1,图2中为了方便算法的描绘,采用像素中心来代替整个像素点。所以不要单纯由图2产生错误理解,要联系实际的物理分布。

一、算法介绍

这里的中点指的是直线与虚拟网格交点Q的上下像素点的连线中点M。将M与Q点进行比较,若Q与M重合或者在M之下,则渲染P1点。else,渲染上方

的P2点。从而渲染出一条直线。

 

二、算法解析

1.设坐标的单位为1,斜率k<1,起点和终点为 (x0, y0)和(x1, y1),则F(x, y) = ax + by + c, 其中a = y0 - y1, b = x1 - x0,c = x0y1 - x1y0

a,b,c是如何得到的?

解:

已知直线上两点求直线的一般式方程 

已知直线上的两点P1(X1,Y1) P2(X2,Y2), P1 P2两点不重合。
对于AX+BY+C=0:
当x1=x2时,直线方程为x-x1=0
当y1=y2时,直线方程为y-y1=0
当x1≠x2,y1≠y2时,直线的斜率k = (y2-y1) / (x2-x1)
故直线方程为y-y1 = (y2-y1) / (x2-x1) × (x-x1)
即x2y-x1y-x2y1+x1y1 = (y2-y1)x - x1(y2-y1)
即(y2-y1)x-(x2-x1)y-x1(y2-y1)+(x2-x1)y1=0
即(y2-y1)x+(x1-x2)y+x2y1-x1y2=0 ①
可以发现,当x1=x2或y1=y2时,①式仍然成立。所以直线AX+BY+C=0的一般式方程就是:
A = Y2 - Y1
B = X1 - X2
C = X2*Y1 - X1*Y2
 

2.算法流程

当 | k | <= 1时

对于直线上的点,F(x, y) = 0;直线上方的点,F(x, y) > 0;直线下方的点,F(x, y) < 0.  因此,判断M在Q的上方还是下方,只需将M点带入方程计算判别式:

d = F(M) = F(xp + 1, yp + 0.5) = a(xp + 1) + b(yp + 0.5) + c(下一个点x轴加1,y轴取中点所以加0.5
             ↓
当d >= 0时,取正右方像素p1也就是取直线下方的点。(此处易产生误解,要记住代入的是中点,F(M)>=0说明中点在直线之上或在直线上所以取靠近直线的点,也就是下方的点
在这种情况下欲判断再下一个像素应取哪个,应计算:d1 = F(xp +1 + 1, yp + 0.5) = a(xp + 1 + 1) + b(yp + 0.5) + c  = d + a,故增量为a。
解释:要渲染的点是直线下方的点,也就是(xp + 1, yp),所以再下一个中点仍然是(xp + 1 +1, yp + 0.5)。只要d>=0则中点y值不改变。
             ↓
当d<0时,此时则取右上方的像素p2,这种情况下要判断再下一个像素,则应该计算:
d2 = F(xp +1 + 1, yp + 1 + 0.5) = a(xp + 1 + 1) + b(yp + 1 + 0.5) + c  = d + a + b,d的增量为 a + b。
解释:下一个点的坐标是(xp + 1, yp + 1),再下一个中点是(xp + 1 +1, yp + 1 + 0.5),只要d<0则中点y值+1。
            ↓
d的初始值,第一个像素应取左端点(x0, y0),相应的判别式为:d0 =F(x0 + 1, y0 + 0.5) = a(x0 + 1) + b(yp + 0.5) + c=  ax0 + by0 + c + a + 0.5b= F(x0, y0) + a + 0.5b
由于(x0, y0)在直线上,所以F(x0, y0) = 0,因此,d的初始值为a + 0.5b。d的初始值包含小数,因此可以用2d来代替d。

解释:为何要用2d代替d?这是为了来摆脱浮点运算,只进行整数的加法,相比于DDA算法进一步提高了效率。而d只是用来比较正负,所以乘以倍数不会改变符号。

 

当 | k | > 1时

方法与DDA同理

 

三、代码

以下代码为|k|<=1时,只包含整数运算的方法:

void MidPointLine(int x0, int y0, int x1, int y1, int color)

{

     int a, b, delta1, delta2, d, x, y;

     a = y0 - y1;

     b = x1 - x0;

     d = 2 * a + b;      //求d的初值

     delta1 = 2 * a;

     delta2 = 2 * (a + b);   //两种不同情况下的增量

     x = x0;

     y = y0;

     DrawPixel(x, y, color);

     while (x < x1)

     {

          if (d < 0)

          {

               ++x;      //取右上方的点

               ++y;

               d += delta2;

          }

         else

          {

               ++x;      //取正右方的点

               d += delta1;

          }

          DrawPixel(x, y, color);

     }

}
 
 
Bresenham画线算法
一、算法介绍
该算法是通过各行、各列像素中心构造一组虚拟网格线,按照直线起点到终点的顺序,计算直线与各垂直网格线的交点,然后根据误差项的符号确定该列像素点中与此交点最近的元素。

 

二、算法解析

y

 

1.如图,假设每次x + 1,而y要么不变,要么递增1,是否递增1取决于误差项d的值(如上图所示)。因为直线起始点在像素中心,所以误差项d的初始值为0,x下标每增加1,d的值相应递增直线的斜率值,即d = d + k.  一旦d >= 1,将它的值减去1,使得d的值总保持在0到1之间。

当d >= 0.5时,直线与x + 1列垂直网格线交点最接近于当前像素的右上方像素点(x + 1, y + 1),d < 0.5时则为正右方像素(x + 1, y)。

 

2.为了将算法也改进为整数加减,所以要进行变动。

改进1:大小判定简化到正负判定

令 e = d - 0.5,当e >= 0时 ,下一个像素值y递增1,e < 0时y不递增。此时e初始值为-0.5。每走一步e=e+k,当渲染后需再次对e进行判定,以保证其相对区间。

当e>0时,e=e-1。(此处-1操作会产生误解,可以转换对象来看,e只是为了转换d的判别形式,所以可以直接看d。e>0与d>0.5一致,下一次取点d>1,此时d-1所以e-1

 ↓

改进2:去除0.5这个浮点数运算

两边同乘2得:2e=2d-1,2e=2e+2k

改进3:去除求斜率k=dy/dx的除法运算

两边再同乘dx得:2dx*e = 2dx*d - dx,2dx*e = 2dx*e + 2dx*k = 2dx*e + 2dy。令e=2dx*e得,e=e-dx,e=e+2dy。同理,在e>0时执行的e=e-1变为e=e-2dx。

结果:初始值d=0,化简上式可得 ①e0 = -dx   ②e = e + 2dy ③e=e-2dx

 

 

最后总结一下具体步骤:

1.输入直线的两个端点P0(x0,y0)和P1(x1,y1)

2.计算初始值Δx、Δy、e=-Δx、x=x0,y=y0

3.绘制点(x,y)

4.e更新为e+2Δy,判断e的符号,若e>0,则(x,y)更新为(x+1,y+1),同时将e更新为e-2Δx;else 将(x,y) 更新为(x+1,y)

5.当直线没有画完时,重复3和4,否则结束。

 

三、代码

//伪代码如下

void Bresenham(int x0, int y0, int x1, int y1, int color)

{

   int x, y, dx, dy;

   float k, e;

   dx = x1 - x0;

   dy = y1 - y0;

   e = -dx

   x = x0;

   y = y0;

   for (int i = 0; i <= dx; ++i)

   {

      DrawPixel(x, y, color);

      x += 1;

      e = e + 2 * dy;

      if (e >= 0)   // 伪代码,实际比较浮点数不这样进行比较

      {

          y += 1;

          e = e - 2 * dx;

      }

   }

}

 

三种算法的对比

一、DDA

采用的是浮点数运算,不易于硬件实现。

二、中点画线算法

只有整数运算,不含乘除,可用硬件实现。相对于DDA提高了效率

三、Bresenham算法

1.在渲染点的循环中没有计算直线的斜率,不用做除法。

2.不用浮点数,只做整数加法和乘2运算,乘2运算可用硬件位移实现。

3.应用范围不拘束于直线的方程形式

4.Bresenham算法”其实“比较像数值微分法,也是增量算法,但是相对来说更利于硬件实现。

 

四、总结

中点画线算法比DDA效率更高,Bresenham算法比中点画线算法的应用范围更广。

posted on 2018-09-14 01:41  Jing_Rui  阅读(2490)  评论(0编辑  收藏  举报

导航