Bresenham快速畫直線演算法
現在的計算機的影象的都是用畫素表示的,無論是點、直線、圓或其他圖形最終都會以點的形式顯示。人們看到螢幕的直線只不過是模擬出來的,人眼不能分辨出來而已。那麼計算機是如何畫直線的呢,其實有比較多的演算法,這裡講的是Bresenham的演算法,是光柵化的畫直線演算法。直線光柵化是指用畫素點來模擬直線,比如下圖用藍色的畫素點來模擬紅色的直線。
給定兩個點起點P1(x1, y1), P2(x2, y2),如何畫它們直連的直線呢,即是如何得到上圖所示的藍色的點。假設直線的斜率0<k>0,直線在第一象限,Bresenham演算法的過程如下:
1.畫起點(x1, y1).
2.準備畫下一個點,X座標加1,判斷如果達到終點,則完成。否則找下一個點,由圖可知要畫的點要麼為當前點的右鄰接點,要麼是當前點的右上鄰接點。
2.1.如果線段ax+by+c=0與x=x1+1的交點y座標大於(y+*y+1))/2則選右上那個點
2.2.否則選右下那個點。
3.畫點
4.跳回第2步
5.結束
演算法的具體過程是怎樣的呢,其實就是在每次畫點的時候選取與實現直線的交點y座標的差最小的那個點,例如下圖:
關鍵是如何找最近的點,每次x都遞增1,y則增1或者不增1,由上圖,假設已經畫了d1點,那麼接下來x加1,但是選d2 還是u點呢,直觀上可以知道d2與目標直線和x+1直線的交點比較近即縱座標之差小也即與(x+1, y+1)點縱座標差大於0.5,所當然是選d2,其他點了是這個道理。
一、演算法原理簡介:
演算法原理的詳細描述及部分實現可參考:
假設以(x, y)為繪製起點,一般情況下的直觀想法是先求m = dy /dx(即x每增加1, y的增量),然後逐步遞增x, 設新的點為x1 = x + j, 則y1 = round(y + j * m)。可以看到,這個過程涉及大量的浮點運算,效率上是比較低的(特別是在嵌入式應用中,DSP可以一週期內完成2次乘法,一次浮點卻要上百個週期)。
下面,我們來看一下Bresenham演算法,如Fig. 1,(x, y +ε)的下一個點為(x, y + ε + m),這裡ε為累加誤差。可以看出,當ε+m < 0.5時,繪製(x + 1, y)點,否則繪製(x + 1, y + 1)點。每次繪製後,ε將更新為新值:
ε = ε + m ,如果(ε + m) <0.5 (或表示為2*(ε + m) < 1)
ε = ε + m – 1, 其他情況
將上述公式都乘以dx, 並將ε*dx用新符號ξ表示,可得
ξ = ξ + dy, 如果2*(ξ + dy) < dx
ξ = ξ + dy – dx, 其他情況
可以看到,此時運算已經全變為整數了。以下為演算法的虛擬碼:
ξ ← 0, y ← y1
For x ← x1 to x2 do
Plot Point at (x, y)
If (2(ξ + dy) < dx)
ξ ←ξ + dy
Else
y ← y + 1,ξ ←ξ + dy – dx
End If
End For
二、演算法的注意點:
在實際應用中,我們會發現,當dy > dx或出現Fig.2 右圖情況時時,便得不到想要的結果,這是由於我們只考慮dx > dy, 且x, y的增量均為正的情況所致。經過分析,需要考慮8種不同的情況,如Fig. 3所示:
當然,如果直接在演算法中對8種情況分別列舉, 那重複程式碼便會顯得十分臃腫,因此在設計演算法時必須充分考慮上述各種情況的共性,後面將給出考慮了所有情況的實現程式碼。
三、演算法的實現
以下程式碼的測試是利用Opencv 2.0進行的,根據需要,只要稍微修改程式碼便能適應不同環境
程式碼1:
int CEnginApp::Draw_Line(int x0, int y0, // starting position int x1, int y1, // ending position COLORREF color, // color index UNINT *vb_start, int lpitch) // video buffer and memory pitch { // this function draws a line from xo,yo to x1,y1 using differential error // terms (based on Bresenahams work) RECT cRect; //GetWindowRect(m_hwnd,&m_x2d_ClientRect); GetClientRect(m_hwnd, &cRect); ClientToScreen(m_hwnd, (LPPOINT)&cRect); ClientToScreen(m_hwnd, (LPPOINT)&cRect+1); vb_start = vb_start + cRect.left + cRect.top*lpitch; int dx, // difference in x's dy, // difference in y's dx2, // dx,dy * 2 dy2, x_inc, // amount in pixel space to move during drawing y_inc, // amount in pixel space to move during drawing error, // the discriminant i.e. error i.e. decision variable index; // used for looping // pre-compute first pixel address in video buffer vb_start = vb_start + x0 + y0*lpitch; // compute horizontal and vertical deltas dx = x1-x0; dy = y1-y0; // test which direction the line is going in i.e. slope angle if (dx>=0) { x_inc = 1; } // end if line is moving right else { x_inc = -1; dx = -dx; // need absolute value } // end else moving left // test y component of slope if (dy>=0) { y_inc = lpitch; } // end if line is moving down else { y_inc = -lpitch; dy = -dy; // need absolute value } // end else moving up // compute (dx,dy) * 2 dx2 = dx << 1; dy2 = dy << 1; // now based on which delta is greater we can draw the line if (dx > dy) { // initialize error term error = dy2 - dx; // draw the line for (index=0; index <= dx; index++) { // set the pixel *vb_start = color; // test if error has overflowed if (error >= 0) { error-=dx2; // move to next line vb_start+=y_inc; } // end if error overflowed // adjust the error term error+=dy2; // move to the next pixel vb_start+=x_inc; } // end for } // end if |slope| <= 1 else { // initialize error term error = dx2 - dy; // draw the line for (index=0; index <= dy; index++) { // set the pixel *vb_start = color; // test if error overflowed if (error >= 0) { error-=dy2; // move to next line vb_start+=x_inc; } // end if error overflowed // adjust the error term error+=dx2; // move to the next pixel vb_start+=y_inc; } // end for } // end else |slope| > 1 // return success return(1); } // end Draw_Line
程式碼2:
int CEnginApp::Draw_Line2(int x1,int y1,int x2, int y2,COLORREF color,UNINT *vb_start, int lpitch) { RECT cRect; //GetWindowRect(m_hwnd,&m_x2d_ClientRect); GetClientRect(m_hwnd, &cRect); ClientToScreen(m_hwnd, (LPPOINT)&cRect); ClientToScreen(m_hwnd, (LPPOINT)&cRect+1); vb_start = vb_start + cRect.left + cRect.top*lpitch; int dx = x2 - x1; int dy = y2 - y1; int ux = ((dx > 0) << 1) - 1;//x的增量方向,取或-1 int uy = ((dy > 0) << 1) - 1;//y的增量方向,取或-1 int x = x1, y = y1, eps;//eps為累加誤差 eps = 0;dx = abs(dx); dy = abs(dy); if (dx > dy) { for (x = x1; x != x2; x += ux) { Plot_Pixel_32(x,y,0,255,0,255,vb_start,lpitch); eps += dy; if ((eps << 1) >= dx) { y += uy; eps -= dx; } } } else { for (y = y1; y != y2; y += uy) { Plot_Pixel_32(x,y,0,255,0,255,vb_start,lpitch); eps += dx; if ((eps << 1) >= dy) { x += ux; eps -= dy; } } } return 1; }
呼叫程式碼:
DD_INIT_STRUCT(ddsd); if (FAILED(lpSface[PrimarySface]->Lock(NULL,&ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL))) return false; int x1,y1,x2,y2; for (int i=0;i<100;i++) { srand(time(0)); x1=rand()%750; y1=rand()%550; x2=rand()%750; y2=rand()%550; Draw_Line2(x1,y1,x2,y2,RGB(0,255,0),(UNINT *)ddsd.lpSurface,ddsd.lPitch>>2); } if (FAILED(lpSface[PrimarySface]->Unlock(NULL))) return false;
效果圖:
轉自:http://www.cnblogs.com/gamesky/archive/2012/08/21/2648623.html