1. 程式人生 > >Bresenham快速畫直線演算法

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