Bresenham 演算法詳解
這篇文章講的非常詳細,這裡列出幾點注意事項
1、bresenham的起始點是整數點,所有初始的 error值 由 y1 來計算, 即 m - 0.5
2、當error值大於0時,取了上面的點,A點變成了 y+1 上的A點,所以必須減去1 才能做正確判斷。具體參照下面內容的公式推導。
貼上AS3程式碼
package { import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.Sprite; import flash.display.StageScaleMode; import flash.events.UncaughtErrorEvent; import flash.geom.Point; [SWF(frameRate="60", backgroundColor="0",height="600",width="800")] public class LineTest extends Sprite { private var bmd:BitmapData = new BitmapData(800,600, false, 0); private var bmp:Bitmap = new Bitmap(bmd); private var error_factor:Number = 0.5; public function LineTest() { stage.scaleMode = StageScaleMode.NO_SCALE; addChild(bmp); draw_help3(new Point(0,300), new Point(800,300), 0xffffff); draw_help3(new Point(400,0), new Point(400,600), 0xffffff); draw_help3(new Point(400,300), new Point(500,500),0x00ffff); draw_help3(new Point(400,300), new Point(500,400),0x00ff00); draw_help3(new Point(400,300), new Point(300,500),0xffff00); draw_help3(new Point(400,300), new Point(300,400),0x0000ff); draw_help3(new Point(100,600), new Point(800,10),0xff0000); } private function draw_help3(start:Point, end:Point, color:uint):void { bmd.lock(); var dx:int = end.x - start.x; var dy:int = end.y - start.y; var x:int = start.x; var y:int = start.y; var xInc:int; var yInc:int; var i:int; if(dx >=0) { xInc = 1; }else { xInc = -1; dx = -dx;//總共要走多少步 } if(dy >= 0) { yInc = 1; }else { yInc = -1; dy = -dy; } //比較值的時候,都是按照第一象限來比較,只不過,步進的時候,按照 xInc, yInc 來步進 var k2dx:int = 2*dy; var error2dx:int = k2dx - 1; var k2dy:int = 2*dx; var error2dy:int = k2dy - 1; if(dx >= dy)//近X軸線 { for(i = 0; i <= dx ; i++) { bmd.setPixel32(x,y, color); if(error2dx > 0) { y += yInc; error2dx = error2dx + k2dx - 2*dx; }else { error2dx = error2dx + k2dx; } x += xInc; } }else//近Y軸線 { for(i = 0; i <= dy ; i++) { bmd.setPixel32(x,y, color); if(error2dy > 0) { x += xInc; error2dy = error2dy + k2dy - 2*dy; }else { error2dy = error2dy + k2dy; } y += yInc; } } bmd.unlock(); } private function draw_help2(start:Point, end:Point):void { bmd.lock(); var dx:int = end.x - start.x; var dy:int = end.y - start.y; var x:int = start.x; var y:int = start.y; var k2dx:int = 2*dy; var error2dx:int = k2dx - 1; for(var i:int = 0; i < dx ; i++) { bmd.setPixel32(x,y, 0xffff00); if(error2dx > 0) { y++; error2dx = error2dx + k2dx - 2*dx; }else { error2dx = error2dx + k2dx; } x++; } bmd.unlock(); } private function draw_help(start:Point, end:Point):void { bmd.lock(); //必須是整點 var dx:int = end.x - start.x; var dy:int = end.y - start.y; var x:int = start.x; var y:int = start.y; var k:Number = dy/dx; var error:Number = k - 0.5;//初始值 for(var i:int = 0; i < dx ; i++) { bmd.setPixel32(x,y, 0xffffff); if(error > 0)//取右上的點 { y++; error = error + k - 1; }else { error = error + k; } x++; } bmd.unlock(); } } }
貼圖:
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
http://wenku.baidu.com/link?url=ova6h39VQ4ilLMIQ51qHpjeuuKu7yLtD1M53NQQc6rUSI9UHymh2r2V8ZZdn6R3bForxZzFf65KO_JCY6flz5A4TU8F4uEHs0N82iCmdQwG
從上面介紹的DDA演算法可以看到,由於在迴圈中涉及實型資料的加減運算,因此直線的生成速度較慢。
在生成直線的演算法中,Bresenham演算法是最有效的演算法之一。Bresenham演算法是一種基於誤差判別式來生成直線的方法。
它也是採用遞推步進的辦法,令每次最大變化方向的座標步進一個象素,同時另一個方向的座標依據誤差判別式的符號來決定是否也要步進一個象素。
我們首先討論m=△y/△x,當0≤m≤1且x1<x2時的Bresenham演算法。從DDA直線演算法可知這些條件成立時,公式(2-2)、(2-3)可寫成:
xi+1=xi+1 | (2-6) |
yi+1=yi+m | (2-7) |
有兩種Bresenham演算法思想,它們各自從不同角度介紹了Bresenham演算法思想,得出的誤差判別式都是一樣的。
由於顯示直線的象素點只能取整數值座標,可以假設直線上第i個象素點座標為(xi,yi),它是直線上點(xi,yi)的最佳近似,並且xi=xi(假設m<1),如下圖所示。那麼,直線上下一個象素點的可能位置是(xi+1,yi)或(xi+1,yi+1)。
由圖中可以知道,在x=xi+1處,直線上點的y值是y=m(xi+1)+b,該點離象素點(xi+1,yi)和象素點(xi+1,yi+1)的距離分別是d1和d2:d1=y-yi=m(xi+1)+b-yi | (2-8) |
d2=(yi+1)-y=(yi+1)-m(xi+1)-b | (2-9) |
這兩個距離差是
d1-d2=2m(xi+1)-2yi+2b-1 | (2-10) |
我們來分析公式(2-10):
(1)當此值為正時,d1>d2,說明直線上理論點離(xi+1,yi+1)象素較近,下一個象素點應取(xi+1,yi+1)。
(2)當此值為負時,d1<d2,說明直線上理論點離(xi+1,yi)象素較近,則下一個象素點應取(xi+1,yi)。
(3)當此值為零時,說明直線上理論點離上、下兩個象素點的距離相等,取哪個點都行,假設演算法規定這種情況下取(xi+1,yi+1)作為下一個象素點。
因此只要利用(d1-d2)的符號就可以決定下一個象素點的選擇。為此,我們進一步定義一個新的判別式:
pi=△x×(d1-d2)=2△y·xi-2△x·yi+c | (2-11) |
式(2-11)中的△x=(x2-x1)>0,因此pi與(d1-d2)有相同的符號;
這裡△y=y2-y1,m=△y/△x;c=2△y+△x(2b-1)。
下面對式(2-11)作進一步處理,以便得出誤差判別遞推公式並消除常數c。
將式(2-11)中的下標i改寫成i+1,得到:
pi+1=2△y·xi+1-2△x·yi+1+c | (2-12) |
將式(2-12)減去(2-11),並利用xi+1=xi+1,可得:
pi+1= pi+2△y-2△x·(yi+1-yi) | (2-13) |
再假設直線的初始端點恰好是其象素點的座標,即滿足:
y1=mx1+b | (2-14) |
由式(2-11)和式(2-14)得到p1的初始值:
p1=2△y-△x | (2-15) |
這樣,我們可利用誤差判別變數,得到如下演算法表示:
初始 p1=2△y-△x | (2-16) |
當pi≥0時: yi+1=yi+1, xi+1=xi+1, pi+1=pi+2(△y-△x) |
|
否則: yi+1=yi, |
從式(2-16)可以看出,第i+1步的判別變數pi+1僅與第i步的判別變數pi、直線的兩個端點座標分量差△x和△y有關,運算中只含有整數相加和乘2運算,而乘2可利用算術左移一位來完成,因此這個演算法速度快並易於硬體實現。
由於象素座標的整數性,數學點(xi,yi)與所取象素點(xi,yir)間會引起誤差(εi),當xi列上已用象素座標(xi,yir)表示直線上的點(xi,yi),下一直線點B(xi+1,yi+1),是取象素點C(xi+1,yir ),還是D(xi+1,y(i+1)r)呢?
設A為CD邊的中點,正確的選擇:
若B點在A點上方,選擇D點; 否則,選C點。
用誤差式描述為:
ε(xi+1)=BC-AC=(yi+1-yir)-0.5 | (2-8') |
求遞推公式:
ε(xi+2)=(yi+2-y(i+1)r)-0.5 = yi+1+m-y(i+1)r-0.5 | (2-9') |
當ε(xi+1)≥0時,選D點,y(i+1)r = yir+1
ε(xi+2)= yi+1+m-yir-1-0.5=ε(xi+1)+m-1 | (2-10') |
當ε(xi+1)﹤0時,選C點,y(i+1)r = yir
ε(xi+2)= yi+1+m-yir-0.5=ε(xi+1)+m | (2-11') |
初始時:
ε(xs+1)=BC-AC=m-0.5 | (2-12') |
為了運算中不含實型數,同時不影響不等式的判斷,將方程兩邊同乘一正整數。
令方程兩邊同乘2·Δx,即d=2·Δx·ε,則:
初始時:
d = 2·Δy-Δx | (2-13') |
遞推式:
當d≥0時:{ d=d+2·(Δy-Δx); y++; x++; } 否則: { d=d+2·Δy; x++; } |
(2-14') |
條件:0≤m≤1且x1<x2
1、輸入線段的兩個端點座標和畫線顏色:x1,y1,x2,y2,color;
2、設定象素座標初值:x=x1,y=y1;
3、設定初始誤差判別值:p=2·Δy-Δx;
4、分別計算:Δx=x2-x1、Δy=y2-y1;
5、迴圈實現直線的生成:
for(x=x1;x<=x2;x++)
{ putpixel(x,y,color) ;
if(p>=0)
{ y=y+1;
p=p+2·(Δy-Δx);
}
else
{ p=p+2·Δy;
}
}
現在我們修正(2-16)公式,以適應對任何方向及任何斜率線段的繪製。如下圖所示,線段的方向可分為八種,從原點出發射向八個區。由線段按圖中所示的區域位置可決定xi+1和yi+1的變換規律。
容易證明:當線段處於①、④、⑧、⑤區時,以|△x|和|△y|代替前面公式中的△x和△y,當線段處於②、③、⑥、⑦區時,將公式中的|△x|和|△y|對換,則上述兩公式仍有效。
線上段起點區分線段方向
斜率小於1 | 斜率大於1 |
由於程式中不含實型數運算,因此速度快、效率高,是一種有效的畫線演算法。
void Bresenhamline (int x1,int y1,int x2,int y2,int color) { int x, y, dx, dy, s1, s2, p, temp, interchange, i; x=x1; y=y1; dx=abs(x2-x1); dy=abs(y2-y1); if(x2>x1) s1=1; else s1=-1; if(y2>y1) s2=1; else s2=-1; if(dy>dx) { temp=dx; dx=dy; dy=temp; interchange=1; } else interchange=0; p=2*dy-dx; for(i=1;i<=dx;i++) { putpixel(x,y,color); if(p>=0) { if(interchange= =0) y=y+s2; else x=x+s1; p=p-2*dx; } if(interchange= =0) x=x+s1; else y=y+s2; p=p+2*dy; } } |
http://cg.sjtu.edu.cn/lecture_site/chap2/mainframe212.htm
2.1.2 Bresenham演算法
Bresenham演算法是計算機圖形學典型的直線光柵化演算法。
- 從另一個角度看直線光柵化顯示演算法的原理
- 由直線的斜率確定選擇在x方向或y方向上每次遞增(減)1個單位,另一變數的遞增(減)量為0或1,它取決於實際直線與最近光柵網格點的距離,這個距離的最大誤差為0.5。
- 1)Bresenham的基本原理
- 假定直線斜率k在0~1之間。此時,只需考慮x方向每次遞增1個單位,決定y方向每次遞增0或1。
設
直線當前點為(xi,y)
直線當前光柵點為(xi,yi)則
下一個直線的點應為(xi+1,y+k)
下一個直線的光柵點
或為右光柵點(xi+1,yi)(y方向遞增量0)
或為右上光柵點(xi+1,yi+1)(y方向遞增量1)記直線與它垂直方向最近的下光柵點的誤差為d,有:d=(y+k)–yi,且
0≤d≤1
當d<0.5:下一個象素應取右光柵點(xi+1,yi)
當d≥0.5:下一個象素應取右上光柵點(xi+1,yi+1)如果直線的(起)端點在整數點上,誤差項d的初值:d0=0,
x座標每增加1,d的值相應遞增直線的斜率值k,即:d=d + k。
一旦d≥1,就把它減去1,保證d的相對性,且在0-1之間。令e=d-0.5,關於d的判別式和初值可簡化成:
e的初值e0= -0.5,增量亦為k;
e<0時,取當前象素(xi,yi)的右方象素(xi+1,yi);
e>0時,取當前象素(xi,yi)的右上方象素(xi+1,yi+1);
e=0時,可任取上、下光柵點顯示。Bresenham演算法的構思巧妙:它引入動態誤差e,當x方向每次遞增1個單位,可根據e的符號決定y方向每次遞增 0 或 1。
e<0,y方向不遞增
e>0,y方向遞增1
x方向每次遞增1個單位,e = e + k因為e是相對量,所以當e>0時,表明e的計值將進入下一個參考點(上升一個光柵點),此時須:e = e - 1
- 假定直線斜率k在0~1之間。此時,只需考慮x方向每次遞增1個單位,決定y方向每次遞增0或1。
- 2)Bresenham演算法的實施——Rogers 版
- 通過(0,0)的所求直線的斜率大於0.5,它與x=1直線的交點離y=1直線較近,離y=0直線較遠,因此取光柵點(1,1)比(1,0)更逼近直線;
如果斜率小於0.5,則反之;
當斜率等於0.5,沒有確定的選擇標準,但本演算法選擇(1,1)(程式)
- //Bresenham's line resterization algorithm for the first octal.
//The line end points are (xs,ys) and (xe,ye) assumed not equal.
// Round is the integer function.
// x,y, ∆x, ∆y are the integer, Error is the real.
//initialize variables
x=xs
y=ys
∆x = xe -xs
∆y = ye -ys
//initialize e to compensate for a nonzero intercept
Error =∆y/∆x-0.5
//begin the main loop
for i=1 to ∆x
WritePixel (x, y, value)
if (Error ≥0) then
y=y+1
Error = Error -1
end if
x=x+1
Error = Error +∆y/∆x
next i
finish
- //Bresenham's line resterization algorithm for the first octal.
- 通過(0,0)的所求直線的斜率大於0.5,它與x=1直線的交點離y=1直線較近,離y=0直線較遠,因此取光柵點(1,1)比(1,0)更逼近直線;
- 3)整數Bresenham演算法
- 上述Bresenham演算法在計算直線斜率和誤差項時要用到浮點運算和除法,採用整數算術運算和避免除法可以加快演算法的速度。
由於上述Bresenham演算法中只用到誤差項(初值Error =∆y/∆x-0.5)的符號
因此只需作如下的簡單變換:
NError = 2*Error*∆x
即可得到整數演算法,這使本演算法便於硬體(韌體)實現。
(程式)
- //Bresenham's integer line resterization algorithm for the first octal.
//The line end points are (xs,ys) and (xe,ye) assumed not equal. All variables are assumed integer.
//initialize variables
x=xs
y=ys
∆x = xe -xs
∆y = ye -ys
//initialize e to compensate for a nonzero intercept
NError =2*∆y-∆x //Error =∆y/∆x-0.5
//begin the main loop
for i=1 to ∆x
WritePixel (x, y)
if (NError >=0) then
y=y+1
NError = NError –2*∆x //Error = Error -1
end if
x=x+1
NError = NError +2*∆y //Error = Error +∆y/∆x
next i
finish
- //Bresenham's integer line resterization algorithm for the first octal.
- 上述Bresenham演算法在計算直線斜率和誤差項時要用到浮點運算和除法,採用整數算術運算和避免除法可以加快演算法的速度。
- 4)一般Bresenham演算法
-
要使第一個八卦的Bresenham演算法適用於一般直線,只需對以下2點作出改造:
當直線的斜率|k|>1時,改成y的增量總是1,再用Bresenham誤差判別式確定x變數是否需要增加1;
x或y的增量可能是“+1”或“-1”,視直線所在的象限決定。(程式)
- //Bresenham's integer line resterization algorithm for all quadrnts
//The line end points are (xs,ys) and (xe,ye) assumed not equal. All variables are assumed integer.
//initialize variables
x=xs
y=ys
∆x = abs(xe -xs) //∆x = xe -xs
∆y = abs(ye -ys) //∆y = ye -ys
sx = isign(xe -xs)
sy = isign(ye -ys)
//Swap ∆x and ∆y depending on the slope of the line.
if ∆y>∆x then
Swap(∆x,∆y)
Flag=1
else
Flag=0
end if
//initialize the error term to compensate for a nonezero intercept
NError =2*∆y-∆x
//begin the main loop
for i=1 to ∆x
WritePixel(x, y , value)
if (Nerror>=0) then
if (Flag) then //∆y>∆x,Y=Y+1
x=x+sx
else
y=y+sy
end if // End of Flag
NError = NError –2*∆x
end if // End of Nerror
if (Flag) then //∆y>∆x,X=X+1
y=y+sy
else
x=x+sx
end if
NError = NError +2*∆y
next i
finish
- //Bresenham's integer line resterization algorithm for all quadrnts
-
-