畫直線算法


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算法比中點畫線算法的應用范圍更廣。

 
轉載:https://www.cnblogs.com/icmzn/p/5045525.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM