Bresenham直線算法與畫圓算法


在我們內部開發使用的一個工具中,我們需要幾乎從 0 開始實現一個高效的二維圖像渲染引擎。比較幸運的是,我們只需要畫直線、圓以及矩形,其中比較復雜的是畫直線和圓。畫直線和圓已經有非常多的成熟的算法了,我們用的是Bresenham的算法。

計算機是如何畫直線的?簡單來說,如下圖所示,真實的直線是連續的,但我們的計算機顯示的精度有限,不可能真正顯示連續的直線,於是我們用一系列離散化后的點(像素)來近似表現這條直線。

 


  (上圖來自於互聯網絡,《計算機圖形學的概念與方法》柳朝陽,鄭州大學數學系)

接下來的問題就是如何盡可能高效地找到這些離散的點,Bresenham直線算法就是一個非常不錯的算法。

 

  Bresenham直線算法是用來描繪由兩點所決定的直線的算法,它會算出一條線段在 n 維光柵上最接近的點。這個算法只會用到較為快速的整數加法、減法和位元移位,常用於繪制電腦畫面中的直線。是計算機圖形學中最先發展出來的算法。

  (引自wiki百科布雷森漢姆直線演算法)

這個算法的流程圖如下:

 

可以看到,算法其實只考慮了斜率在 0 ~ 1 之間的直線,也就是與 x 軸夾角在 0 度到 45 度的直線。只要解決了這類直線的畫法,其它角度的直線的繪制全部可以通過簡單的坐標變換來實現。

下面是一個C語言實現版本。

// 交換整數 a 、b 的值
inline void swap_int(int *a, int *b) {
    *a ^= *b;
    *b ^= *a;
    *a ^= *b;
}
 
// Bresenham's line algorithm
void draw_line(IMAGE *img, int x1, int y1, int x2, int y2, unsigned long c) {
    // 參數 c 為顏色值
    int dx = abs(x2 - x1),
        dy = abs(y2 - y1),
        yy = 0;
 
    if (dx < dy) {
        yy = 1;
        swap_int(&x1, &y1);
        swap_int(&x2, &y2);
        swap_int(&dx, &dy);
    }
 
    int ix = (x2 - x1) > 0 ? 1 : -1,
         iy = (y2 - y1) > 0 ? 1 : -1,
         cx = x1,
         cy = y1,
         n2dy = dy * 2,
         n2dydx = (dy - dx) * 2,
         d = dy * 2 - dx;
 
    if (yy) { // 如果直線與 x 軸的夾角大於 45 度
        while (cx != x2) {
            if (d < 0) {
                d += n2dy;
            } else {
                cy += iy;
                d += n2dydx;
            }
            putpixel(img, cy, cx, c);
            cx += ix;
        }
    } else { // 如果直線與 x 軸的夾角小於 45 度
        while (cx != x2) {
            if (d < 0) {
                d += n2dy;
            } else {
                cy += iy;
                d += n2dydx;
            }
            putpixel(img, cx, cy, c);
            cx += ix;
        }
    }
}

可以看到,在畫線的循環中,這個算法只用到了整數的加法,所以可以非常的高效。

接下來,我們再來看一看Bresenham畫圓算法

Bresenham畫圓算法又稱中點畫圓算法,與Bresenham 直線算法一樣,其基本的方法是利用判別變量來判斷選擇最近的像素點,判別變量的數值僅僅用一些加、減和移位運算就可以計算出來。為了簡便起見,考慮一個圓 心在坐標原點的圓,而且只計算八分圓周上的點,其余圓周上的點利用對稱性就可得到。

為什么只計算八分圓周上的點就可以了呢?和上面的直線算法類似,圓也有一個“八對稱性”,如下圖所示。

 

顯然,我們只需要知道了圓上的一個點的坐標 (x, y) ,利用八對稱性,我們馬上就能得到另外七個對稱點的坐標。

和直線算法類似,Bresenham畫圓算法也是用一系列離散的點來近似描述一個圓,如下圖。

 


  (上圖來自於互聯網絡,《計算機圖形學的概念與方法》柳朝陽,鄭州大學數學系)

Bresenham畫圓算法的流程圖如下。

 

可以看到,與畫線算法相比,畫圓的循環中用到了整數的乘法,相對復雜了一些。

下面是一個C語言實現版本。

 

// 八對稱性
inline void _draw_circle_8(IMAGE *img, int xc, int yc, int x, int y, unsigned long c) {
    // 參數 c 為顏色值
    putpixel(img, xc + x, yc + y, c);
    putpixel(img, xc - x, yc + y, c);
    putpixel(img, xc + x, yc - y, c);
    putpixel(img, xc - x, yc - y, c);
    putpixel(img, xc + y, yc + x, c);
    putpixel(img, xc - y, yc + x, c);
    putpixel(img, xc + y, yc - x, c);
    putpixel(img, xc - y, yc - x, c);
}
 
//Bresenham's circle algorithm
void draw_circle(IMAGE *img, int xc, int yc, int r, int fill, unsigned long c) {
    // (xc, yc) 為圓心,r 為半徑
    // fill 為是否填充
    // c 為顏色值
 
    // 如果圓在圖片可見區域外,直接退出
    if (xc + r < 0 || xc - r >= img->w ||
            yc + r < 0 || yc - r >= img->h) return;
 
    int x = 0, y = r, yi, d;
    d = 3 - 2 * r;
 
    if (fill) {
        // 如果填充(畫實心圓)
        while (x <= y) {
            for (yi = x; yi <= y; yi ++)
                _draw_circle_8(img, xc, yc, x, yi, c);
 
            if (d < 0) {
                d = d + 4 * x + 6;
            } else {
                d = d + 4 * (x - y) + 10;
                y --;
            }
            x++;
        }
    } else {
        // 如果不填充(畫空心圓)
        while (x <= y) {
            _draw_circle_8(img, xc, yc, x, y, c);
 
            if (d < 0) {
                d = d + 4 * x + 6;
            } else {
                d = d + 4 * (x - y) + 10;
                y --;
            }
            x ++;
        }
    }
}
 
 

可以看到,Bresenham畫圓算法(中點圓算法)的實現也非常簡單。在另一個項目中,我還有一個 Python 實現的版本,不過那段代碼和其余部分結合得比較緊,有點難摳,這次就不貼出來了。

 


免責聲明!

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



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