橢圓和直線、圓一樣,是圖形學領域中的一種常見圖元,橢圓的生成算法(光柵轉換算法)也是圖形學軟件中最常見的生成算法之一。在平面解析幾何中,橢圓的方程可以描述爲(x – x0)2 / a2+ (y – y0)2 / b2 = 1,其中(x0, y0)是圓心座標,a和b是橢圓的長短軸,特別的,當(x0, y0)就是座標中心點時,橢圓方程可以簡化爲x2 / a2 + y2 / b2 = 1。在計算機圖形學中,橢圓圖形也存在在點陣輸出設備上顯示或輸出的問題,因此也需要一套光柵掃描轉換算法。爲了簡化,我們先考慮圓心在原點的橢圓的生成,對於中心不是原點的橢圓,可以通過座標的平移變換獲得相應位置的橢圓。
在進行掃描轉換之前,需要了解一下橢圓的對稱性,如圖(1)所示:
圖(1)橢圓的對稱性
中心在原點。焦點在座標軸上的標準橢圓具有X軸對稱、Y軸對稱和原點對稱特性,已知橢圓上第一象限的P點座標是(x, y),則橢圓在另外三個象限的對稱點分別是(x, -y)、(-x, y)和(-x, -y)。因此,只要畫出第一象限的四分之一橢圓,就可以利用這三個對稱性得到整個橢圓。
在光柵設備上輸出橢圓有很多種方法,可以根據直角平面座標方程直接求解點座標,yekeyii利用極座標方程求解,但是因爲涉及到浮點數取整,效果都不好,一般都不使用直接求解的方式。本文就介紹幾種計算機圖形學中兩種比較常用的橢圓生成方法:中點畫橢圓算法和Bresenham橢圓生成算法。
1、 中點畫橢圓法
中點在座標原點,焦點在座標軸上(軸對齊)的橢圓的平面集合方程是:
x2 / a2 + y2 / b2 = 1,也可以轉化爲如下非參數化方程形式:
F(x, y) = b2x2 + a2y2 - a2b2 = 0 (方程 1)
無論是中點畫線算法、中點畫圓算法還是本節要介紹的中點畫橢圓算法,對選擇x方向像素Δ增量還是y方向像素Δ增量都是很敏感的。舉個例子,如果某段圓弧上,x方向上增量+1個像素時,y方向上的增量如果 < 1,則比較適合用中點算法,如果y方向上的增量 > 1,就會產生一些跳躍的點,最後生成的光柵位圖圓弧會有一些突變的點,看起來好像不在圓弧上。因此,對於中點畫圓弧算法,要區分出橢圓弧上哪段Δx增量變化顯著,哪段Δy增量變化顯著,然後區別對待。由於橢圓的對稱性,我們只考慮第一象限的橢圓圓弧,如圖(2)所示:
圖(2)第一象限橢圓弧示意圖
定義橢圓弧上某點的切線法向量N如下:
對方程1分別求x偏導和y偏導,最後得到橢圓弧上(x,y)點處的法向量是(2b2x, 2a2y)。dy/dx = -1的點是橢圓弧上的分界點。此點之上的部分(橙褐色部分)橢圓弧法向量的y分量比較大,即:2b2(x + 1) < 2a2(y – 0.5);此點之下的部分(藍紫色部分)橢圓弧法向量的x分量比較大,即:2b2(x + 1) > 2a2(y – 0.5)。
對於圖(2)中橙褐色標識的上部區域,y方向每變化1個單位,x方向變化大於一個單位,因此中點算法需要沿着x方向步進畫點,x每次增量加1,求y的值。同理,對於圖(2)中藍紫色標識的下部區域,中點算法沿着y方向反向步進,y每次減1,求x的值。先來討論上部區域橢圓弧的生成,如圖(3)所示:
圖(3)中點畫橢圓算法對上部區域處理示意圖
假設當前位置是P(xi, yi),則下一個可能的點就是P點右邊的P1(xi+1, yi)點或右下方的P2(xi+1, yi-1)點,取捨的方法取決於判別式di,di的定義如下:
di = F(xi+1, yi-0.5) = b2(xi+1)2 + a2(yi-0.5)2 – a2b2
若di < 0,表示像素點P1和P2的中點在橢圓內,這時可取P1爲下一個像素點。此時xi+1 = xi + 1,yi+1 = yi,代入判別式di得到di+1:
di+1 = F(xi+1+1, yi+1-0.5) = b2(xi+2)2 + a2(yi-0.5)2 – a2b2 = di + b2(2xi + 3)
計算出di的增量是b2(2xi + 3)。同理,若di >= 0,表示像素點P1和P2的中點在橢圓外,這時應當取P2爲下一個像素點。此時xi+1 = xi + 1,yi+1 = yi - 1,代入判別式di得到di+1:
di+1 = F(xi+1+1, yi+1-0.5) = b2(xi+2)2 + a2(yi-1.5)2 – a2b2 = d1 + b2(2xi+3) + a2(-2yi+2)
計算出di的增量是b2(2xi+3)+a2(-2yi+2)。計算di的增量的目的是減少計算量,提高算法效率,每次判斷一個點時,不必完整的計算判別式di,只需在上一次計算出的判別式上增加一個增量即可。
接下來繼續討論下部區域橢圓弧的生成,如圖(4)所示:
圖(4)中點畫橢圓算法對下部區域處理示意圖
假設當前位置是P(xi, yi),則下一個可能的點就是P點左下方的P1(xi -1, yi-1)點或下方的P2(xi, yi-1)點,取捨的方法同樣取決於判別式di,di的定義如下:
di = F(xi+0.5, yi-1) = b2(xi+0.5)2 + a2(yi-1)2 – a2b2
若di < 0,表示像素點P1和P2的中點在橢圓內,這時可取P2爲下一個像素點。此時xi+1 = xi + 1,yi+1 = yi - 1,代入判別式di得到di+1:
di+1 = F(xi+1+0.5, yi+1-1) = b2(xi+1.5)2 + a2(yi-2)2 – a2b2 = di + b2(2xi+2)+a2(-2yi+3)
計算出di的增量是b2(2xi+2)+a2(-2yi+3)。同理,若di >= 0,表示像素點P1和P2的中點在橢圓外,這時應當取P1爲下一個像素點。此時xi+1 = xi,yi+1 = yi - 1,代入判別式di得到di+1:
di+1 = F(xi+1+0.5, yi+1-1) = b2(xi+0.5)2 + a2(yi-2)2 – a2b2 = d1 + a2(-2yi+3)
計算出di的增量是a2(-2yi+3)。
中點畫橢圓算法從(0, b)點開始,第一個中點是(1, b – 0.5),判別式d的初始值是:
d0 = F(1, b–0.5) = b2 + a2(-b+0.25)
上部區域生成算法的循環終止條件是:2b2(x + 1) >= 2a2(y – 0.5),下部區域的循環終止條件是y = 0,至此,中點畫橢圓算法就可以完整給出了:
20 void MP_Ellipse(int xc , int yc , int a, int b) 21 { 22 double sqa = a * a; 23 double sqb = b * b; 24 25 double d = sqb + sqa * (-b + 0.25); 26 int x = 0; 27 int y = b; 28 EllipsePlot(xc, yc, x, y); 29 while( sqb * (x + 1) < sqa * (y - 0.5)) 30 { 31 if (d < 0) 32 { 33 d += sqb * (2 * x + 3); 34 } 35 else 36 { 37 d += (sqb * (2 * x + 3) + sqa * (-2 * y + 2)); 38 y--; 39 } 40 x++; 41 EllipsePlot(xc, yc, x, y); 42 } 43 d = (b * (x + 0.5)) * 2 + (a * (y - 1)) * 2 - (a * b) * 2; 44 while(y > 0) 45 { 46 if (d < 0) 47 { 48 d += sqb * (2 * x + 2) + sqa * (-2 * y + 3); 49 x++; 50 } 51 else 52 { 53 d += sqa * (-2 * y + 3); 54 } 55 y--; 56 EllipsePlot(xc, yc, x, y); 57 } 58 } |
EllipsePlot()函數利用橢圓的三個對稱性,一次完成四個對稱點的繪製,因爲簡單,此處就不再列出代碼。
2、 Bresenham算法
中點畫橢圓法中,計算判別式d使用了浮點運算,影響了橢圓的生成效率。如果能將判別式規約到整數運算,則可以簡化計算,提高效率。於是人們針對中點畫橢圓法進行了多種改進,提出了很多種中點生成橢圓的整數型算法,Bresenham橢圓生成算法就是其中之一。
在生成橢圓上部區域時,以x軸爲步進方向,如圖(5-a)所示:
圖(5)Bresenham橢圓生成算法判別式
x每步進一個單位,就需要在判斷y保持不變還是也步進減1,bresenham算法定義判別式爲:
D = d1 – d2
如果D < 0,則取P1爲下一個點,否則,取P2爲下一個點。採用判別式D,避免了中點算法因y-0.5而引入的浮點運算,使得判別式規約爲全整數運算,算法效率得到了很大的提升。根據橢圓方程,可以計算出d1和d2分別是:
d1 = a2(yi2 – y2)
d2 = a2(y2 – yi+12)
以(0, b)作爲橢圓上部區域的起點,將其代入判別式D可以得到如下遞推關係:
Di+1 = Di + 2b2(2xi + 3) (Di < 0)
Di+1 = Di + 2b2(2xi + 3) – 4a2(yi - 1) (Di >= 0)
D0 = 2b2 – 2a2b + a2
在生成橢圓下部區域時,以y軸爲步進方向,如圖(5-b)所示,y每步進減一個單位,就需要在判斷x保持不變還是步進加一個單位,對於下部區域,計算出d1和d2分別是:
d1 = b2(xi+12 – x2)
d2 = b2(x2 – xi2)
以(xp, yp)作爲橢圓下部區域的起點,將其代入判別式D可以得到如下遞推關係:
Di+1 = Di – 4a2(yi - 1) + 2a2 (Di < 0)
Di+1 = Di + 2b2(xi + 1) – 4a2(y - 1) + 2a2 + b2 (Di >= 0)
D0 = b2(xp + 1)2 +b2xp2 - 2a2b2 + 2a2(yp - 1)2
根據以上分析,Bresenham橢圓生成算法的實現就比較簡單了:
61 void Bresenham_Ellipse(int xc , int yc , int a, int b) 62 { 63 int sqa = a * a; 64 int sqb = b * b; 65 66 int x = 0; 67 int y = b; 68 int d = 2 * sqb - 2 * b * sqa + sqa; 69 EllipsePlot(xc, yc, x, y); 70 int P_x = ROUND_INT( (double)sqa/sqrt((double)(sqa+sqb)) ); 71 while(x <= P_x) 72 { 73 if(d < 0) 74 { 75 d += 2 * sqb * (2 * x + 3); 76 } 77 else 78 { 79 d += 2 * sqb * (2 * x + 3) - 4 * sqa * (y - 1); 80 y--; 81 } 82 x++; 83 EllipsePlot(xc, yc, x, y); 84 } 85 86 d = sqb * (x * x + x) + sqa * (y * y - y) - sqa * sqb; 87 while(y >= 0) 88 { 89 EllipsePlot(xc, yc, x, y); 90 y--; 91 if(d < 0) 92 { 93 x++; 94 d = d - 2 * sqa * y - sqa + 2 * sqb * x + 2 * sqb; 95 } 96 else 97 { 98 d = d - 2 * sqa * y - sqa; 99 } 100 } 101 } |
總結一下,本文介紹了兩種計算機圖形學中常見的橢圓生成算法,實際上還有很多種改進算法,包括提高效率,引入反走樣技術等等,但那已經不是本文的重點,能把算法的基本原理講清楚纔是本文的目的。
參考資料:
【1】計算幾何:算法設計與分析周培德 清華大學出版社 2005年
【2】計算幾何:算法與應用德貝爾赫(鄧俊輝譯) 清華大學出版社 2005年
【3】計算機圖形學孫家廣、楊常貴清華大學出版社 1995年